MrWho

Docker Security Best Practices for MrWhoOidc

Version: 1.0 Last Updated: 2025-11-02 Target Audience: Security engineers, operations teams

This document provides comprehensive security hardening guidelines for deploying MrWhoOidc in production environments using Docker.

Table of Contents

  1. Security Overview
  2. Container Security
  3. Network Security
  4. Secrets Management
  5. TLS/Certificate Security
  6. Database Security
  7. Redis Security
  8. Access Control
  9. Monitoring and Auditing
  10. Compliance Considerations
  11. Security Checklist

Security Overview

Defense in Depth Strategy

MrWhoOidc implements multiple layers of security:

  1. Container Isolation: Non-root user, read-only filesystems, minimal attack surface
  2. Network Segmentation: Internal networks for database/cache, edge network for public access
  3. Secrets Protection: Environment variables, volume mounts, external secret stores
  4. TLS Everywhere: Encrypted communication, certificate validation
  5. Audit Logging: Comprehensive logging of security events
  6. Regular Updates: Automated image builds with security patches

Threat Model

Threats Addressed:

Out of Scope (requires additional infrastructure):

Container Security

1. Run as Non-Root User

MrWhoOidc containers run as non-root by default:

# Already configured in Dockerfile
USER 1654

Verification:

# Check user in running container
docker compose exec webauth whoami
# Should output: "appuser" or numeric UID 1654, NOT root

Why Important: Prevents container escape exploits from gaining root access on host.

2. Use Minimal Base Images

# Dockerfile uses chiseled Ubuntu base
FROM mcr.microsoft.com/dotnet/aspnet:9.0-jammy-chiseled

Benefits:

3. Read-Only Root Filesystem

For webauth container, consider read-only root filesystem:

services:
  webauth:
    image: ghcr.io/popicka70/mrwhooidc:latest
    read_only: true
    tmpfs:
      - /tmp
      - /var/tmp

Important: Test thoroughly - some features may require write access to specific paths.

4. Drop Unnecessary Capabilities

services:
  webauth:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Only if binding to ports <1024

5. Limit Resources

Prevent DoS from resource exhaustion:

services:
  webauth:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

6. Security Scanning

Scan images for vulnerabilities:

# Using Docker Scout
docker scout cves ghcr.io/popicka70/mrwhooidc:latest

# Using Trivy
trivy image ghcr.io/popicka70/mrwhooidc:latest

# Using Grype
grype ghcr.io/popicka70/mrwhooidc:latest

Best Practice: Scan on every build in CI/CD pipeline.

7. Image Signing and Verification

Sign images with Docker Content Trust:

# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1

# Pull only signed images
docker pull ghcr.io/popicka70/mrwhooidc:latest

For Publishers: Sign images in CI/CD:

# .github/workflows/docker-publish.yml
- name: Sign image
  run: |
    docker trust sign ghcr.io/popicka70/mrwhooidc:$

Network Security

1. Network Segmentation

Architecture:

networks:
  internal:
    driver: bridge
    internal: true  # CRITICAL: No external access
  edge:
    driver: bridge

services:
  webauth:
    networks:
      - internal  # Access to database/redis
      - edge      # Public access
  
  postgres:
    networks:
      - internal  # Isolated, no public access
  
  redis:
    networks:
      - internal  # Isolated, no public access

Verification:

# Postgres should NOT have external connectivity
docker compose exec postgres ping -c 1 google.com
# Should fail: "ping: bad address 'google.com'"

2. Firewall Rules

Host Firewall (iptables/firewalld):

# Allow only HTTPS traffic to webauth
iptables -A INPUT -p tcp --dport 8443 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Block direct access to PostgreSQL/Redis ports
iptables -A INPUT -p tcp --dport 5432 -j DROP
iptables -A INPUT -p tcp --dport 6379 -j DROP

# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

Cloud Security Groups (AWS/Azure/GCP):

3. Reverse Proxy with TLS Termination

Recommended Architecture:

Internet → Reverse Proxy (nginx/Traefik) → Webauth Container
                ↓ TLS termination

nginx Configuration:

upstream mrwhooidc {
    server localhost:8443;
}

server {
    listen 443 ssl http2;
    server_name auth.company.com;

    # TLS Configuration
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Rate Limiting
    limit_req_zone $binary_remote_addr zone=auth:10m rate=10r/s;
    limit_req zone=auth burst=20 nodelay;

    location / {
        proxy_pass https://mrwhooidc;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Traefik Configuration (docker-compose.yml):

services:
  traefik:
    image: traefik:v2.10
    command:
      - --providers.docker=true
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.letsencrypt.acme.email=admin@company.com
      - --certificatesresolvers.letsencrypt.acme.storage=/acme.json
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./acme.json:/acme.json
    networks:
      - edge

  webauth:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mrwhooidc.rule=Host(`auth.company.com`)"
      - "traefik.http.routers.mrwhooidc.entrypoints=websecure"
      - "traefik.http.routers.mrwhooidc.tls.certresolver=letsencrypt"
      - "traefik.http.middlewares.ratelimit.ratelimit.average=100"
      - "traefik.http.middlewares.ratelimit.ratelimit.burst=50"

4. Disable Unnecessary Ports

Only expose required ports:

services:
  webauth:
    ports:
      - "8443:8443"  # HTTPS only
      # DO NOT expose 8080 (HTTP) in production

5. Network Encryption

PostgreSQL TLS (production recommendation):

postgres:
  command: >
    postgres
    -c ssl=on
    -c ssl_cert_file=/etc/ssl/certs/server.crt
    -c ssl_key_file=/etc/ssl/private/server.key
  volumes:
    - ./certs/postgres-server.crt:/etc/ssl/certs/server.crt:ro
    - ./certs/postgres-server.key:/etc/ssl/private/server.key:ro

Update connection string:

webauth:
  environment:
    ConnectionStrings__authdb: "Host=postgres;Database=authdb;Username=oidc;Password=${POSTGRES_PASSWORD};SSL Mode=Require"

Redis TLS (if using Redis Cloud or requiring encryption):

redis:
  command: >
    redis-server
    --tls-port 6379
    --port 0
    --tls-cert-file /etc/redis/certs/redis.crt
    --tls-key-file /etc/redis/certs/redis.key
    --tls-ca-cert-file /etc/redis/certs/ca.crt

Secrets Management

1. Never Commit Secrets

Critical Files (add to .gitignore):

.env
certs/*.pfx
certs/*.key
secrets/

Verification:

# Check repository for leaked secrets
git log -p | grep -i "password\|secret\|key" --color

# Use tools
trufflehog git file://. --only-verified
gitleaks detect --source . -v

2. Environment Variable Security

Development (.env file):

# Set restrictive permissions
chmod 600 .env

# Verify
ls -la .env
# Should show: -rw------- (owner read/write only)

Production Options:

Option A: Docker Secrets (Swarm Mode)

services:
  webauth:
    secrets:
      - postgres_password
      - cert_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
      CERT_PASSWORD_FILE: /run/secrets/cert_password

secrets:
  postgres_password:
    external: true
  cert_password:
    external: true

Create secrets:

echo "strong-password" | docker secret create postgres_password -
echo "cert-password" | docker secret create cert_password -

Option B: HashiCorp Vault

# Fetch secrets from Vault at runtime
export POSTGRES_PASSWORD=$(vault kv get -field=password secret/mrwhooidc/postgres)
export CERT_PASSWORD=$(vault kv get -field=password secret/mrwhooidc/cert)

docker compose up -d

Option C: Cloud Provider Secrets

AWS Secrets Manager:

# Fetch from AWS Secrets Manager
export POSTGRES_PASSWORD=$(aws secretsmanager get-secret-value \
  --secret-id mrwhooidc/postgres-password \
  --query SecretString \
  --output text)

Azure Key Vault:

# Fetch from Azure Key Vault
export POSTGRES_PASSWORD=$(az keyvault secret show \
  --vault-name mrwhooidc-vault \
  --name postgres-password \
  --query value \
  --output tsv)

Google Secret Manager:

# Fetch from GCP Secret Manager
export POSTGRES_PASSWORD=$(gcloud secrets versions access latest \
  --secret="mrwhooidc-postgres-password")

3. Certificate Security

Storage:

# Store certificates with restrictive permissions
chmod 600 certs/aspnetapp.pfx
chmod 600 certs/*.key
chmod 644 certs/*.crt

# Verify
ls -la certs/

Rotation:

# Rotate certificates annually (Let's Encrypt: every 90 days)
# 1. Generate new certificate
# 2. Update certs/ directory
# 3. Restart webauth: docker compose restart webauth

Backup:

# Backup certificates to secure location (encrypted)
tar czf certs-backup-$(date +%Y%m%d).tar.gz certs/
gpg --encrypt --recipient admin@company.com certs-backup-*.tar.gz
# Store encrypted backup offsite

4. Database Password Security

Requirements:

Generation:

# Generate strong password
openssl rand -base64 32

# Or using pwgen
pwgen -s 32 1

Rotation Procedure:

# 1. Update password in secret store (Vault/AWS/Azure)
# 2. Update PostgreSQL password
docker compose exec postgres psql -U postgres -c "ALTER USER oidc WITH PASSWORD 'new-password';"

# 3. Update .env or secret
POSTGRES_PASSWORD=new-password

# 4. Restart webauth
docker compose restart webauth

# 5. Verify
docker compose logs webauth | grep "database connection"

TLS/Certificate Security

1. Certificate Requirements

Production Certificates:

2. Let’s Encrypt Automation

Using Certbot:

# Install certbot
apt-get install certbot

# Obtain certificate (standalone mode - stops on port 80)
certbot certonly --standalone -d auth.company.com

# Certificates saved to:
# /etc/letsencrypt/live/auth.company.com/fullchain.pem
# /etc/letsencrypt/live/auth.company.com/privkey.pem

# Convert to PFX for ASP.NET Core
openssl pkcs12 -export \
  -out ./certs/aspnetapp.pfx \
  -inkey /etc/letsencrypt/live/auth.company.com/privkey.pem \
  -in /etc/letsencrypt/live/auth.company.com/fullchain.pem \
  -password pass:YourCertPassword

# Auto-renewal (cron)
0 0 * * * certbot renew --post-hook "docker compose restart webauth"

3. TLS Configuration

Enforce TLS 1.2+ only:

webauth:
  environment:
    Kestrel__Endpoints__Https__Protocols: Http1AndHttp2
    Kestrel__Endpoints__Https__SslProtocols: Tls12,Tls13

4. HTTP Strict Transport Security (HSTS)

Enable HSTS in reverse proxy (see nginx config above) or application:

webauth:
  environment:
    HSTS_ENABLED: true
    HSTS_MAX_AGE: 31536000  # 1 year

Database Security

1. PostgreSQL Hardening

Connection Limits:

postgres:
  command: >
    postgres
    -c max_connections=100
    -c shared_buffers=256MB
    -c log_connections=on
    -c log_disconnections=on

Authentication:

# Edit pg_hba.conf (inside container)
docker compose exec postgres bash
echo "host all all 0.0.0.0/0 scram-sha-256" >> /var/lib/postgresql/data/pg_hba.conf

Best Practice: Use scram-sha-256 instead of md5 or password.

2. Database Encryption at Rest

Volume Encryption:

Example (LUKS):

# Create encrypted volume
cryptsetup luksFormat /dev/sdb
cryptsetup open /dev/sdb postgres-encrypted

# Format and mount
mkfs.ext4 /dev/mapper/postgres-encrypted
mount /dev/mapper/postgres-encrypted /var/lib/docker/volumes/mrwhooidc_postgres-data/_data

3. Backup Encryption

# Encrypt backups with GPG
docker compose exec postgres pg_dump -U oidc authdb | \
  gzip | \
  gpg --encrypt --recipient admin@company.com \
  > backup-encrypted-$(date +%Y%m%d).sql.gz.gpg

# Decrypt when restoring
gpg --decrypt backup-encrypted-YYYYMMDD.sql.gz.gpg | \
  gunzip | \
  docker compose exec -T postgres psql -U oidc authdb

4. Audit Logging

Enable PostgreSQL audit logging:

postgres:
  command: >
    postgres
    -c log_statement=all
    -c log_duration=on
    -c log_line_prefix='%t [%p]: user=%u,db=%d,app=%a,client=%h '

Warning: log_statement=all logs everything (verbose). For production, use log_statement=ddl or mod.

Redis Security

1. Authentication

Require password:

redis:
  command: redis-server --requirepass ${REDIS_PASSWORD} --save 60 1 --loglevel warning

Update connection string:

webauth:
  environment:
    Redis__ConnectionString: "redis:6379,password=${REDIS_PASSWORD},abortConnect=false"

2. Disable Dangerous Commands

redis:
  command: >
    redis-server
    --requirepass ${REDIS_PASSWORD}
    --rename-command FLUSHDB ""
    --rename-command FLUSHALL ""
    --rename-command CONFIG ""
    --save 60 1

3. Network Isolation

Redis should NEVER be exposed publicly:

redis:
  networks:
    - internal  # Isolated network only
  # NO ports section - do not expose to host

Access Control

1. Admin UI Access

Restrict Admin Access:

nginx IP Whitelist:

location /admin {
    allow 10.0.0.0/8;      # Internal network
    allow 203.0.113.0/24;  # Office IP range
    deny all;

    proxy_pass https://mrwhooidc;
}

2. Container Shell Access

Disable Shell in Production:

services:
  webauth:
    security_opt:
      - no-new-privileges:true

Audit Shell Access:

# Log all docker exec commands
auditctl -w /usr/bin/docker -p x -k docker_exec

3. Role-Based Access Control (RBAC)

Docker Host Access:

Monitoring and Auditing

1. Security Event Logging

Enable Audit Logs:

webauth:
  environment:
    Logging__LogLevel__MrWhoOidc.Auth: Information
    Logging__LogLevel__MrWhoOidc.WebAuth.Handlers: Information

Critical Events to Log:

2. Log Aggregation

Centralized Logging (ELK Stack):

services:
  webauth:
    logging:
      driver: "fluentd"
      options:
        fluentd-address: "fluentd:24224"
        tag: "mrwhooidc.webauth"

Syslog:

webauth:
  logging:
    driver: "syslog"
    options:
      syslog-address: "udp://logserver:514"
      tag: "mrwhooidc"

3. Security Monitoring

Metrics to Monitor:

Alerting Rules:

# Prometheus alert example
groups:
  - name: mrwhooidc_security
    rules:
      - alert: HighFailedLogins
        expr: rate(failed_logins_total[5m]) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High rate of failed logins detected"

4. Intrusion Detection

Host-Based IDS (OSSEC/Wazuh):

# Monitor Docker logs for suspicious activity
<localfile>
  <log_format>json</log_format>
  <location>/var/lib/docker/containers/*/*-json.log</location>
</localfile>

Container Runtime Security (Falco):

# Falco rules for MrWhoOidc
- rule: Unauthorized Process in Container
  condition: spawned_process and container.name = "mrwhooidc-webauth" and not proc.name in (dotnet)
  output: "Unexpected process in MrWhoOidc container (proc=%proc.name)"
  priority: WARNING

Compliance Considerations

1. GDPR Compliance

Data Protection:

2. PCI-DSS (if handling payment data)

Requirements:

3. HIPAA (if handling health data)

Requirements:

4. SOC 2 Type II

Requirements:

Security Checklist

Use this checklist for production deployments:

Container Security

Network Security

Secrets Management

TLS/Certificates

Database Security

Access Control

Monitoring and Auditing

Compliance

Operational Security

Support and Resources

Document Version: 1.0 Last Updated: 2025-11-02 Maintained By: MrWhoOidc Security Team