pyramid_openapi3
pyramid_openapi3 copied to clipboard
Explorer UI refers to wrong procol if operated behind referse proxy with HTTPS
Setup:
- reverse proxy, implements TLS, let's say
https://example.com
- pyramid app w/ pyramid_openapi3, behind the proxy, HTTP
- pyramid config
config.include("pyramid_openapi3") config.pyramid_openapi3_spec('docs/openapi.yaml', route='/api/openapi.yaml') config.pyramid_openapi3_add_explorer(route='/api/')
The API explorer UI opens up fine when we request https://example.com/api/
. but the URL for spec file we get in the top most text box is http://example.com/api/openapi.yaml
(note http
instead of https
).
I've tracked this down to index.html
template rendering, specifically call to request.route_url()
: https://github.com/Pylons/pyramid_openapi3/blob/29f0180a97d9d33cbe65488aee97af8339c120c4/pyramid_openapi3/init.py#L162
This will generate and absolute URL, and it will not take into account the protocol (it cannot?).
I've tried setting X-Forwarded-Proto
on the proxy - did not help.
I also could not find a global setting in Pyramid for the 'public URL' we operate on.
The easiest fix I can see is to generate a relative URL by passing _app_url=''
to request.route_url()
.
I'm running on Heroku, with a reverse proxy in front, and it works as expected: https://dash.paretosecurity.com/api/v1
Can you share a bit more about your setup?
I'm running on Heroku, with a reverse proxy in front, and it works as expected: https://dash.paretosecurity.com/api/v1
Can you share a bit more about your setup?
Sure, thanks for your answer! We have Apache HTTPD as a reverse proxy, it listens on 443, has TLS configured and so on:
ProxyPass "/api/" "http://localhost:6543/api/"
ProxyPassReverse "/api/" "http://localhost:6543/api/"
# also, this is set, but I think I'll remove it later
ProxyPreserveHost On
The Pyramid app is quite simple, created with cookiecutter as per docs, run with pserve, waitress. There is nothing configured that would relate to how we serve, etc (maybe there should be something..?).
Hmm, I'm also using https://pypi.org/project/pyramid_force_https/
, maybe that makes it work? Could you give it a try?
Hmm, I'm also using
https://pypi.org/project/pyramid_force_https/
, maybe that makes it work? Could you give it a try?
Doesn't work with pyramid_force_https
at all.
I guess that is because any plain http request it receives it will redirect to https, but with our config (which is pretty common), pyramid only handles plain http, and any redirected https will go back to proxy to be proxied via http to pyramid, essentially creating a loop.
I'm out of ideas, sorry. Happy to review a PR with a fix when you figure it out.
I'm having the same issue, app is deployed behind a reverse proxy. The thing is, everything behind the reverse proxy is in HTTP, so the application gets the request as http, and in the spec_url, it's https.
It would require some time to provide a full docker-compose minimal example, I'll try to come with it.
A fix would be to be able to set the protocol, for instance by putting a parameter in https://github.com/Pylons/pyramid_openapi3/blob/368119d926ebf5a84d78a061664fbf673c57c119/pyramid_openapi3/init.py#L149
for instance
def add_explorer_view(
config: Configurator,
route: str = "/docs/",
route_name: str = "pyramid_openapi3.explorer",
template: str = "static/index.html",
ui_version: str = "4.15.5",
permission: str = NO_PERMISSION_REQUIRED,
apiname: str = "pyramid_openapi3",
protocol: str = "http"
) -> None:
Then, I'm not familiar enough with the Pyramid Request object to know how to force the protocol.
Would this be an acceptable solution ? Should I try a PR ?
Yes, please do!
Great! Can this be closed now?
@zupo @kskarthik Came back to this and I am a bit confused - why don't we make the URL relative? It removes the need for any protocol/port handling whatsoever. It's also a simple change, works like a charm:
--- a/pyramid_openapi3/__init__.py
+++ b/pyramid_openapi3/__init__.py
@@ -179,7 +179,7 @@ def add_explorer_view(
_port=proto_port[1],
)
else:
- spec_url = request.route_url(settings[apiname]["spec_route_name"])
+ spec_url = request.route_path(settings[apiname]["spec_route_name"])
template = Template(f.read())
html = template.safe_substitute(
Is there any requirement that openapi spec should be a fully qualified URL and not an absolute path?
P.S. Yes, I understand that the protocol+port API was merged and we'd have to something about it.
I don't have a good answer, it looks like it should work? Can you create a PR so that CI runs tests and we see if something starts failing? Or clone and run tests locally and report back please.
I don't have a good answer, it looks like it should work? Can you create a PR so that CI runs tests and we see if something starts failing? Or clone and run tests locally and report back please.
Done that, see #238. Of course I've fixed tests because they were specifically looking for an absolute URI. Of course it works in manual testing.