Traefik behind Teleport redirection issues

Hi everyone!

I'm posting my problem here after spending several hours searching and trying different configurations.

I have a Raspberry-based app that is accessed directly through IP when in local LAN and through Teleport from outside.

On that system I have several http services running (portainer, syncthing, pgweb, nodered and others) running on a docker stack. Each service has internally a different ip/port and Traefik serves a central point in order to access them as http//:<IP>/portainer, http//:<IP>/nodered, http//:<IP>/pgweb, etc.

On NodeRed, the Dashboard plugin serves a central framed UI that allows the user to browse the different services from it.

I've managed to configure Traefik to handle url rewrites and redirections to, for example route from http//:<IP>/nodered/ to http://<docker-IP>:1880/ and http//:<IP>/dasboard/ to http//:<docker-IP>/:1880/ui. I even added redirects to handle the case of missing trailing slashes.

The problem comes when I access the device through Teleport. On our Teleport cluster this device gets an address like device.our-domain.net and accessing it with https://device.our-domain.net/nodered/ woks as expected but using https://device.our-domain.net/nodered (with no trailing slash) triggers the Traefik redirection and the browser is redirected to http://internal-docker-IP:<Traefik-port>/nodered (the raefik redirection replacement target.

If I hardcode the redirection rule like:

http:
  middlewares:
      add-trailing-slash:
        RedirectRegex:
          regex: "^(https?://[^/]+/)([a-z0-9_]+)$"
          replacement: "https://device.our-domain.net/${2}/"
          permanent: true

The redirection works as expected but that would break when accessed from the local LAN

I would be able to distinguish when the request comes from Teleport thanks to the Referer header, but I don't know how to use it in the redirection rule.

Any help?

PS: Part of my config

middlewares.yml

# traefik.yml (or a file in the dynamic configuration directory)
http:
  middlewares:
      add-trailing-slash:
        RedirectRegex:
          regex: "^(https?://[^/]+/)([a-z0-9_]+)$"
          replacement: "https://device.our-domain.net/${2}/"
          permanent: true

    strip-prefix: 
      StripPrefixRegex:
        regex: "/[a-z0-9_]+"

    # Header Dump plugin config
    my-headerdump:
      plugin:
        headerdump:
          Prefix: HDlog
          TLS: "true"

    # This middleware is used on almost all docker service routers
    handle-service:
      chain:
        middlewares:
          - my-headerdump
          - add-trailing-slash
          - strip-prefix

Docker-compose

name: mx100
services:
  traefik:
    container_name: traefik
    image: traefik:3
    command:
      # - "--api.dashboard"
      - "--api.insecure"
      - "--providers.docker"
      - "--providers.docker.network=mx100"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.file=true"
      - "--providers.file.filename=/config/middlewares.yml"
      - "--entrypoints.web.address=:3088"
      - "--experimental.plugins.headerdump.modulename=github.com/jaybubs/headerdump"
      - "--experimental.plugins.headerdump.version=v0.1.0"
      - "--log.level=INFO"
      # - "--log.level=DEBUG"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    ports:
      - "3088:3088"         # Traefik Web UI
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${MX100_DATA}/traefik/middlewares.yml:/config/middlewares.yml
    networks:
      - mx100

  portainer:
    container_name: portainer
    image: portainer/portainer:1.24.1
    restart: unless-stopped
    command: --no-auth
    volumes:
      - ${MX100_DATA}/portainer:/data
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - "9000:9000"         # Acceso GUI
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=mx100"
      - "traefik.http.routers.portainer.rule=PathRegexp(`^/portainer.*`)"
      - "traefik.http.routers.portainer.middlewares=handle-service@file"

  nodered:
    container_name: nodered
    restart: unless-stopped
    image: ghcr.io/digicard/nodered:${NODERED_IMAGE_TAG}
    depends_on:
      - "mqtt"
    healthcheck:
      disable: true
    user: root
    environment:
      - TZ=America/Argentina/Buenos_Aires
      - NODE_OPTIONS=--max-old-space-size=128
      - MX100_COMPANY_NAME=digi
      - MX100_COMPANY_SITE=alirpi5
      - MX100_PANELID=05
      - PLATFORM=RPI5
    volumes:
      - ${MX100_DATA}/nodered:/data
      - /etc/localtime:/etc/localtime:ro
      - /root/.ssh:/root/.ssh:ro
    ports:
      - "1880:1880"         # Acceso GUI
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=mx100"
      - "traefik.http.routers.nodered.rule=PathPrefix(`/dashboard`) || PathPrefix(`/nodered`)"
      - "traefik.http.middlewares.nodered-dashboard.RedirectRegex.regex=^(https?://[^/]+)/dashboard/?$$"
      - "traefik.http.middlewares.nodered-dashboard.RedirectRegex.replacement=$${1}/nodered/ui/"
      - "traefik.http.middlewares.nodered-dashboard.RedirectRegex.permanent=true"
      - "traefik.http.middlewares.nodered.replacepathregex.regex=^/nodered/(.*)"      
      - "traefik.http.middlewares.nodered.replacepathregex.replacement=/$${1}"
      - "traefik.http.routers.nodered.middlewares=nodered-dashboard,nodered,add-trailing-slash@file"
    networks:
      - mx100

... Other services...

If you need different middlewares for your different channels in, then you would need to create multiple routers, where you match with the referrer.

In general using a single domain with path is not recommended, as not all web GUI apps like to be placed under a path, but they expect to be root.

They will usually load dependencies like /static/script.js, which will not arrive at the target service because it’s without the custom path.

Hi bluepuma!

Yeahh that’s what I did. In the docker-compose there are 8 ui-services each with their traefik configuration labels working perfectly when your access them locally.

I already went that way at first. The problem with that approach is that Teleport needs to authenticate the user in every each of these 8 domains, and it takes a loooooong time on the first access. That's why I went for the one-domain, multiple-subdirs approach.

So far, I haven't found any problem. Many of the services are single page (glances, wetty) I only had an issue with a swagger doc page generated with Fast API but tweaked with some code rewriting.

All what I need is a way of redirecting to the original (external) url, not the internal (Teleport redirected) one.