openkilt / openrepo

Open Source repository management for deb, rpm, and generic packages
GNU Affero General Public License v3.0
171 stars 11 forks source link

Cannot login behind reverse proxy #2

Closed populationless closed 1 year ago

populationless commented 1 year ago

Hi,

I have found your project and wanted to test it out. I deployed using docker-compose and can access the website and login just fine using http://myLocalIP:7376. I have HAProxy running as reverse proxy for HTTPS SSL offloading. The website is accessible fine, however when I try to login at https://apt.mydomain.com, I get a 403 error:

image

The log shows: WARNING log:241: Unauthorized: /api/whoami WARNING log:241: Unauthorized: /api/repos/ WARNING log:241: Unauthorized: /api/signingkeys/ WARNING log:241: Forbidden (Origin checking failed - https://apt.mydomain.com does not match any trusted origins.): /admin/login/

Searching online, I have found this qestion on StackOverflow, which seems to be related. However, I am not a programmer.

If you need further info or testing, please let me know.

populationless commented 1 year ago

I have done a little more digging. Seeing as you are using Django 4.0.7 and nginx as a proxy for it, I believe this might be a possible solution. From looking at the nginx.conf, https is not passed as X-Forwarded-Proto. I'm going to play around a bit and see if I can make it work.

matthill commented 1 year ago

Can you share your haproxy config as well? I'll try and reproduce the problem on my side. I think you're probably right about it being related to a header not pulling through

populationless commented 1 year ago

Sure, here is my sanitized haproxy.conf. For what it's worth, I'm running HAProxy on OPNsense, although that shouldn't matter.

#
# Automatically generated configuration.
# Do not edit this file manually.
#

global
    uid                         80
    gid                         80
    chroot                      /var/haproxy
    daemon
    stats                       socket /var/run/haproxy.socket group proxy mode 775 level admin
    nbproc                      1
    nbthread                    4
    hard-stop-after             60s
    no strict-limits
    maxconn                     10000
    tune.ssl.default-dh-param   4096
    spread-checks               2
    tune.bufsize                16384
    tune.lua.maxmem             0
    log                         /var/run/log local0 info
    lua-prepend-path            /tmp/haproxy/lua/?.lua

defaults
    log     global
    option redispatch -1
    maxconn 5000
    timeout client 30s
    timeout connect 30s
    timeout server 30s
    retries 3
    default-server init-addr last,libc
    default-server maxconn 5000

# autogenerated entries for ACLs

# autogenerated entries for config in backends/frontends

# autogenerated entries for stats

# Frontend: 0_SNI_Frontend (Listening on 0.0.0.0:80, 0.0.0.0:443)
frontend 0_SNI_Frontend
    bind 0.0.0.0:443 name 0.0.0.0:443 
    bind 0.0.0.0:80 name 0.0.0.0:80 
    mode tcp
    default_backend SSL_Backend
    # tuning options
    timeout client 30s

    # logging options

# Frontend: 1_HTTP_Frontend (Listening on 10.11.12.13:80)
frontend 1_HTTP_Frontend
    bind 10.11.12.13:80 name 10.11.12.13:80 accept-proxy 
    mode http
    option http-keep-alive
    option forwardfor
    # tuning options
    timeout client 30s

    # logging options
    # ACL: No_SSL_Condition
    acl acl_61f0e69b774101.44572667 ssl_fc

    # ACTION: HTTP_to_HTTPS_redirect-rule
    http-request redirect scheme https code 301 if !acl_61f0e69b774101.44572667

# Frontend: 1_HTTPS_Frontend (Listening on 10.11.12.13:443)
frontend 1_HTTPS_Frontend
    http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    bind 10.11.12.13:443 name 10.11.12.13:443 accept-proxy ssl curves secp384r1  no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384 ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 alpn h2,http/1.1 crt-list /tmp/haproxy/ssl/61f1488b809469.11422827.certlist 
    mode http
    option http-keep-alive
    default_backend WWW_Backend
    option forwardfor
    # tuning options
    timeout client 15m

    # logging options
    # ACL: Local_Subnet_Condition
    acl acl_61f143f1b2bc74.96164672 src 10.10.10.0/24 10.10.11.0/24 10.10.12.0/24

    # ACTION: Local_subdomains_map-rule
    use_backend %[req.hdr(host),lower,map_dom(/tmp/haproxy/mapfiles/61f143408eb157.92527912.txt)] if acl_61f143f1b2bc74.96164672
    # ACTION: Public_subdomains_map-rule
    # NOTE: actions with no ACLs/conditions will always match
    use_backend %[req.hdr(host),lower,map_dom(/tmp/haproxy/mapfiles/61f0e7787a5363.74556424.txt)] 

# Backend: SSL_Backend ()
backend SSL_Backend
    # health checking is DISABLED
    mode tcp
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    server SSL_Server 10.11.12.13 send-proxy-v2 check-send-proxy

# Backend: Mail_Backend ()
backend Mail_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server mail 10.10.10.10 ssl alpn h2,http/1.1 verify none

# Backend: Nextcloud_Backend ()
backend Nextcloud_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server nextcloud 10.10.10.10:2443 ssl verify none

# Backend: Vaultwarden_Backend ()
backend Vaultwarden_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server vaultwarden 10.10.10.10:1443 

# Backend: Cockpit_Backend ()
backend Cockpit_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server cockpit 10.10.10.10:9090 ssl verify none

# Backend: Brocade_Backend ()
backend Brocade_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server brocade 10.10.10.240:443 ssl verify none

# Backend: Draytek_Backend ()
backend Draytek_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server draytek 10.10.10.1:443 ssl verify none

# Backend: ILO_Backend ()
backend ILO_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server ilo 10.10.10.11:443 ssl verify none

# Backend: OPNsense_Backend ()
backend OPNsense_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server opnsense 10.10.10.254:1443 ssl verify none

# Backend: Print_Backend ()
backend Print_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server print 10.10.10.15:443 ssl verify none

# Backend: Autoconfig_Backend ()
backend Autoconfig_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server autoconfig 10.10.10.10 ssl verify none

# Backend: Autodiscover_Backend ()
backend Autodiscover_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server autoconfig 10.10.10.10 ssl verify none

# Backend: Collabora_Backend ()
backend Collabora_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server collabora 10.10.10.10:9980 ssl verify none

# Backend: APC_Backend ()
backend APC_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    # ACL: APC_Condition
    acl acl_61f869835bd219.58052927 path -i /

    # ACTION: Redirect_APC-rule
    http-request redirect code 301 location https://apc.mydomain.com/cgi-bin/apcupsd/multimon.cgi if acl_61f869835bd219.58052927
    http-reuse safe
    server apc 10.10.10.10:1080 

# Backend: UniFi_Backend ()
backend UniFi_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server unifi 10.10.10.10:8443 ssl verify none

# Backend: Notify_Backend ()
backend Notify_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server notify 10.10.10.10:7867 source 10.10.10.254

# Backend: Element_Backend ()
backend Element_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server element 10.10.10.10:3443 

# Backend: Matrix_Backend ()
backend Matrix_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    # WARNING: pass through options below this line
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-reuse safe
    server matrix 10.10.10.10:8008 

# Backend: WWW_Backend ()
backend WWW_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server www 10.10.10.10:5443 ssl verify none

# Backend: Jellyfin_Backend ()
backend Jellyfin_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server jellyfin 10.10.10.10:8096 

# Backend: Fritz_Backend ()
backend Fritz_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server fritz 10.10.12.1:80 

# Backend: Freetz_Backend ()
backend Freetz_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server freetz 10.10.12.1:81 

# Backend: Deluge_Backend ()
backend Deluge_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    # WARNING: pass through options below this line
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-reuse safe
    server deluge 10.10.10.10:8112 

# Backend: QNAP_Backend ()
backend QNAP_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server qnap 10.10.10.235:9090 ssl verify none

# Backend: Mikrotik_Backend ()
backend Mikrotik_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server mikrotik 10.10.10.241:80 

# Backend: APT_Backend ()
backend APT_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server apt 10.10.10.10:7376 

# statistics are DISABLED

APT_Backend refers to OpenRepo. I have tried adding http-request set-header X-Forwarded-Proto https if { ssl_fc } as well, didn't make a difference with unmodified nginx.conf.

I have also created a copy of nginx.conf in my docker directory and mounted this file in the container using docker-volumes. Simply adding proxy_set_header X-Forwarded-Proto https; and restarting the container didn't work either.

matthill commented 1 year ago

I was able to make this work by making this code change: https://github.com/openkilt/openrepo/commit/d1e1791d0d7deea473ff0c7e84a648f2fa385e63

and adding this line to the haproxy config: http-request add-header X-Forwarded-Proto https if { ssl_fc }

for example:

# Backend: APT_Backend ()
backend APT_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    server apt 127.0.0.1:7376 

I pushed an update to the dockerhub on this image, please let me know if it works for you: docker pull openkilt/openrepo:latest

populationless commented 1 year ago

Pulled the updated image, added the line to haproxy.conf, works 🥳 Had to docker-compose down && docker-compose up -d again for it to work. I'm going to close this as fixed. Thanks for looking at this, happy holidays and happy new year!

danwalkeruk commented 1 year ago

Just wanted to add an update to say I had the same experience but using nginx - I chose to use my own nginx instance on the local host to handle SSL and forward on to the nginx Docker instance, but I was getting the CSRF issue.

I resolved it by adding the following to my nginx config:

    location / {
            proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
            proxy_pass http://127.0.0.1:7376;
    }

But I also had to add the following to the settings.py file within the Django container:

CSRF_TRUSTED_ORIGINS = ["https://openrepo.acme.net"]

Would it be worth adding this to the settings.py file, based upon the environment variable for the domain?

matthill commented 1 year ago

I think that setting (CSRF_TRUSTED_ORIGINS) disables CSRF validation for the domain, which could be unsafe. Hopefully there's a way to make it work without needing to bypass that security feature.

I wonder if the issue is the host header is not being passed through? Django’s CSRF protection requires that header match the origin present in the Host header. Perhaps try this instead:

location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For  $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header Connection "";
        proxy_pass http://127.0.0.1:7376;
}
danwalkeruk commented 1 year ago

Thanks @matthill, I was missing one of those, now resolved. Might be worth adding HAProxy and Nginx reverse proxy info in the main readme?

I just need to figure out why trailing slashes are mandatory in URLs when accessing the repo files/folders now, think that might be a configuration issue with the provided nginx container config, will open a new issue if I can dig a bit deeper...

moritzfriedrich commented 4 months ago

I am currently failing to configure the proxy of an upstream Nginx with TLS certificate. @danwalkeruk What exactly does your Nginx configuration look like? And what values do you use to start the OpenRepo containers?

danwalkeruk commented 4 months ago

I am currently failing to configure the proxy of an upstream Nginx with TLS certificate. @danwalkeruk What exactly does your Nginx configuration look like? And what values do you use to start the OpenRepo containers?

My nginx configuration looks like this (domains etc changed):

server {
    listen 80;
    server_name myreposerver.net;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name myreposerver.net;

    ssl_certificate     /etc/ssl/myreposerver.net.crt;
    ssl_certificate_key /etc/ssl/myreposerver.net.key;

    client_max_body_size 100M;

    location / {
           # proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
       # proxy_pass http://127.0.0.1:7376;

        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For  $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header Connection "";
        proxy_pass http://127.0.0.1:7376;
    }
}

I'm using the standard Docker setup, the nginx service part looks like this:

services:
  nginx:
    image: openkilt/openrepo:latest
    command: nginx
    restart: unless-stopped
    ports:
      - "7376:8080"
    depends_on:
      - "django"
    volumes:
      - /opt/openrepo:/var/lib/openrepo

Hope that helps!

moritzfriedrich commented 4 months ago

Hope that helps!

Oh yes, that helped me. Now it works as I had expected. Thank you very much.