iFargle / headscale-webui

A simple Headscale web UI for small-scale deployments.
Other
628 stars 57 forks source link

redirect loop with traefik #63

Closed alex1702 closed 1 year ago

alex1702 commented 1 year ago

When I try to go to the main page I get an infinite redirect. Until my browser gives up and gives me an "error: redirect error".

I guess I might have a thinking error somewhere or a bug in traefik, I'm not a traefik expert ^^

By the way, if I call directly the subpages like https://vpnadmin.example.com/settings or https://vpnadmin.example.com/overview then it works.

Because of my little experience with traefik, I had rather used an extra subdomain and not the path "/admin". I think it is actually nicer to reach the admin interface at vpn.example.com/admin.

My docker-compose:

version: '3.5'

services:
  headscale:
    image: headscale/headscale:0
    command: headscale serve
    restart: unless-stopped
    networks:
      - proxy
    volumes:
      - /opt/dockerprojekte/headscale/data/hsconfig:/etc/headscale/
      - hs-data:/var/lib/headscale
    ports:
      - 27896:8080
      - 3478:3478/udp
    environment:
      - TZ=Europe/Berlin
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}.entrypoints=http"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}.rule=Host(`${DOMAIN}`)"
      - "traefik.http.middlewares.${TRAEFIKROUTERNAME}-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}.middlewares=${TRAEFIKROUTERNAME}-https-redirect"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.entrypoints=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.tls=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.service=${TRAEFIKROUTERNAME}"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.tls.certresolver=http01"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.tls.domains[0].main=${DOMAIN}"
      - "traefik.http.services.${TRAEFIKROUTERNAME}.loadbalancer.server.port=8080"
      - "traefik.docker.network=proxy"

  headscale-webui:
    image: ghcr.io/ifargle/headscale-webui:latest
    networks:
      - proxy
    environment:
      - TZ=Europe/Berlin
      - COLOR=red                              # Use the base colors (ie, no darken-3, etc) - 
      - HS_SERVER=http://headscale:8080        # Reachable endpoint for your Headscale server
      - DOMAIN_NAME=https://$DOMAINADMIN       # The base domain name for this container.
      - SCRIPT_NAME=/                          # This is your applications base path (wsgi requires the name "SCRIPT_NAME")
      - KEY                                    # Generate with "openssl rand -base64 32" - used to encrypt your key on disk.
      - AUTH_TYPE=Basic                        # AUTH_TYPE is either Basic or OIDC.  Empty for no authentication
      - LOG_LEVEL=info                         # Log level.  "DEBUG", "ERROR", "WARNING", or "INFO".  Default "INFO"
      # ENV for Basic Auth (Used only if AUTH_TYPE is "Basic").  Can be omitted if you aren't using Basic Auth
      - BASIC_AUTH_USER
      - BASIC_AUTH_PASS
      # ENV for OIDC (Used only if AUTH_TYPE is "OIDC").  Can be omitted if you aren't using OIDC
#      - OIDC_AUTH_URL=https://auth.$DOMAIN/.well-known/openid-configuration # URL for your OIDC issuer's well-known endpoint
#      - OIDC_CLIENT_ID=headscale-webui         # Your OIDC Issuer's Client ID for Headscale-WebUI
#      - OIDC_CLIENT_SECRET=YourSecretHere      # Your OIDC Issuer's Secret Key for Headscale-WebUI
    volumes:
      - hsweb-data:/data                         # Headscale-WebUI's storage.  Make sure ./volume is readable by UID 1000 (chown 1000:1000 ./volume)
      - /opt/dockerprojekte/headscale/data/hsconfig:/etc/headscale/:ro # Headscale's config storage location.  Used to read your Headscale config.
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}.entrypoints=http"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}.rule=Host(`${DOMAINADMIN}`)"
      - "traefik.http.middlewares.${TRAEFIKROUTERNAME2}-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}.middlewares=${TRAEFIKROUTERNAME2}-https-redirect"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.entrypoints=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.rule=Host(`${DOMAINADMIN}`)"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.tls=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.service=${TRAEFIKROUTERNAME2}"
      # redirect /admin to /
      # - "traefik.http.middlewares.${TRAEFIKROUTERNAME2}-stripprefix.stripprefix.forceslash=true"
      # - "traefik.http.middlewares.${TRAEFIKROUTERNAME2}-stripprefix.stripprefix.prefixes=/admin/"
      # - 'traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.middlewares=${TRAEFIKROUTERNAME2}-stripprefix@docker'
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.tls.certresolver=http01"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.tls.domains[0].main=${DOMAINADMIN}"
      - "traefik.http.services.${TRAEFIKROUTERNAME2}.loadbalancer.server.port=5000"
      - "traefik.docker.network=proxy"

volumes:
  hs-data:
  hsweb-data:

networks:
  proxy:
    external: true

Environment variables are set in portainer:

TRAEFIKROUTERNAME=headscale
DOMAIN=vpn.example.com
BASIC_AUTH_USER=username
BASIC_AUTH_PASS=123456
KEY=mykey
DOMAINADMIN=vpnadmin.example.com
TRAEFIKROUTERNAME2=headscalewebui

Call with curl:

curl https://vpnadmin.example.com -v -L -u "username:123456"
*   Trying 1.2.3.4:443...
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=vpnadmin.example.com
*  start date: Mar 28 22:22:23 2023 GMT
*  expire date: Jun 26 22:22:22 2023 GMT
*  subjectAltName: host "vpnadmin.example.com" matched cert's "vpnadmin.example.com"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Server auth using Basic with user 'alex'
* Using Stream ID: 1 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 3 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 5 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 7 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 9 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: b (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: d (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: f (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 11 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 13 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 15 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
............ <truncated> ....................
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 65 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:17 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Maximum (50) redirects followed
curl: (47) Maximum (50) redirects followed
iFargle commented 1 year ago

What do the logs of headscale-webui say? Does it at least say you're getting to /?

alex1702 commented 1 year ago
[2023-03-29 10:52:21 +0200] [1] [INFO] Starting gunicorn 20.1.0

[2023-03-29 10:52:21 +0200] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)

[2023-03-29 10:52:21 +0200] [1] [INFO] Using worker: sync

[2023-03-29 10:52:21 +0200] [7] [INFO] Booting worker with pid: 7

[2023-03-29 10:52:24,066] INFO in server: Headscale-WebUI Version:  v0.5.6 / main

[2023-03-29 10:52:24,066] INFO in server: LOG LEVEL SET TO INFO

[2023-03-29 10:52:24,067] INFO in server: DEBUG STATE:  False

[2023-03-29 10:52:24,067] INFO in server: Loading basic auth libraries and configuring app...

[2023-03-29 11:06:21,387] INFO in helper: All startup checks passed.

[2023-03-29 11:06:21,401] INFO in helper: Testing API key validity.

[2023-03-29 11:06:21,528] INFO in helper: Key check passed.

[2023-03-29 11:06:21,529] INFO in headscale: Getting API key information

[2023-03-29 11:06:21,651] INFO in headscale: Looking for valid API Key...

[2023-03-29 11:06:21,652] INFO in headscale: Key found.

[2023-03-29 11:06:21,653] INFO in renderer: Rendering the Overview page

[2023-03-29 11:06:21,725] INFO in renderer: Opening /etc/headscale/config.yaml

[2023-03-29 11:06:21,747] INFO in headscale: Getting machine information

[2023-03-29 11:06:21,891] INFO in headscale: Getting routes

[2023-03-29 11:06:22,026] INFO in headscale: Getting Users

That is all. Maybe test with log level debug?

iFargle commented 1 year ago

Goofy. /overview and / point to the same function, so if one renders the other should as well:

@app.route('/')
@app.route('/overview')
@oidc.require_login
def overview_page():

What HTML is rendered? (in the Inspect menu in Firefox, for example)

alex1702 commented 1 year ago

There is no HTML page that is sent, Firefox gives an error message itself. Sorry for the german language in the browser screenshot ^^

2023-03-29_11-32 Translated:

Error: Redirection error

An error occurred while connecting to vpnadmin.example.com

    This problem can sometimes occur when cookies are disabled or refused.
Try again
alex1702 commented 1 year ago

For some reason I get back a 308 Permanant Redirect with the location header location: https://vpnadmin.example.com/ The very similar configuration above for vpn.example.com does not do this.

iFargle commented 1 year ago

So when startup checks are ran, the page does redirect based on a few conditions if any checks fail, but it looks like yours passes...

[2023-03-29 11:06:21,387] INFO in helper: All startup checks passed.

One possibility: Have you entered your API key on the /settings page yet? Also, is your KEY variable in qoutes? KEY=mykey should be KEY="mykey"

alex1702 commented 1 year ago

Yes, I have entered the API key under settings.

KEY is passed directly from portainer. I have now tested setting the apostrophes in portainer, but this had no effect. Next changed the docker-compose to: - KEY="${KEY}"

A docker inspect shows:

"Env": [
                "COLOR=red",
                "SCRIPT_NAME=/",
                "KEY=\"4K<censored>Po=\"",

Is now in quotation marks, but the redirect remains.

I have additionally executed a curl call without automatic redirect:

curl https://vpnadmin.example.com/ -v -u "username:<censored>"
*   Trying 1.2.3.4:443...
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=vpnadmin.example.com
*  start date: Mar 28 22:22:23 2023 GMT
*  expire date: Jun 26 22:22:22 2023 GMT
*  subjectAltName: host "vpnadmin.example.com" matched cert's "vpnadmin.example.com"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Server auth using Basic with user 'alex'
* Using Stream ID: 1 (easy handle 0x55b99b207e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 09:44:15 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="https://vpnadmin.example.com/">https://vpnadmin.example.com/</a>. If not, click the link.
* Connection #0 to host vpnadmin.example.com left intact
iFargle commented 1 year ago

Interesting.. Give me a bit to play around.

iFargle commented 1 year ago

Can you try the ghcr.io/ifargle/headscale-webui:testing build? I don't know of any changes that would fix this issue sadly, but I can't rule out I haven't made changes that would fix this issue :)

alex1702 commented 1 year ago

Unfortunately no change

Does gunicorn possibly think that I come in via http and would like to redirect to https for example?

iFargle commented 1 year ago

That was my initial thought. Can you try removing all the http redirects? Just expose it to the https endpoint you have set up?

It should go user -> https -> traefik -> http -> container -> http -> traefik -> https > user

alex1702 commented 1 year ago

is the destination port 5000 correct?

alex1702 commented 1 year ago

I have tested both. Only https and only http same result. very strange

iFargle commented 1 year ago

Yep, destination port is 5000. Let me add more debug logs to the testing build.

iFargle commented 1 year ago

Cool. I'm able to replicate this :) I'll work on it and let you know when I have a fix

alex1702 commented 1 year ago

ok ^^

iFargle commented 1 year ago

Potential fix. Try the testing build again and omit the SCRIPT_NAME variable entirely

alex1702 commented 1 year ago

it works woohoo 🎉

iFargle commented 1 year ago

Perfect!

Don't pull updates. It will break. I'll be pushing this to main in the next week or so

iFargle commented 1 year ago

Closing -- Fixes will be in main in about 1.5 hours (multiarch builds take a while). Please use the main branch after it's available.

Thanks!

vbrandl commented 1 year ago

I have a similar problem with the webui behind a nginx reverse proxy. With SCRIPT_NAME set, I get a redirect loop. The UI should be available under https://headscale.example.com/admin. Nginx takes care for http -> https redirects but https://headscale.example.com/admin redirects me back to http, causing a loop. Removing SCRIPT_NAME results in a 404

joachimtingvold commented 11 months ago

@iFargle , omitting SCRIPT_NAME results in exception:

headscale-webui  |   File "/app/server.py", line 48, in <module>
headscale-webui  |     BASE_PATH      = os.environ["SCRIPT_NAME"] if os.environ["SCRIPT_NAME"] != "/" else ""
headscale-webui  |                                                   ~~~~~~~~~~^^^^^^^^^^^^^^^
headscale-webui  |   File "<frozen os>", line 679, in __getitem__
headscale-webui  | KeyError: 'SCRIPT_NAME'

Attempting to set SCRIPT_NAME=/ results in the eternal redirect-loop described in this issue.

gregistech commented 3 months ago

@iFargle , omitting SCRIPT_NAME results in exception:

headscale-webui  |   File "/app/server.py", line 48, in <module>
headscale-webui  |     BASE_PATH      = os.environ["SCRIPT_NAME"] if os.environ["SCRIPT_NAME"] != "/" else ""
headscale-webui  |                                                   ~~~~~~~~~~^^^^^^^^^^^^^^^
headscale-webui  |   File "<frozen os>", line 679, in __getitem__
headscale-webui  | KeyError: 'SCRIPT_NAME'

Attempting to set SCRIPT_NAME=/ results in the eternal redirect-loop described in this issue.

Same issue as you. Setting it to /admin works tho.