mmcc-xx / BirdCAGE

181 stars 18 forks source link

Reverse proxy support #39

Closed kchristensen closed 1 year ago

kchristensen commented 1 year ago

Greetings! First thanks for this project! I've been setting this up on my docker host, and noticed that unless I'm missing something there's no support for setting a HTTP root. I run all of my docker containers behind a reverse proxy so I can terminate SSL, and prefer the subfolder style of doing so (e.g. https://myhost.com/birdcage/).

It would be swell to be able to set an environment variable for the backend and frontend containers so that when they craft links for redirection and what not that they are prefixed with the appropriate host and path to make this more reverse proxy friendly.

I haven't had a chance to dig in too much but it seems like prepending the prefix to the SWAGGER_URL and API_URL here might be enough to do it on the backend.

Do you have any plans to support such a thing in the roadmap? If not, I can likely take a swing at it, but I didn't want to get too into the weeds since there's another PR out there with some niceties that would factor into such a PR (like this).

kchristensen commented 1 year ago

(Related: I also noticed I can't build the backend container locally, since it's trying to include a requirements.txt that doesn't exist in the repository).

mmcc-xx commented 1 year ago

(just pushed the missing requirements.txt file as I ponder your first question)

mmcc-xx commented 1 year ago

Regarding your main question...

I have my deployment being reverse proxied and SSL'ed by Ngninx Proxy Manager, but I'm using the subdomain approach. I don't know what would be involved in making it more friendly to the sub-folder approach. However, I don't think the SWAGGER_URL and API_URL values would have anything to do with it.

With the subdomain approach, I have https://birdcage.mydomain.com mapped to http://192.168.1.75:7008 and https://birdcageapi.mydomain.com mapped to http://192.168.1.75:7007. Both have to be mapped because the client side scripts in the front end make API calls to the back end. In the docker-compose, the front end container is told the address of the back end so it knows where to send the API calls, and the back end is told the address of the front end so CORS works right.

If the front end was told that the API server was https://mydomain.com/birdcageapi I think that would be OK. However, I don't really know how CORS works... I'd guess you'd use https://mydomains.com ?

If you try it let me know what happens.

mmcc-xx commented 1 year ago

I tried doing a subfolder reverse proxy to my API server and failed. However, don't let discourage you as reverse proxying is close to black magic as far as I'm concerned.

kchristensen commented 1 year ago

So, here's what I'm running into. I'm using swag (nginx) with a basic config that looks like this:

location ^~ /birdcage {
    include /config/nginx/proxy.conf;
    resolver 127.0.0.11 valid=30s;
    rewrite ^/birdcage/(.*)$ /$1 break;
    proxy_pass http://birdcage-frontend:7008;
}

location ^~ /birdcage/api {
    include /config/nginx/proxy.conf;
    resolver 127.0.0.11 valid=30s;
    rewrite ^/birdcage/api/(.*)$ /api/$1 break;
    proxy_pass http://birdcage-backend:7007;
}

Then in my docker config:

API_SERVER_URL: https://docker.mydomain.com/birdcage
and 
CORS_ORIGINS: https://docker.mydomain.com/birdcage/

in their respective entries.

Fire stuff up, and the API works as expected:

❯ curl https://docker.mydomain.com/birdcage/api/app_health/db_health
{"file_size":126976,"integrity_check":true,"table_stats":[{"row_count":1,"table_name":"streams"},{"row_count":16,"table_name":"user_preferences"},{"row_count":2,"table_name":"sqlite_sequence"},{"row_count":1,"table_name":"recording_metadata"},{"row_count":305,"table_name":"detections"},{"row_count":1,"table_name":"filter_thresholds"},{"row_count":0,"table_name":"species_overrides"},{"row_count":3,"table_name":"notification_services"},{"row_count":0,"table_name":"notification_assignments"},{"row_count":1,"table_name":"commands"},{"row_count":106,"table_name":"birdsoftheweek"}]}

(FWIW that url is behind a password on the web interface but doesn't require any authentication if you know the url)

The app will load at https://docker.mydomain.com/birdcage/ and looks more or less right, but all the links won't factor in that the actual url because they're all relative links in the code, so the Reports > App Health link is just https://docker.mydomain.com/app_health etc.

image

So it seems like API_SERVER_URL works as expected in the frontend container, but there might be a need for a SERVER_URL variable or something similar for the non API links on the frontend since they're all hardcoded relative links?

Edit: I did confirm that the frontend links besides / work as expected manually:

❯ curl -s https://docker.mydomain.com/birdcage/app_health 2>&1 | grep "Database Health"
                    <div class="card-header">Database Health</div>
mmcc-xx commented 1 year ago

(FWIW that url is behind a password on the web interface but doesn't require any authentication if you know the url) Hmmm... I thought I had put the "protected" decorator on that. I'll have to check.

If you don't mind digging into the issue with the sub-folder based reverse proxying, I'd be happy to incorporate a pull request providing it doesn't break the subdomain based reverse proxying.

Thanks!

kchristensen commented 1 year ago

Sure, I'll see what I can put together.

rpatel3001 commented 1 year ago

+1 thanks for the issue/PR, it would make reverse proxying from a single subdomain easier

Currently using the following traefik config to serve both the frontend and API from a single subdomain, would be nice to have to make less specific exceptions for the routes:

http:
  routers:
    birds-rtr:
      entrypoints:
        - websecure
      service: birds-srv
      rule: Host(`birds.mydomain.net`)
      middlewares: oauth-mid
    birdsapi-rtr:
      entrypoints:
        - websecure
      service: birdsapi-srv
      rule: Host(`birds.mydomain.net`) && (PathPrefix(`/api`) || Path(`/services`) || Path(`/assignments`) || Path(`/static/openapi.yaml`))
      middlewares: oauth-mid
  services:
    birds-srv:
      loadbalancer:
        servers:
          - url: http://frontend_ip:7008
    birdsapi-srv:
      loadbalancer:
        servers:
          - url: http://api_ip:7007