Adding development-containers that we used to develop fail2ban-ui, can also be used for easy and fast stack-testing

This commit is contained in:
2026-01-19 18:21:24 +01:00
parent 1190aa4f38
commit 62ab6dede3
9 changed files with 1421 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
# Environment variables for keycloak OIDC development setup
# Copy this file to .env and update with your server's IP address or hostname
#
# Usage:
# cp .env.example .env
# # Edit .env with your server details
# podman compose up -d
# Your server's public URL for fail2ban-ui (used for redirect URIs)
# This is the URL that browsers will use to access fail2ban-ui
# Example for remote server: http://172.16.10.18:3080
# Example for localhost: http://localhost:3080
PUBLIC_FRONTEND_URL=http://172.16.10.18:3080
# Your server's public URL for Keycloak (usually same host, different port)
# This is the URL that browsers will use to access Keycloak
# Example for remote server: http://172.16.10.18:3000
# Example for localhost: http://localhost:3000
KEYCLOAK_URL=http://172.16.10.18:3000
# Keycloak's public URL (used for issuer in discovery document)
# Should match KEYCLOAK_URL
# Example for remote server: http://172.16.10.18:3000
# Example for localhost: http://localhost:3000
KEYCLOAK_PUBLIC_URL=http://172.16.10.18:3000

12
development/oidc/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
config/
f2b-run-local/
fail2ban-config-local/
keycloak-data/
keycloak-db/
pocket-id-data/
authentik-media/
authentik-db/
authelia-config/
authelia-db/
authelia-redis/
.env

483
development/oidc/README.md Normal file
View File

@@ -0,0 +1,483 @@
# OIDC Authentication Development Setup
This setup provides a complete OIDC authentication testing environment with Keycloak, Pocket-ID, and Authentik.
## Available OIDC Providers
### 1. Keycloak (Primary - Default)
- **Container:** `DEV_keycloak`
- **Port:** `3000` (mapped from internal `8080`)
- **Management Port:** `9000` (for health checks)
- **URL:** `http://localhost:3000`
- **Admin Console:** `http://localhost:3000`
- **Purpose:** Enterprise-grade OIDC provider (recommended)
- **Data:** Stored in `./keycloak-data/` and `./keycloak-db/`
- **Database:** PostgreSQL (container: `DEV_keycloak-db`)
### 2. Pocket-ID (Alternative 1)
- **Container:** `DEV_pocket-id` (commented out by default)
- **Port:** `3005` (when enabled)
- **URL:** `http://localhost:3005`
- **Purpose:** Lightweight OIDC provider with passkey support
- **Data:** Stored in `./pocket-id-data/`
- **Note:** Uncomment in `container-compose.yml` to use
### 3. Authentik (Alternative 2)
- **Containers:** `DEV_authentik-server`, `DEV_authentik-worker` (commented out by default)
- **Ports:** `3007` (HTTP), `3008` (HTTPS) (when enabled)
- **URL:** `http://localhost:3007`
- **Purpose:** Full-featured identity provider with OIDC support
- **Data:** Stored in `./authentik-media/` and `./authentik-db/`
- **Note:** Requires migrations and initial setup. Uncomment in `container-compose.yml` to use
### 4. Fail2ban-UI
- **Container:** `DEV_fail2ban-ui-oidc`
- **Port:** `3080`
- **URL:** `http://localhost:3080`
- **Purpose:** Main application with OIDC authentication enabled
- **OIDC Provider:** Keycloak (default, configurable)
- **Network:** Uses host network mode to access Keycloak on localhost
## Quick Start
**✅ Automatic Setup:** The OIDC client is automatically configured for Keycloak! Just start the containers and everything should work.
**Note:** All services bind to `0.0.0.0` for easy access from any network interface.
### For Remote Server Access
**Default Configuration:** The setup defaults to `localhost` for local development. For remote server access, you need to create a `.env` file with your server's IP address or hostname.
**Option 1: Using .env file (Recommended)**
1. Copy the example file:
```bash
cd /opt/fail2ban-ui/development/oidc
cp .env.example .env
```
2. Edit `.env` and update with your server's IP address or hostname:
```bash
# Example: If your server IP is 172.16.10.18
PUBLIC_FRONTEND_URL=http://172.16.10.18:3080
KEYCLOAK_URL=http://172.16.10.18:3000
KEYCLOAK_PUBLIC_URL=http://172.16.10.18:3000
```
3. Start containers (docker-compose/podman-compose will automatically load .env):
```bash
podman compose up -d
```
**Option 2: Using environment variables**
```bash
# Set your server's IP address or hostname
export PUBLIC_FRONTEND_URL=http://YOUR_SERVER_IP:3080
export KEYCLOAK_URL=http://YOUR_SERVER_IP:3000
export KEYCLOAK_PUBLIC_URL=http://YOUR_SERVER_IP:3000
# Then start containers
cd /opt/fail2ban-ui/development/oidc
podman compose up -d
```
**Important:**
- Without setting these, redirect URIs will use `localhost` which won't work from remote browsers
- After changing these values, you may need to recreate the Keycloak client:
```bash
podman compose down
rm -rf config/keycloak-client-secret
podman compose up -d
```
## Setup Instructions
### 1. Build the Fail2ban-UI Image
```bash
cd /opt/fail2ban-ui
podman build -t localhost/fail2ban-ui:dev .
# or
docker build -t localhost/fail2ban-ui:dev .
```
### 2. Start the Services
```bash
cd /opt/fail2ban-ui/development/oidc
podman compose up -d
# or
docker-compose up -d
```
**Important Notes:**
- **Keycloak startup time:** Keycloak takes 30-60 seconds to fully start. Be patient!
- **Container status:** The fail2ban-ui container will wait for Keycloak to start
- **If fail2ban-ui is stuck in "Created" status:**
- Wait for Keycloak to show "healthy" status: `podman compose ps`
- Or manually start fail2ban-ui: `podman start DEV_fail2ban-ui-oidc` (it will retry connecting)
### 3. Automatic Keycloak Client Configuration
**✅ Automatic:** The OIDC client is automatically created by the `keycloak-init` container. No manual configuration needed!
The `keycloak-init` container will:
- Wait for Keycloak to be ready
- Automatically create the `fail2ban-ui` OIDC client
- Configure redirect URIs and web origins
- Save the client secret to `/config/keycloak-client-secret`
- Fail2ban-ui will automatically read the secret from this file
**If you see "Client not found" error:**
This means the `keycloak-init` container hasn't run yet or failed. To fix:
1. **Check if Keycloak is running:**
```bash
podman compose ps keycloak
# Should show "healthy" status
```
2. **Run keycloak-init manually:**
```bash
cd /opt/fail2ban-ui/development/oidc
podman compose run --rm keycloak-init
```
3. **Verify the client secret was created:**
```bash
ls -la config/keycloak-client-secret
cat config/keycloak-client-secret
```
4. **Restart fail2ban-ui:**
```bash
podman compose restart fail2ban-ui
```
**Manual Configuration (Alternative):**
If automatic configuration fails, you can manually create the client:
**Manual Setup Steps (if needed):**
1. **Wait for Keycloak to Start:**
- Check logs: `podman logs DEV_keycloak`
- Wait for "Keycloak started" message (may take 30-60 seconds)
- Check container status: `podman compose ps` - Keycloak should show "healthy" status
2. **Access Keycloak Admin Console:**
- Open `http://localhost:3000` in your browser (or use your host's IP address)
- Login with:
- **Username:** `admin`
- **Password:** `admin`
3. **Select Realm:**
- The default configuration uses the `master` realm (already selected)
- **Optional:** Create a custom realm for production use (see below)
4. **Create OIDC Client (if auto-configuration failed):**
- In the left sidebar, click **Clients**
- Click **Create client** button (top right)
- **Client ID:** Enter `fail2ban-ui` (must match `OIDC_CLIENT_ID` in container-compose.yml)
- **Client protocol:** Select `openid-connect`
- Click **Next**
- **Client authentication:** Toggle **ON** (this makes it a confidential client)
- **Authorization:** Leave OFF (unless you need it)
- **Authentication flow:** Leave default settings
- Click **Next**
- **Login settings:**
- **Root URL:** Leave empty
- **Home URL:** Leave empty
- **Valid redirect URIs:** Add `http://localhost:3080/auth/callback` (must match exactly)
- **Valid post logout redirect URIs:** Leave empty
- **Web origins:** Add `http://localhost:3080` (for CORS)
- Click **Save**
5. **Get Client Secret (REQUIRED):**
- After saving, you'll be on the client settings page
- Click the **Credentials** tab
- Copy the **Client secret** value (click "Copy" or manually copy)
- **Update `container-compose.yml`:**
```bash
# Edit the file
nano /opt/fail2ban-ui/development/oidc/container-compose.yml
# or
vi /opt/fail2ban-ui/development/oidc/container-compose.yml
```
- Find the line: `- OIDC_CLIENT_SECRET=change-me-secret`
- Replace `change-me-secret` with the actual secret you copied
- Save the file
6. **Restart fail2ban-ui:**
```bash
podman compose restart fail2ban-ui
```
7. **Create a Test User (Optional but recommended):**
- In Keycloak admin console, click **Users** in the left sidebar
- Click **Create new user** button (top right)
- **Username:** `testuser` (or any username)
- **Email:** `test@example.com` (optional)
- **Email verified:** Toggle ON (optional)
- Click **Create**
- Go to the **Credentials** tab
- Click **Set password**
- Enter a password
- **Temporary:** Toggle OFF (so user doesn't need to reset password on first login)
- Click **Save**
**Now you can access fail2ban-ui:**
- Open `http://localhost:3080` in your browser
- You should be redirected to Keycloak login
- Login with your test user credentials
- After successful authentication, you'll be redirected back to fail2ban-ui
**Optional: Create Custom Realm (for production):**
If you want to use a custom realm instead of `master`:
1. In Keycloak admin console, click the realm dropdown (top left, shows "master")
2. Click **Create Realm**
3. **Realm name:** Enter `myrealm` (or any name)
4. **Enabled:** Toggle ON
5. Click **Create**
6. **Update `container-compose.yml`:**
```yaml
- OIDC_ISSUER_URL=http://localhost:3000/realms/myrealm
```
7. **Restart fail2ban-ui:**
```bash
podman compose restart fail2ban-ui
```
8. **Create the OIDC client in the new realm** (follow steps 4-6 above)
### 4. Test Authentication
1. **Verify Fail2ban-UI Started Successfully:**
- Check logs: `podman logs DEV_fail2ban-ui-oidc`
- Look for: "OIDC authentication enabled" (should appear after Keycloak is ready)
- If you see retry messages, wait a bit longer for Keycloak to fully start
2. **Access Fail2ban UI:**
- Open `http://localhost:3080`
- You should be redirected to Keycloak login
3. **Login:**
- Use your Keycloak test user credentials
- After successful authentication, you'll be redirected back to Fail2ban UI
4. **Verify Session:**
- Check the header for your user information
- Verify logout functionality
## Switching Between Providers
### Using Pocket-ID Instead of Keycloak
1. **Stop current services:**
```bash
podman compose down
```
2. **Edit `container-compose.yml`:**
- Comment out the `keycloak` and `postgres` services
- Uncomment the `pocket-id` service
- Update fail2ban-ui environment variables:
```yaml
- OIDC_PROVIDER=pocketid
- OIDC_ISSUER_URL=http://localhost:3005
- OIDC_LOGOUT_URL=http://localhost:3005/logout
```
- Change fail2ban-ui back to bridge network (remove `network_mode: host`)
3. **Start services:**
```bash
podman compose up -d
```
4. **Configure Pocket-ID:**
- Access `http://localhost:3005`
- Create admin account
- Create OIDC client with redirect URI: `http://localhost:3080/auth/callback`
### Using Authentik Instead of Keycloak
1. **Stop current services:**
```bash
podman compose down
```
2. **Edit `container-compose.yml`:**
- Comment out the `keycloak` and `postgres` services
- Uncomment all `authentik-*` services
- Update fail2ban-ui environment variables:
```yaml
- OIDC_PROVIDER=authentik
- OIDC_ISSUER_URL=http://localhost:3007/application/o/fail2ban-ui/
```
- Change fail2ban-ui back to bridge network (remove `network_mode: host`)
3. **Start services:**
```bash
podman compose up -d
```
4. **Run migrations and setup:**
```bash
podman compose run --rm authentik-server migrate
```
5. **Access initial setup:**
- Open `http://localhost:3007/if/flow/initial-setup/`
- Create initial admin user
6. **Configure Authentik:**
- Create OIDC Provider
- Create Provider Application
- Configure client ID and secret
## Configuration Options
### OIDC Environment Variables
Edit `container-compose.yml` to customize OIDC settings:
```yaml
environment:
- OIDC_ENABLED=true # Enable/disable OIDC
- OIDC_PROVIDER=keycloak # Provider: keycloak, authentik, pocketid
- OIDC_ISSUER_URL=http://localhost:3000/realms/master # Must match provider's discovery document
- OIDC_CLIENT_ID=fail2ban-ui
- OIDC_CLIENT_SECRET=your-secret
- OIDC_REDIRECT_URL=http://localhost:3080/auth/callback # External URL for browser redirects
- OIDC_SCOPES=openid,profile,email # Comma-separated scopes
- OIDC_SESSION_MAX_AGE=7200 # Session timeout (seconds)
- OIDC_USERNAME_CLAIM=preferred_username
- OIDC_SKIP_VERIFY=true # Skip TLS verification (dev only)
```
**Note:** `OIDC_ISSUER_URL` must match the issuer returned by the provider's discovery document. For Keycloak with `KC_HOSTNAME=localhost`, use `http://localhost:3000/realms/master`.
### Provider-Specific Issuer URLs
- **Keycloak:** `http://localhost:3000/realms/master` (or your custom realm name)
- **Pocket-ID:** `http://localhost:3005`
- **Authentik:** `http://localhost:3007/application/o/<client-slug>/`
## Troubleshooting
### Keycloak Not Accessible / Healthcheck Failing
- Check if container is running: `podman ps | grep keycloak`
- Check container health status: `podman inspect DEV_keycloak | grep -A 10 Health`
- Check logs: `podman logs DEV_keycloak`
- Verify port mapping: `netstat -tlnp | grep 3000`
- Wait for database to be ready (healthcheck)
- Keycloak takes 30-60 seconds to fully start - wait for "Keycloak started" in logs
- **Healthcheck:** Keycloak v26+ uses port 9000 for health endpoints. Verify health endpoint:
```bash
# From host (if port 9000 is exposed):
curl http://localhost:9000/health/ready
# Or test from inside container:
podman exec DEV_keycloak bash -c 'exec 3<>/dev/tcp/localhost/9000 && echo -e "GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" >&3 && cat <&3'
```
- **If healthcheck keeps failing:** You can temporarily modify `container-compose.yml` to remove the `condition: service_healthy` from `depends_on` and let fail2ban-ui's retry logic handle the connection:
```yaml
depends_on:
- keycloak # Remove condition to start immediately
```
### Fail2ban-UI Fails to Start / OIDC Initialization Errors
- **Container stuck in "Created" status:**
- This means it's waiting for Keycloak to start (via `depends_on`)
- Check Keycloak status: `podman compose ps` - should show "healthy" when ready
- Check Keycloak logs: `podman logs DEV_keycloak`
- If Keycloak healthcheck is failing but Keycloak is running, you can:
1. Wait longer (Keycloak takes 30-60 seconds to start)
2. Manually start fail2ban-ui: `podman start DEV_fail2ban-ui-oidc` (it will retry connecting)
3. Temporarily remove `condition: service_healthy` from `depends_on` in `container-compose.yml`
- **"Connection refused" errors:** Keycloak isn't ready yet. The application will retry automatically (up to 10 times with exponential backoff). Wait for Keycloak to fully start (30-60 seconds).
- **"Issuer did not match" errors:**
- This happens when `OIDC_ISSUER_URL` doesn't match the issuer in Keycloak's discovery document
- Keycloak is configured with `KC_HOSTNAME=localhost`, so it returns `http://localhost:3000/realms/master` as issuer
- Ensure `OIDC_ISSUER_URL` is set to: `http://localhost:3000/realms/master` (or your custom realm)
- Verify the issuer: `curl http://localhost:3000/realms/master/.well-known/openid-configuration | grep issuer`
- **"Realm does not exist" errors:**
- The default configuration uses the `master` realm which always exists
- If you see this error, check that `OIDC_ISSUER_URL` in `container-compose.yml` matches an existing realm
- Verify the realm exists: `curl http://localhost:3000/realms/master/.well-known/openid-configuration`
- Or access Keycloak admin console and check available realms
- **Check fail2ban-ui logs:** `podman logs DEV_fail2ban-ui-oidc` for detailed error messages
- **Check Keycloak is ready:** Wait for log message "Keycloak ... started" and verify health endpoint responds
### Authentication Fails
1. **Check OIDC Configuration:**
- Verify `OIDC_ISSUER_URL` matches provider URL exactly
- Ensure client ID and secret match provider configuration
- Check redirect URI matches exactly
2. **Check Fail2ban-UI Logs:**
```bash
podman logs DEV_fail2ban-ui-oidc
```
3. **Verify Provider Client:**
- Ensure client is active in provider admin (accessible at `http://localhost:3000` for Keycloak)
- Check redirect URI is exactly: `http://localhost:3080/auth/callback`
### Session Issues
- Check `OIDC_SESSION_SECRET` is set (or auto-generated)
- Verify session cookie settings (should work with `OIDC_SKIP_VERIFY=true` in dev)
- Clear browser cookies and try again
### Keycloak Realm Not Found
- **Default Configuration:** The setup uses the `master` realm by default. This realm always exists in Keycloak.
- **Custom Realm:** If you created a custom realm (e.g., `myrealm`), ensure:
- The realm name in `OIDC_ISSUER_URL` matches exactly (case-sensitive)
- The realm is enabled in Keycloak admin console
- Update `OIDC_ISSUER_URL` in `container-compose.yml` to: `http://localhost:3000/realms/myrealm`
- **Check Realm:** Access `http://localhost:3000/realms/<realm-name>/.well-known/openid-configuration` to verify the realm exists
## Cleanup
To remove all containers and volumes:
```bash
podman compose down -v
# or
docker-compose down -v
```
This will remove:
- All containers
- Volume data (Keycloak database, Fail2ban-UI config, etc.)
**Note:** This deletes all development data. Make sure to backup anything important.
## Production Considerations
⚠️ **This setup is for development only!**
For production:
- Use HTTPS/TLS (not HTTP)
- Set `OIDC_SKIP_VERIFY=false`
- Use strong, randomly generated secrets
- Configure proper reverse proxy
- Use secure session secrets
- Change default admin passwords
- Enable proper logging and monitoring
- Use production-ready database configurations
- Configure proper backup strategies

View File

@@ -0,0 +1,293 @@
services:
# Keycloak: OIDC Provider (Recommended)
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: DEV_keycloak
environment:
# Admin credentials (change for production!)
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
# Database settings
KC_DB: postgres
KC_DB_URL_HOST: postgres
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak
# Hostname configuration for development
# Using full URL so issuer matches what fail2ban-ui expects
# When using KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true, hostname must be a full URL
# Set KEYCLOAK_PUBLIC_URL environment variable to your server's public URL
# If not set, defaults to http://localhost:3000
KC_HOSTNAME: ${KEYCLOAK_PUBLIC_URL:-http://localhost:3000}
KC_HOSTNAME_STRICT: "false"
KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true"
# Development mode (use "start" for production)
KC_HEALTH_ENABLED: "true"
KC_METRICS_ENABLED: "true"
command:
- start-dev
- --http-host=0.0.0.0
- --http-port=8080
ports:
- "0.0.0.0:3000:8080"
- "0.0.0.0:9000:9000" # Management port for health checks
depends_on:
postgres:
condition: service_healthy
volumes:
- ./keycloak-data:/opt/keycloak/data:z
healthcheck:
# Keycloak v26+ uses port 9000 for health endpoints (management port)
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && cat <&3 | grep -q '200 OK' || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 90s
restart: unless-stopped
networks:
- oidc-network
# PostgreSQL: Database for Keycloak
postgres:
image: postgres:15-alpine
container_name: DEV_keycloak-db
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: keycloak
volumes:
- ./keycloak-db:/var/lib/postgresql/data:z
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- oidc-network
# Pocket-ID: OIDC Provider (Alternative 1) - Not fully tested
# Uncomment to use Pocket-ID instead of Keycloak
# pocket-id:
# image: ghcr.io/pocket-id/pocket-id:latest
# container_name: DEV_pocket-id
# environment:
# - APP_URL=http://localhost:3005
# # Encryption key (choose one method):
# # Method 1: Direct key (simple but less secure)
# # Generate with: openssl rand -base64 32
# - ENCRYPTION_KEY=aF98qjUZuCJ_j_9Bh8hXx5U832ZpYQW
# # Method 2: File-based key (recommended)
# # Put the base64 key in a file and point to it here.
# # ENCRYPTION_KEY_FILE=/path/to/encryption_key
# - TRUST_PROXY=false
# - MAXMIND_LICENSE_KEY=
# - PUID=1000
# - PGID=1000
# - DB_PROVIDER=sqlite
# - DB_CONNECTION_STRING=sqlite:///pocket-id/var/db.sqlite
# - PORT=1411
# volumes:
# - ./pocket-id-data:/pocket-id/var:z
# ports:
# - "3005:1411"
# restart: unless-stopped
# networks:
# - oidc-network
# Authentik: OIDC Provider (Alternative 2) - Not fully tested
# Uncomment to use Authentik instead of Keycloak
# Note: Authentik requires additional setup (migrations, initial admin user)
# authentik-server:
# image: ghcr.io/goauthentik/server:latest
# container_name: DEV_authentik-server
# environment:
# - AUTHENTIK_SECRET_KEY=dev-secret-key-change-in-production-generate-with-openssl-rand-base64-60
# - AUTHENTIK_ERROR_REPORTING__ENABLED=false
# - AUTHENTIK_DISABLE_UPDATE_CHECK=true
# - AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
# - PG_PASS=authentik
# - AUTHENTIK_REDIS__HOST=redis
# - AUTHENTIK_POSTGRESQL__HOST=postgresql
# - AUTHENTIK_POSTGRESQL__USER=authentik
# - AUTHENTIK_POSTGRESQL__PASSWORD=authentik
# - AUTHENTIK_POSTGRESQL__NAME=authentik
# ports:
# - "3007:9000"
# - "3008:9443"
# depends_on:
# - authentik-postgresql
# - authentik-redis
# volumes:
# - ./authentik-media:/media:z
# restart: unless-stopped
# networks:
# - oidc-network
#
# authentik-worker:
# image: ghcr.io/goauthentik/server:latest
# container_name: DEV_authentik-worker
# environment:
# - AUTHENTIK_SECRET_KEY=dev-secret-key-change-in-production-generate-with-openssl-rand-base64-60
# - AUTHENTIK_ERROR_REPORTING__ENABLED=false
# - AUTHENTIK_DISABLE_UPDATE_CHECK=true
# - AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
# - PG_PASS=authentik
# - AUTHENTIK_REDIS__HOST=redis
# - AUTHENTIK_POSTGRESQL__HOST=postgresql
# - AUTHENTIK_POSTGRESQL__USER=authentik
# - AUTHENTIK_POSTGRESQL__PASSWORD=authentik
# - AUTHENTIK_POSTGRESQL__NAME=authentik
# command: ["authentik", "worker"]
# depends_on:
# - authentik-postgresql
# - authentik-redis
# volumes:
# - ./authentik-media:/media:z
# restart: unless-stopped
# networks:
# - oidc-network
#
# authentik-postgresql:
# image: postgres:15-alpine
# container_name: DEV_authentik-db
# environment:
# POSTGRES_PASSWORD: authentik
# POSTGRES_USER: authentik
# POSTGRES_DB: authentik
# volumes:
# - ./authentik-db:/var/lib/postgresql/data:z
# restart: unless-stopped
# networks:
# - oidc-network
#
# authentik-redis:
# image: redis:7-alpine
# container_name: DEV_authentik-redis
# restart: unless-stopped
# networks:
# - oidc-network
# Keycloak Client Auto-Configuration
# This container automatically creates the OIDC client in Keycloak
# Set PUBLIC_FRONTEND_URL environment variable to your server's public URL (e.g., http://192.168.1.100:3080)
# If not set, defaults to http://localhost:3080
keycloak-init:
image: curlimages/curl:latest
container_name: DEV_keycloak-init
depends_on:
keycloak:
condition: service_healthy
environment:
- KEYCLOAK_URL=http://keycloak:8080
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_PASSWORD=admin
- REALM=master
- CLIENT_ID=fail2ban-ui
# PUBLIC_FRONTEND_URL: Set this to your server's public URL (IP or hostname)
# If not set, defaults to http://localhost:3080
- PUBLIC_FRONTEND_URL=${PUBLIC_FRONTEND_URL:-http://localhost:3080}
- SECRET_FILE=/config/keycloak-client-secret
volumes:
- ./init-keycloak.sh:/init-keycloak.sh:ro
- ./config:/config:z
# Run as root to avoid permission issues
user: "0:0"
command: ["/bin/sh", "-c", "apk add --no-cache jq 2>/dev/null && /bin/sh /init-keycloak.sh 2>&1"]
networks:
- oidc-network
restart: "no" # Only run once
# Fail2ban-UI: Main application with OIDC authentication
fail2ban-ui:
image: localhost/fail2ban-ui:dev
container_name: DEV_fail2ban-ui-oidc
privileged: true
# Use host network to access Keycloak on localhost:3000
# This allows issuer URL to match Keycloak's discovery document
network_mode: host
# ports:
# - "3080:8080" # Not needed with host network
environment:
- PORT=3080
- BIND_ADDRESS=0.0.0.0
# OIDC Configuration for Keycloak (default)
# PUBLIC_FRONTEND_URL: Server's public URL for redirects (if not set, defaults to http://localhost:3080)
# KEYCLOAK_URL: Server's public URL for Keycloak (if not set, defaults to http://localhost:3000)
# Create a .env file with these variables for remote server access
- PUBLIC_FRONTEND_URL=${PUBLIC_FRONTEND_URL:-http://localhost:3080}
- KEYCLOAK_URL=${KEYCLOAK_URL:-http://localhost:3000}
# OIDC settings
- OIDC_ENABLED=true
- OIDC_PROVIDER=keycloak
- OIDC_ISSUER_URL=${KEYCLOAK_URL}/realms/master
- OIDC_CLIENT_ID=fail2ban-ui
- OIDC_CLIENT_SECRET=auto-configured
- OIDC_CLIENT_SECRET_FILE=/config/keycloak-client-secret
- OIDC_REDIRECT_URL=${PUBLIC_FRONTEND_URL}/auth/callback
- OIDC_SCOPES=openid,profile,email
- OIDC_SESSION_MAX_AGE=7200
- OIDC_USERNAME_CLAIM=preferred_username
- OIDC_SKIP_VERIFY=true
# Optional: Logout URL
#- OIDC_LOGOUT_URL=${KEYCLOAK_URL}/realms/master/protocol/openid-connect/logout
# Alternative OIDC Configuration Examples (uncomment and adjust as needed):
# For Pocket-ID:
# - OIDC_PROVIDER=pocketid
# - OIDC_ISSUER_URL=http://localhost:3005
# - OIDC_LOGOUT_URL=http://localhost:3005/logout
#
# For Authentik:
# - OIDC_PROVIDER=authentik
# - OIDC_ISSUER_URL=http://localhost:3007/application/o/fail2ban-ui/
volumes:
# Required for fail2ban-ui: Stores SQLite database, application settings, and SSH keys
- ./config:/config:z
# Required for fail2ban-ui: Used for testing logpath before enabling jails
- /var/log:/var/log:ro
# Required for compose-local fail2ban instance: We mount the same Fail2Ban config as the linuxserver-fail2ban container (under /config/fail2ban to fail2ban-ui can modify configs)
- ./fail2ban-config-local/fail2ban:/etc/fail2ban:z
# Required for compose-local fail2ban instance: Mount the same run directory that contains fail2ban.sock for communication between fail2ban-ui and the linuxserver-fail2ban container
- ./f2b-run-local:/var/run/fail2ban:z
restart: unless-stopped
# Wait for Keycloak to be healthy before starting
# Note: keycloak-init runs as a one-time job, so we only wait for Keycloak
# The application will retry connecting to Keycloak if needed
# Note: With host network mode, depends_on still works but network isolation is bypassed
depends_on:
- keycloak
# Fail2ban-Local: Local Fail2ban instance for testing local connector
fail2ban-local:
image: lscr.io/linuxserver/fail2ban:latest
container_name: DEV_fail2ban-local
cap_add:
# Required for fail2ban container: Allows to manage network interfaces and iptables from the container
- NET_ADMIN
# Required for fail2ban container: Allows to create raw sockets (needed for fail2ban.sock)
- NET_RAW
# Required for fail2ban container: Allows to run as root (needed to manage network interfaces and raw sockets)
- SYS_ADMIN
#privileged: true
network_mode: host # needed to add iptables rules to the host network
environment:
- TZ=Europe/Zurich
- VERBOSITY=-vv
volumes:
# To make sure linuxserver-fail2ban configs are persistent across container restarts (also needed by fail2ban-ui to modify configs)
- ./fail2ban-config-local:/config:z
# Directory that contains fail2ban.sock for communication between fail2ban-ui and fail2ban container
- ./f2b-run-local:/var/run/fail2ban:z
# Log sources for fail2ban container
- /var/log:/var/log:ro
- /var/log/httpd:/remotelogs/apache2:ro
restart: unless-stopped
networks:
oidc-network:
driver: bridge

166
development/oidc/init-keycloak.sh Executable file
View File

@@ -0,0 +1,166 @@
#!/bin/bash
# Automatic Keycloak OIDC client configuration script
# This script creates the fail2ban-ui OIDC client in Keycloak automatically
set -e
KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:3000}"
KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN:-admin}"
KEYCLOAK_PASSWORD="${KEYCLOAK_PASSWORD:-admin}"
REALM="${REALM:-master}"
CLIENT_ID="${CLIENT_ID:-fail2ban-ui}"
CLIENT_SECRET="${CLIENT_SECRET:-}"
# Use PUBLIC_FRONTEND_URL if provided, otherwise default to localhost
PUBLIC_FRONTEND_URL="${PUBLIC_FRONTEND_URL:-http://localhost:3080}"
REDIRECT_URI="${REDIRECT_URI:-${PUBLIC_FRONTEND_URL}/auth/callback}"
WEB_ORIGIN="${WEB_ORIGIN:-${PUBLIC_FRONTEND_URL}}"
# Extract host and port from KEYCLOAK_URL for health check
# KEYCLOAK_URL is the internal URL (e.g., http://keycloak:8080)
# Health endpoint is on management port 9000
KEYCLOAK_HOST=$(echo "${KEYCLOAK_URL}" | sed -E 's|https?://([^:/]+).*|\1|')
KEYCLOAK_HEALTH_URL="http://${KEYCLOAK_HOST}:9000/health/ready"
echo "Waiting for Keycloak to be ready..."
echo "Checking health endpoint: ${KEYCLOAK_HEALTH_URL}"
max_attempts=120 # Increased timeout since Keycloak can take a while
attempt=0
while [ $attempt -lt $max_attempts ]; do
# Check health endpoint on management port 9000
if curl -s -f "${KEYCLOAK_HEALTH_URL}" > /dev/null 2>&1; then
echo "Keycloak is ready!"
break
fi
# Also try the main port as fallback
if curl -s -f "${KEYCLOAK_URL}/health/ready" > /dev/null 2>&1; then
echo "Keycloak is ready (via main port)!"
break
fi
attempt=$((attempt + 1))
if [ $((attempt % 10)) -eq 0 ]; then
echo "Attempt $attempt/$max_attempts: Keycloak not ready yet, waiting..."
fi
sleep 2
done
if [ $attempt -eq $max_attempts ]; then
echo "ERROR: Keycloak did not become ready in time"
exit 1
fi
echo "Waiting for Keycloak admin API to be available..."
sleep 5
echo "Getting admin access token..."
ADMIN_TOKEN=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=${KEYCLOAK_ADMIN}" \
-d "password=${KEYCLOAK_PASSWORD}" \
-d "grant_type=password" \
-d "client_id=admin-cli" | jq -r '.access_token')
if [ -z "$ADMIN_TOKEN" ] || [ "$ADMIN_TOKEN" = "null" ]; then
echo "ERROR: Failed to get admin token"
exit 1
fi
echo "Checking if client already exists..."
EXISTING_CLIENT=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id // empty')
if [ -n "$EXISTING_CLIENT" ]; then
echo "Client '${CLIENT_ID}' already exists, updating..."
CLIENT_UUID="$EXISTING_CLIENT"
# Update client configuration
curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"clientId\": \"${CLIENT_ID}\",
\"enabled\": true,
\"clientAuthenticatorType\": \"client-secret\",
\"redirectUris\": [\"${REDIRECT_URI}\"],
\"webOrigins\": [\"${WEB_ORIGIN}\"],
\"protocol\": \"openid-connect\",
\"publicClient\": false,
\"standardFlowEnabled\": true,
\"directAccessGrantsEnabled\": true
}" > /dev/null
echo "Client updated successfully"
else
echo "Creating new client '${CLIENT_ID}'..."
# Create client
CLIENT_RESPONSE=$(curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${REALM}/clients" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"clientId\": \"${CLIENT_ID}\",
\"enabled\": true,
\"clientAuthenticatorType\": \"client-secret\",
\"redirectUris\": [\"${REDIRECT_URI}\"],
\"webOrigins\": [\"${WEB_ORIGIN}\"],
\"protocol\": \"openid-connect\",
\"publicClient\": false,
\"standardFlowEnabled\": true,
\"directAccessGrantsEnabled\": true
}")
if [ $? -ne 0 ]; then
echo "ERROR: Failed to create client"
exit 1
fi
# Get the client UUID
CLIENT_UUID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
echo "Client created successfully with UUID: ${CLIENT_UUID}"
fi
# Get or regenerate client secret
echo "Getting client secret..."
CLIENT_SECRET=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.value')
if [ -z "$CLIENT_SECRET" ] || [ "$CLIENT_SECRET" = "null" ]; then
echo "Regenerating client secret..."
CLIENT_SECRET=$(curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.value')
fi
if [ -z "$CLIENT_SECRET" ] || [ "$CLIENT_SECRET" = "null" ]; then
echo "ERROR: Failed to get client secret"
exit 1
fi
echo ""
echo "=========================================="
echo "OIDC Client Configuration Complete!"
echo "=========================================="
echo "Client ID: ${CLIENT_ID}"
echo "Client Secret: ${CLIENT_SECRET}"
echo "Realm: ${REALM}"
echo "Redirect URI: ${REDIRECT_URI}"
echo "=========================================="
# Save secret to shared volume for fail2ban-ui to read
SECRET_FILE="${SECRET_FILE:-/config/keycloak-client-secret}"
# Create directory if it doesn't exist
mkdir -p "$(dirname "${SECRET_FILE}")" 2>/dev/null || true
# Write secret file (running as root, so should have permissions)
if echo "${CLIENT_SECRET}" > "${SECRET_FILE}" 2>/dev/null; then
chmod 644 "${SECRET_FILE}" 2>/dev/null || true
echo "Client secret saved to ${SECRET_FILE} for fail2ban-ui"
else
echo "ERROR: Failed to write client secret to ${SECRET_FILE}"
echo "Client secret: ${CLIENT_SECRET}"
echo "Please save this secret manually to ${SECRET_FILE}"
exit 1
fi