Minimal auth service to protect backends behind Traefik using ForwardAuth. Tokens are hashed and stored in Redis with optional TTLs and rate limiting. Includes a tiny HTML admin UI (protected via Basic Auth).
- Traefik (Ingress) → ForwardAuth → FastAPI auth service → backend service
- Auth flow:
- Client sends
Authorization: Bearer <token> - Traefik calls
GET /authon this service - Service checks Redis:
tokens:<token>hash with fieldhosts - If requested host is in token's allowed hosts → 200 OK; else 401/403
- Client sends
auth_service/app.py— FastAPI app (/auth,/healthz, admin UI, hashing, rate limits)auth_service/templates/index.html— simple token manager UIauth_service/requirements.txt— pinned dependenciesauth_service/Dockerfile— container for auth servicedocker-compose.yml— local stack with Redis + authk8s/— K8s manifests for auth service, Redis, and Traefik middleware/ingress
- Docker / Docker Compose
- Redis (docker-compose provides one)
- Start stack:
docker compose up --build- Open admin UI:
http://localhost:8000/
-
Create token for hosts (comma-separated), e.g.
trofkm.ru,firecrawl.trofkm.ru. You will be prompted for Basic Auth (set via env). Optionally set TTL seconds. -
Test the auth endpoint:
curl -H "Authorization: Bearer <your_token>" \
-H "X-Forwarded-Host: trofkm.ru" \
http://localhost:8000/auth- OK →
200 OKwith bodyOK - Wrong/missing token →
401 - Token without access to host →
403
-
GET /auth- Headers:
Authorization: Bearer <token>(required)X-Forwarded-Host: <requested-host>(Traefik sets this; send manually for testing)
- Responses:
200 OK,401,403
- Headers:
-
GET /healthz→okwhen Redis is reachable -
Admin UI
GET /— list tokens and allowed hosts, email, comment, TTLPOST /create_token(form fields:hosts, optionalemail,comment,ttl_seconds)POST /delete_token(form fieldtoken)
- Tokens are never stored in plaintext. We store
SHA-256(token + PEPPER)only. - Key:
tokens:<sha256>(hash)- Field:
hosts→host1,host2,... - Optional TTL is applied per-token.
- Field:
- Rate limiting: sliding buckets per token hash (
RATE_LIMIT_WINDOW_SEC,RATE_LIMIT_MAX).
REDIS_HOST(default:localhost)REDIS_PORT(default:6379)REDIS_DB(default:0)REDIS_USERNAME(optional)REDIS_PASSWORD(optional; required if Redis secured)REDIS_TLS(true|false, defaultfalse)REDIS_TLS_SKIP_VERIFY(true|false, defaultfalse)PEPPER(required; server-side secret for hashing)ADMIN_USER,ADMIN_PASS(required for admin UI Basic Auth)TOKEN_TTL_SECONDS(default0, no default TTL)RATE_LIMIT_WINDOW_SEC(default1)RATE_LIMIT_MAX(default20)
Build manually (if needed):
docker build -t acl-auth-service:local ./auth_serviceRun manually:
docker run --rm -p 8000:8000 \
-e REDIS_HOST=host.docker.internal \
acl-auth-service:local- Push your built image to a registry and update image in
k8s/auth-service.yaml:
containers:
- name: auth-service
image: ghcr.io/your-org/acl-auth-service:latest- Apply manifests (includes Secrets for demo; change values):
- Create ConfigMap and Secrets from your local env/values
# Non-secrets from your local .env (e.g., TOKEN_TTL_SECONDS, RATE_LIMIT_*)
kubectl create configmap auth-service-config \
--from-env-file=.env \
-n default \
--dry-run=client -o yaml | kubectl apply -f -
# App secrets (pepper and admin basic auth)
kubectl create secret generic auth-service-secrets \
--from-literal=pepper=CHANGE_ME \
--from-literal=admin_user=admin \
--from-literal=admin_pass=CHANGE_ME \
-n default --dry-run=client -o yaml | kubectl apply -f -
# Redis password (used by both Redis and the app)
kubectl create secret generic redis-auth \
--from-literal=password=CHANGE_ME_REDIS \
-n default --dry-run=client -o yaml | kubectl apply -f -
# Deploy or update resources
kubectl apply -f k8s/auth-service.yaml
kubectl rollout restart deploy/auth-service -n defaultkubectl apply -f k8s/auth-service.yaml
kubectl apply -f k8s/ingress-traefik.yamlNotes:
- The Traefik middleware in
k8s/ingress-traefik.yamlforwards tohttp://auth-service.default.svc.cluster.local:8000/auth. - Add the middleware annotation to any Ingress you want protected.
k8s/ingress-traefik.yaml defines:
- Middleware:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: auth-middleware
namespace: default
spec:
forwardAuth:
address: "http://auth-service.default.svc.cluster.local:8000/auth"
trustForwardHeader: true- Ingress (example backend service
firecrawl-service):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: firecrawl
namespace: default
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.middlewares: default-auth-middleware@kubernetescrd
spec:
rules:
- host: firecrawl.trofkm.ru
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: firecrawl-service
port:
number: 80- Use HTTPS on the public edge (Traefik TLS) so tokens are not sent in cleartext.
- Tokens are hashed with
PEPPERand never stored raw. RotatePEPPERby re-issuing tokens. - Use Redis AOF for persistence; back up AOF/RDB off-cluster.
- Consider managed Redis (Sentinel/Cluster) for HA; test restoration regularly.
- Restrict admin UI (Basic Auth already enabled); additionally use IP allowlists, NetworkPolicies, or mTLS.
401 missing bearer token— ensureAuthorization: Bearer <token>is present.401 invalid token— token not found in Redis; create via UI.403 forbidden for host— host not in token'shostslist.503 redis unavailable— check Redis connection/env vars.
MIT