Traefik v2 simple trick to bypass basic Auth

I have spend a huge amount of time to find a way to bypass basic authentication depending on source IP/network and never find a way to do so. But yesterday i finally succeeded to manage this need.

note : i am using Traefik with Docker without Swarm. I am not an export and i am learning.

Goal:

bypass basic auth for a defined list of networks/ip using a single host rule and a single entrypoint

Trick

Create 2 routers with the same host rule and use HeadersRegexp

http:
  routers:
    router1:
      entryPoints:
        - websecure
      rule: Host(`mysinglehostrule`)
      middlewares:
         - Security-Headers@file
         - BasicAuthMiddleware@file
      service: myservice
      tls:
        options: default
        certResolver: xxx
        domains:
          - main: xxxxxx
            sans:
              - "*.xxxxxxxxx"
    router2:
      entryPoints:
        - websecure
      rule: Host(`mysinglehostrule`) && HeadersRegexp(`X-Real-Ip`, `(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)`)
      middlewares:
         - Security-Headers@file
         - AgentsNetworksIpWhitelist@file
      service: myservice
      tls:
        options: default
        certResolver: xxx
        domains:
          - main: xxxxxx
            sans:
              - "*.xxxxxxx"
  services:
    myservice:
    ..........

In this example i would whitelist RFC 1918 but you can adapt to your taste :slight_smile:

Hope it can help someone........

Have a nice day.

Thank you for sharing this. People are really keen on "complete working examples" here, so this could be improved by posting the rest of the configuration, that one can paste into the files and up with their docker-compose (or just docker).

Hi Zespri, thank you for your feedback.
It's not that easy to give a full working example without double checking private informations have been changed.
Here is a more complete configuration.

  • traefik docker-compose.yml
version: "3.3"

services:

  traefik:
    image: "traefik:v2.0"
    container_name: "traefik"
    networks:
      - traefik
      - dockerproxy
    ports:
      - "80:80"
      - "443:443"

    volumes:
      - /srv/conf/traefik/traefik.yml:/etc/traefik/traefik.yml
      - /srv/conf/traefik/dynamic:/etc/traefik/dynamic
      - /srv/data/traefik/letsencrypt:/letsencrypt
      - /srv/data/traefik/log:/var/log
    environment:
      - provider1_ENDPOINT=ep
      - provider1_APPLICATION_KEY=ak
  

    restart: always
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-net

      - traefik.http.routers.traefik-dash2.rule=Host("traefik-dash.dom.com")
      - traefik.http.routers.traefik-dash2.entrypoints=websecure
      - traefik.http.routers.traefik-dash2.tls=true
      - traefik.http.routers.traefik-dash2.tls.options=default
      - traefik.http.routers.traefik-dash2.tls.certresolver=provider1
      - traefik.http.routers.traefik-dash2.tls.domains[0].main=dom.com
      - traefik.http.routers.traefik-dash2.tls.domains[0].sans=*.dom.com
      - traefik.http.routers.traefik-dash2.middlewares=too much
      - traefik.http.routers.traefik-dash2.service=api@internal

  dockerproxy:
    image: tecnativa/docker-socket-proxy
    container_name: dockerproxy
    networks:
      - dockerproxy
    expose:
      - "2375"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:z"
    restart: always
    environment:
      - CONTAINERS=1
    labels:
      - traefik.enable=false

networks:
    traefik:
      external:
        name: traefik-net
    dockerproxy:

  • traefik.yml
global:
  checkNewVersion: true
  sendAnonymousUsage: true

serversTransport:
  insecureSkipVerify: true

EntryPoints:
  web:
    address: :80
  websecure:
    address: :443

log:
#  level: INFO
  level: DEBUG
  filePath: "/var/log/traefik.log"

accessLog:
  filePath: "/var/log/access.log"
  bufferingSize: 100
  fields:
    defaultMode: keep
    headers:
      defaultMode: keep
      names:
          User-Agent: keep


providers:
  providersThrottleDuration: 10s
  docker:
    watch: true
    endpoint: tcp://dockerproxy:2375
    exposedByDefault: false
    swarmMode: false
  file:
    directory: /etc/traefik/dynamic/
    watch: true

api:
  dashboard: true
  debug: true

certificatesResolvers:
  provider1:
    acme:
      email: bob@alice.com
      #caServer: https://acme-staging-v02.api.letsencrypt.org/directory
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: provider1
  • the dynamic configuration file dynamic/myExternalService.yml
http:
  routers:
    cpydio-secure:
      entryPoints:
        - websecure
      rule: Host(`pydio.dom.com`)
      middlewares:
         - Security-Headers@file
         - pydioBasicAuth
         - pydioMaxRequestBody
      service: cpydio-service
      tls:
        options: default
        certResolver: provider1
        domains:
          - main: dom.com
            sans:
              - "*.dom.com"
    cpydio-secure-int:
      entryPoints:
        - websecure
      rule: Host(`pydio.dom.com`) && HeadersRegexp(`X-Real-Ip`, `(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)|(^88\.184\.222\.179)|(^78\.244\.93\.94)`)
      middlewares:
         - Security-Headers@file
         - pydioMaxRequestBody
      service: cpydio-service
      tls:
        options: default
        certResolver: provider1
        domains:
        domains:
          - main: dom.com
            sans:
              - "*.dom.com"
  services:
    cpydio-service:
      loadBalancer:
        servers:
          - url: "https://w.x.y.z:443"
        passHostHeader: true


  middlewares:
    pydioBasicAuth:
      basicAuth:
        realm: "RRRRRRRRRR"
        users:
        - user:passssssssssssssssssssssssssssssssssss
    pydioMaxRequestBody:
      buffering:
        maxRequestBodyBytes: 2048000000

hope this is better.

Best regards,

1 Like

A simple example that works, based on inspiration in this post.

In short you have two routers + priorities with the highest granting without auth middleware and the next priority the production-facing rule.

HTH, cost me a morning to bash that one out.

version: '3'

services:
  traefik:
    # The official v2 Traefik docker image
    image: traefik:v2.2
    container_name: "traefik"
    # Enables the web UI and tells Traefik to listen to docker
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock

  whoami:
    # A container that exposes an API to show its IP address
    image: containous/whoami
    labels:
      - "traefik.http.routers.whoami2.rule=Host(`whoami.localhost`) && HeadersRegexp(`X-Real-Ip`, `^(192\\.168\\.99\\.2)`)"
      - "traefik.http.routers.whoami2.priority=100"
      - "traefik.http.routers.whoami2.middlewares=secured2"
      - "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
      - "traefik.http.routers.whoami.priority=99"
      - "traefik.http.routers.whoami.middlewares=secured"
      - "traefik.http.middlewares.secured.chain.middlewares=auth"
      - "traefik.http.middlewares.secured2.chain.middlewares="
      - "traefik.http.middlewares.auth.basicauth.users=test1:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"
      #- "traefik.http.middlewares.known-ips.ipwhitelist.sourceRange=192.168.1.7,127.0.0.1/32"
2 Likes

This somehow doesn't work for me in traefik 2.4.2 - does it work for you?

[Reviving the topic since it's at the top of Google search results.]

This is working for me with Traefik 2.6.1 using ClientIp condition instead of HeadersRegexp to check for both hosts and subnets. It uses Kubernetes syntax but should be easily adaptable.

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  namespace: example-namespace
  name: example-ingressroute
  labels:
    app: example
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`example.com`)
      kind: Rule
      priority: 99
      services:
        - name: example-service
          port: 3000
    - match: Host(`example.com`) && ! ClientIP(`12.34.56.78/32`, `10.0.0.0/8`)
      kind: Rule
      priority: 100
      services:
        - name: example-service
          port: 3000
      middlewares:
        - name: basicauth-middleware
          namespace: example-namespace
  tls:
    secretName: example-cert