pyramid_openapi3 icon indicating copy to clipboard operation
pyramid_openapi3 copied to clipboard

Explorer UI refers to wrong procol if operated behind referse proxy with HTTPS

Open Jmennius opened this issue 2 years ago • 5 comments

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().

Jmennius avatar Oct 07 '22 10:10 Jmennius

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?

zupo avatar Oct 07 '22 10:10 zupo

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..?).

Jmennius avatar Oct 07 '22 11:10 Jmennius

Hmm, I'm also using https://pypi.org/project/pyramid_force_https/, maybe that makes it work? Could you give it a try?

zupo avatar Oct 07 '22 11:10 zupo

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.

Jmennius avatar Oct 07 '22 12:10 Jmennius

I'm out of ideas, sorry. Happy to review a PR with a fix when you figure it out.

zupo avatar Oct 07 '22 17:10 zupo

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 ?

jschaeff avatar Feb 10 '23 07:02 jschaeff

Yes, please do!

zupo avatar Feb 10 '23 09:02 zupo

Great! Can this be closed now?

kskarthik avatar Jun 19 '23 18:06 kskarthik

@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.

Jmennius avatar Apr 16 '24 15:04 Jmennius

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.

zupo avatar Apr 16 '24 16:04 zupo

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.

Jmennius avatar Apr 17 '24 09:04 Jmennius