enhance(badger): Expose configuration options for badger@http middleware #158

Open
opened 2026-04-05 17:01:44 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @Christopher87R on 3/4/2026

Describe the Bug

The log files do not show the user's IP address, but rather that of the Docker host. Basically, I have a standard

setup: Cloudflare Tunnel (proxy) - VM with Docker-based Pangolin and Traefik.

In Traefik, the forwardedHeaders: trustedIPs are configured and pangolin server has trust_proxy: 2 configured. If I use the fork github.com/hajimAIM/badger in version v1.2.1 instead of the badger traefik plugin github.com/fosrl/badger v1.3.1, it works as expected.

Image Image

Environment

  • OS Type & Version: Debian GNU/Linux 12 (bookworm)
  • Pangolin Version: 1.16.2
  • Gerbil Version: 1.3.0
  • Traefik Version: 3.6
  • Newt Version: not used - whoami on local VM
  • Badger Version: v1.3.1

To Reproduce

docker-compose.yml

name: pangolin
services:
  pangolin:
    image: docker.io/fosrl/pangolin:1.16.2
    container_name: pangolin
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 1g
        reservations:
          memory: 256m
    volumes:
      - ./config:/app/config
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
      interval: "10s"
      timeout: "10s"
      retries: 15
    networks:
      - pangolin

  gerbil:
    image: docker.io/fosrl/gerbil:1.3.0
    container_name: gerbil
    restart: unless-stopped
    depends_on:
      pangolin:
        condition: service_healthy
    command:
      - --reachableAt=http://gerbil:3004
      - --generateAndSaveKeyTo=/var/config/key
      - --remoteConfig=http://pangolin:3001/api/v1/
    volumes:
      - ./config/:/var/config
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    networks:
      - pangolin
      - cftunnel-transport
    ports:
      - 51820:51820/udp
      - 21820:21820/udp
      #- 443:443
      #- 80:80

  traefik:
    image: docker.io/traefik:v3.6
    container_name: traefik
    restart: unless-stopped
    network_mode: service:gerbil # Ports appear on the gerbil service
    depends_on:
      pangolin:
        condition: service_healthy
    command:
      - --configFile=/etc/traefik/traefik_config.yml
    volumes:
      - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
      - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
      - ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
    environment:
      TZ: "Europe/Berlin"
      CF_API_EMAIL: "xxx"
      CF_DNS_API_TOKEN: "xxxx"
      CF_ZONE_API_TOKEN: "xxx"

  whoami:
    image: "traefik/whoami"
    container_name: whoami
    hostname: whoami
    depends_on:
      pangolin:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - pangolin

  cf-tunnel:
    image: cloudflare/cloudflared:latest
    container_name: cf-tunnel
    hostname: cf-tunnel
    restart: unless-stopped
    networks:
      - cftunnel-transport
    command: tunnel --no-autoupdate run
    environment:
      - TUNNEL_TOKEN=xxx

networks:
  pangolin:
    driver: bridge
    name: pangolin
    enable_ipv6: true
  cftunnel-transport:
    driver: bridge
    name: cftunnel-transport
    enable_ipv6: true

config.yml

# To see all available options, please visit the docs:
# https://docs.pangolin.net/

gerbil:
    start_port: 51820
    base_endpoint: "xxx.goodsrv.de"

app:
    dashboard_url: "https://pangolin.xxx.me"
    log_level: "debug"
    #log_level: "info"
    telemetry:
        anonymous_usage: true

domains:
    domain1:
        base_domain: "xxx.me"

server:
    trust_proxy: 2
    secret: "xxx"
    cors:
        origins: ["https://pangolin.xxx.me"]
        methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
        allowed_headers: ["X-CSRF-Token", "Content-Type"]
        credentials: false
    maxmind_db_path: "./config/GeoLite2-Country.mmdb"

email:
    smtp_host: "xxx"
    smtp_port: 587
    smtp_user: "xxx"
    smtp_pass: "xxx"
    no_reply: "xxx"

flags:
    require_email_verification: true
    disable_signup_without_invite: true
    disable_user_create_org: false
    allow_raw_resources: true

traefik_config.yml

api:
  insecure: true
  dashboard: true

providers:
  http:
    endpoint: "http://pangolin:3001/api/v1/traefik-config"
    pollInterval: "5s"
  file:
    filename: "/etc/traefik/dynamic_config.yml"

experimental:
  plugins:
    badger:
      moduleName: "github.com/fosrl/badger"
      version: "v1.3.1"
    real-ip:
      moduleName: "github.com/Paxxs/traefik-get-real-ip"
      version: "v1.0.4"

log:
  level: "INFO"
  format: "common"
  maxSize: 100
  maxBackups: 3
  maxAge: 3
  compress: true

certificatesResolvers:
  letsencrypt:
    acme:
      httpChallenge:
        entryPoint: web
      email: "xxx"
      storage: "/letsencrypt/acme.json"
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
  cloudflare:
    acme:
      email: xxx
      storage: /letsencrypt/acme_cf.json
      keyType: EC384
      dnschallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

entryPoints:
  web:
    address: ":80"
    http:
      #middlewares:
      #  - real-ip@file
    forwardedHeaders:
      trustedIPs: &trustedIps
        # Cloudflare 2024 12 14 https://www.cloudflare.com/ips
        - 173.245.48.0/20
        - 103.21.244.0/22
        - 103.22.200.0/22
        - 103.31.4.0/22
        - 141.101.64.0/18
        - 108.162.192.0/18
        - 190.93.240.0/20
        - 188.114.96.0/20
        - 197.234.240.0/22
        - 198.41.128.0/17
        - 162.158.0.0/15
        - 104.16.0.0/13
        - 104.24.0.0/14
        - 172.64.0.0/13
        - 131.0.72.0/22
        - 2400:cb00::/32
        - 2606:4700::/32
        - 2803:f800::/32
        - 2405:b500::/32
        - 2405:8100::/32
        - 2a06:98c0::/29
        - 2c0f:f248::/32
        # local
        - 172.16.0.0/16
        - 172.17.0.0/16
        - 172.18.0.0/16
        - 172.19.0.0/16
        - 172.20.0.0/16
        - 172.21.0.0/16
        - 172.22.0.0/16
        - 172.23.0.0/16
        
    #proxyProtocol:
    #  trustedIPs: *trustedIps   
        
  websecure:
    address: ":443"
    forwardedHeaders:
      trustedIPs: *trustedIps
    #proxyProtocol:
    #    trustedIPs: *trustedIps 
    transport:
      respondingTimeouts:
        readTimeout: "30m"
    http:
      #middlewares:
      #  - real-ip@file
      #tls:
      #  certResolver: "letsencrypt"
      tls:
        certResolver: cloudflare
        domains:
          - main: xxx.me
      encodedCharacters:
        allowEncodedSlash: true
        allowEncodedQuestionMark: true

serversTransport:
  insecureSkipVerify: true

ping:
  entryPoint: "web"

tls:
  options:
    default:
      minVersion: VersionTLS13
      sniStrict: true
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
      curvePreferences:
        - CurveP521
        - CurveP384

dynamic_config.yml

http:
  middlewares:
    badger:
      plugin:
        badger:
          disableForwardAuth: true
    redirect-to-https:
      redirectScheme:
        scheme: https
    real-ip:
      plugin:
        real-ip:
          enableLog: true # default: false, enable to see detailed logs
          deny403OnFail: true # default: false, when true returns 403 if no matching CDN header found
          eraseProxyHeaders: false # default: false, erase CDN-specific headers after processing
          Proxy:
            - proxyHeadername: X-From-Cdn
              proxyHeadervalue: cf-foo
              realIP: Cf-Connecting-Ip
              OverwriteXFF: true # default: false, v1.0.2 or above
            - proxyHeadername: "*"
              realIP: RemoteAddr

  routers:
    # HTTP to HTTPS redirect router
    main-app-router-redirect:
      rule: "Host(`pangolin.xxx.me`)"
      service: next-service
      entryPoints:
        - web
      middlewares:
        - redirect-to-https
        #- real-ip
        - badger

    # Next.js router (handles everything except API and WebSocket paths)
    next-router:
      rule: "Host(`pangolin.xxx.me`) && !PathPrefix(`/api/v1`)"
      service: next-service
      entryPoints:
        - websecure
      middlewares:
        #- real-ip
        - badger
      tls:
        certResolver: cloudflare
        domains:
          - main: "xxx.me"
            sans:
              - "*.xxx.me"

    # API router (handles /api/v1 paths)
    api-router:
      rule: "Host(`pangolin.xxx.me`) && PathPrefix(`/api/v1`)"
      service: api-service
      entryPoints:
        - websecure
      middlewares:
        #- real-ip
        - badger
      tls:
        certResolver: cloudflare
        domains:
          - main: "xxx.me"
            sans:
              - "*.xxx.me"

    # WebSocket router
    ws-router:
      rule: "Host(`pangolin.xxx.me`)"
      service: api-service
      entryPoints:
        - websecure
      middlewares:
        #- real-ip
        - badger
      tls:
        certResolver: cloudflare
        domains:
          - main: "xxx.me"
            sans:
              - "*.xxx.me"
  services:
    next-service:
      loadBalancer:
        servers:
          - url: "http://pangolin:3002"  # Next.js server

    api-service:
      loadBalancer:
        servers:
          - url: "http://pangolin:3000"  # API/WebSocket server

tcp:
  serversTransports:
    pp-transport-v1:
      proxyProtocol:
        version: 1
    pp-transport-v2:
      proxyProtocol:
        version: 2

Expected Behavior

I would expect my IPv4 (or IPv6, if applicable) address to be in the log file and also at Whoami under X-Real-Ip.

I would expect my IPv4 (or IPv6, if applicable) address to appear in the log file and also under X-Real-Ip in Whoami. This seems to work when I use the fork github.com/hajimAIM/badger in version v1.2.1 for Badger. However, I then have to deactivate the middleware.

*Originally created by @Christopher87R on 3/4/2026* ### Describe the Bug The log files do not show the user's IP address, but rather that of the Docker host. Basically, I have a standard setup: Cloudflare Tunnel (proxy) - VM with Docker-based Pangolin and Traefik. In Traefik, the forwardedHeaders: trustedIPs are configured and pangolin server has trust_proxy: 2 configured. If I use the fork github.com/hajimAIM/badger in version v1.2.1 instead of the badger traefik plugin github.com/fosrl/badger v1.3.1, it works as expected. <img width="1282" height="255" alt="Image" src="https://github.com/user-attachments/assets/d0a7d01e-a25c-4fbb-8242-83e0e79cc7e5" /> <img width="610" height="829" alt="Image" src="https://github.com/user-attachments/assets/76c3c5af-d29b-4e82-a572-147ef6d63a42" /> ### Environment - OS Type & Version: Debian GNU/Linux 12 (bookworm) - Pangolin Version: 1.16.2 - Gerbil Version: 1.3.0 - Traefik Version: 3.6 - Newt Version: not used - whoami on local VM - Badger Version: v1.3.1 ### To Reproduce docker-compose.yml ``` name: pangolin services: pangolin: image: docker.io/fosrl/pangolin:1.16.2 container_name: pangolin restart: unless-stopped deploy: resources: limits: memory: 1g reservations: memory: 256m volumes: - ./config:/app/config healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"] interval: "10s" timeout: "10s" retries: 15 networks: - pangolin gerbil: image: docker.io/fosrl/gerbil:1.3.0 container_name: gerbil restart: unless-stopped depends_on: pangolin: condition: service_healthy command: - --reachableAt=http://gerbil:3004 - --generateAndSaveKeyTo=/var/config/key - --remoteConfig=http://pangolin:3001/api/v1/ volumes: - ./config/:/var/config cap_add: - NET_ADMIN - SYS_MODULE networks: - pangolin - cftunnel-transport ports: - 51820:51820/udp - 21820:21820/udp #- 443:443 #- 80:80 traefik: image: docker.io/traefik:v3.6 container_name: traefik restart: unless-stopped network_mode: service:gerbil # Ports appear on the gerbil service depends_on: pangolin: condition: service_healthy command: - --configFile=/etc/traefik/traefik_config.yml volumes: - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates - ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs environment: TZ: "Europe/Berlin" CF_API_EMAIL: "xxx" CF_DNS_API_TOKEN: "xxxx" CF_ZONE_API_TOKEN: "xxx" whoami: image: "traefik/whoami" container_name: whoami hostname: whoami depends_on: pangolin: condition: service_healthy restart: unless-stopped networks: - pangolin cf-tunnel: image: cloudflare/cloudflared:latest container_name: cf-tunnel hostname: cf-tunnel restart: unless-stopped networks: - cftunnel-transport command: tunnel --no-autoupdate run environment: - TUNNEL_TOKEN=xxx networks: pangolin: driver: bridge name: pangolin enable_ipv6: true cftunnel-transport: driver: bridge name: cftunnel-transport enable_ipv6: true ``` config.yml ``` # To see all available options, please visit the docs: # https://docs.pangolin.net/ gerbil: start_port: 51820 base_endpoint: "xxx.goodsrv.de" app: dashboard_url: "https://pangolin.xxx.me" log_level: "debug" #log_level: "info" telemetry: anonymous_usage: true domains: domain1: base_domain: "xxx.me" server: trust_proxy: 2 secret: "xxx" cors: origins: ["https://pangolin.xxx.me"] methods: ["GET", "POST", "PUT", "DELETE", "PATCH"] allowed_headers: ["X-CSRF-Token", "Content-Type"] credentials: false maxmind_db_path: "./config/GeoLite2-Country.mmdb" email: smtp_host: "xxx" smtp_port: 587 smtp_user: "xxx" smtp_pass: "xxx" no_reply: "xxx" flags: require_email_verification: true disable_signup_without_invite: true disable_user_create_org: false allow_raw_resources: true ``` traefik_config.yml ``` api: insecure: true dashboard: true providers: http: endpoint: "http://pangolin:3001/api/v1/traefik-config" pollInterval: "5s" file: filename: "/etc/traefik/dynamic_config.yml" experimental: plugins: badger: moduleName: "github.com/fosrl/badger" version: "v1.3.1" real-ip: moduleName: "github.com/Paxxs/traefik-get-real-ip" version: "v1.0.4" log: level: "INFO" format: "common" maxSize: 100 maxBackups: 3 maxAge: 3 compress: true certificatesResolvers: letsencrypt: acme: httpChallenge: entryPoint: web email: "xxx" storage: "/letsencrypt/acme.json" caServer: "https://acme-v02.api.letsencrypt.org/directory" cloudflare: acme: email: xxx storage: /letsencrypt/acme_cf.json keyType: EC384 dnschallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "1.0.0.1:53" entryPoints: web: address: ":80" http: #middlewares: # - real-ip@file forwardedHeaders: trustedIPs: &trustedIps # Cloudflare 2024 12 14 https://www.cloudflare.com/ips - 173.245.48.0/20 - 103.21.244.0/22 - 103.22.200.0/22 - 103.31.4.0/22 - 141.101.64.0/18 - 108.162.192.0/18 - 190.93.240.0/20 - 188.114.96.0/20 - 197.234.240.0/22 - 198.41.128.0/17 - 162.158.0.0/15 - 104.16.0.0/13 - 104.24.0.0/14 - 172.64.0.0/13 - 131.0.72.0/22 - 2400:cb00::/32 - 2606:4700::/32 - 2803:f800::/32 - 2405:b500::/32 - 2405:8100::/32 - 2a06:98c0::/29 - 2c0f:f248::/32 # local - 172.16.0.0/16 - 172.17.0.0/16 - 172.18.0.0/16 - 172.19.0.0/16 - 172.20.0.0/16 - 172.21.0.0/16 - 172.22.0.0/16 - 172.23.0.0/16 #proxyProtocol: # trustedIPs: *trustedIps websecure: address: ":443" forwardedHeaders: trustedIPs: *trustedIps #proxyProtocol: # trustedIPs: *trustedIps transport: respondingTimeouts: readTimeout: "30m" http: #middlewares: # - real-ip@file #tls: # certResolver: "letsencrypt" tls: certResolver: cloudflare domains: - main: xxx.me encodedCharacters: allowEncodedSlash: true allowEncodedQuestionMark: true serversTransport: insecureSkipVerify: true ping: entryPoint: "web" tls: options: default: minVersion: VersionTLS13 sniStrict: true cipherSuites: - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 curvePreferences: - CurveP521 - CurveP384 ``` dynamic_config.yml ``` http: middlewares: badger: plugin: badger: disableForwardAuth: true redirect-to-https: redirectScheme: scheme: https real-ip: plugin: real-ip: enableLog: true # default: false, enable to see detailed logs deny403OnFail: true # default: false, when true returns 403 if no matching CDN header found eraseProxyHeaders: false # default: false, erase CDN-specific headers after processing Proxy: - proxyHeadername: X-From-Cdn proxyHeadervalue: cf-foo realIP: Cf-Connecting-Ip OverwriteXFF: true # default: false, v1.0.2 or above - proxyHeadername: "*" realIP: RemoteAddr routers: # HTTP to HTTPS redirect router main-app-router-redirect: rule: "Host(`pangolin.xxx.me`)" service: next-service entryPoints: - web middlewares: - redirect-to-https #- real-ip - badger # Next.js router (handles everything except API and WebSocket paths) next-router: rule: "Host(`pangolin.xxx.me`) && !PathPrefix(`/api/v1`)" service: next-service entryPoints: - websecure middlewares: #- real-ip - badger tls: certResolver: cloudflare domains: - main: "xxx.me" sans: - "*.xxx.me" # API router (handles /api/v1 paths) api-router: rule: "Host(`pangolin.xxx.me`) && PathPrefix(`/api/v1`)" service: api-service entryPoints: - websecure middlewares: #- real-ip - badger tls: certResolver: cloudflare domains: - main: "xxx.me" sans: - "*.xxx.me" # WebSocket router ws-router: rule: "Host(`pangolin.xxx.me`)" service: api-service entryPoints: - websecure middlewares: #- real-ip - badger tls: certResolver: cloudflare domains: - main: "xxx.me" sans: - "*.xxx.me" services: next-service: loadBalancer: servers: - url: "http://pangolin:3002" # Next.js server api-service: loadBalancer: servers: - url: "http://pangolin:3000" # API/WebSocket server tcp: serversTransports: pp-transport-v1: proxyProtocol: version: 1 pp-transport-v2: proxyProtocol: version: 2 ``` ### Expected Behavior I would expect my IPv4 (or IPv6, if applicable) address to be in the log file and also at Whoami under X-Real-Ip. I would expect my IPv4 (or IPv6, if applicable) address to appear in the log file and also under X-Real-Ip in Whoami. This seems to work when I use the fork github.com/hajimAIM/badger in version v1.2.1 for Badger. However, I then have to deactivate the middleware.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/pangolin#158