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

50
development/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Development Environment
This directory contains Docker Compose configurations for local development and testing of Fail2ban UI.
## Available Development Setups
### 1. OIDC Authentication Testing (`oidc/`)
Complete OIDC authentication setup with Keycloak and Pocket-ID for testing authentication flows.
**See:** [oidc/README.md](./oidc/README.md)
### 2. SSH and Local Fail2ban Testing (`ssh_and_local/`)
Setup for testing Fail2ban UI with:
- Local Fail2ban instance (container)
- Remote Fail2ban instance via SSH (container)
**See:** [ssh_and_local/README.md](./ssh_and_local/README.md)
## Quick Start
1. **Build the fail2ban-ui development image:**
```bash
podman build -t localhost/fail2ban-ui:dev .
# or
docker build -t localhost/fail2ban-ui:dev .
```
2. **Choose a development setup:**
- For OIDC testing: `cd oidc/`
- For SSH/Local testing: `cd ssh_and_local/`
3. **Start the services:**
```bash
podman compose up -d
# or
docker-compose up -d
```
4. **Access the services:**
- Fail2ban UI: `http://localhost:3080` (or configured port)
- OIDC Provider (Pocket-ID): `http://localhost:3000` (if using OIDC setup)
## Notes
- All development containers use the `DEV_` prefix for easy identification
- Data volumes are stored in subdirectories (e.g., `./config`, `./pocket-id-data`)
- These setups are for **development only** - not for production use
- Some containers require `privileged: true` or specific capabilities for full functionality

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

5
development/ssh_and_local/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
fail2ban-config-local
fail2ban-config-ssh
f2b-run-local
f2b-run-ssh
ssh-keys

View File

@@ -0,0 +1,250 @@
# SSH and Local Fail2ban Development Setup
This setup provides a complete testing environment for Fail2ban UI with:
- **Local Fail2ban instance** (container) - for testing local connector
- **Remote Fail2ban instance via SSH** (container) - for testing SSH connector
## Services
### 1. Fail2ban-Local
- **Container:** `DEV_fail2ban-local`
- **Purpose:** Local Fail2ban instance for testing local connector
- **Network:** `host` mode (for iptables access)
- **Config:** `./fail2ban-config-local/`
- **Socket:** `./f2b-run-local/`
### 2. Fail2ban-SSH
- **Container:** `DEV_fail2ban-ssh`
- **Purpose:** Remote Fail2ban instance accessible via SSH
- **Network:** Bridge mode
- **SSH Port:** `2222` (mapped from container port 22)
- **SSH User:** `testuser`
- **SSH Key:** Auto-generated in `./ssh-keys/`
- **Config:** `./fail2ban-config-ssh/`
### 3. Fail2ban-UI
- **Container:** `DEV_fail2ban-ui`
- **Port:** `3080`
- **URL:** `http://172.16.10.18:3080` (or configured BIND_ADDRESS)
- **Purpose:** Main application for managing both Fail2ban instances
## 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/ssh_and_local
podman compose up -d
# or
docker-compose up -d
```
### 3. Wait for SSH Container Setup
The SSH container takes a moment to:
- Generate SSH keys (if not present)
- Configure SSH server
- Set up user permissions
- Configure sudoers
Check logs to verify:
```bash
podman logs DEV_fail2ban-ssh
```
Look for:
```
========================================
SSH Test Container Ready
========================================
```
### 4. Configure Fail2ban-UI
1. **Access Fail2ban UI:**
- Open `http://172.16.10.18:3080` (or your configured BIND_ADDRESS:PORT)
- Or if using host network: `http://localhost:3080`
2. **Add Local Server:**
- Go to "Manage Servers"
- The local Fail2ban instance should be auto-detected
- Enable the local connector
3. **Add SSH Server:**
- Go to "Manage Servers"
- Click "Add Server"
- Configure:
- **Name:** `SSH Test Server`
- **Type:** `SSH`
- **Host:** `127.0.0.1`
- **Port:** `2222`
- **SSH User:** `testuser`
- **SSH Key:** Select `/config/.ssh/id_rsa` (auto-mounted)
- Enable the connector
- Click "Test Connection" to verify
## SSH Connection Details
- **Host:** `127.0.0.1`
- **Port:** `2222`
- **User:** `testuser`
- **Key Path (in container):** `/config/.ssh/id_rsa`
- **Key Path (host):** `./ssh-keys/id_rsa`
### Test SSH Connection Manually
```bash
# From host
podman exec -it DEV_fail2ban-ui ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o BatchMode=yes \
-i /config/.ssh/id_rsa \
-p 2222 \
testuser@127.0.0.1
```
## Configuration
### Fail2ban-UI Environment Variables
Edit `container-compose.yml` to customize:
```yaml
environment:
- PORT=3080
- BIND_ADDRESS=172.16.10.18 # Change to your IP or 0.0.0.0
# OIDC settings (if testing OIDC)
- OIDC_ENABLED=false # Set to true to enable OIDC
```
### SSH Container Customization
The SSH container is pre-configured with:
- Passwordless SSH key authentication
- Sudo permissions for fail2ban-client commands
- Proper file permissions (FACLs) for Fail2ban config directories
- Root access for network management
To modify SSH configuration, edit the `command` section in `container-compose.yml`.
## Volume Structure
```
./config/ # Fail2ban-UI configuration and database
./ssh-keys/ # SSH key pair (shared between containers)
./fail2ban-config-local/ # Local Fail2ban configuration
./f2b-run-local/ # Local Fail2ban socket directory
./fail2ban-config-ssh/ # SSH Fail2ban configuration
```
## Testing Scenarios
### 1. Local Connector Test
1. Enable local connector in Fail2ban-UI
2. Create a test jail
3. Verify jail appears in dashboard
4. Test ban/unban operations
5. Verify configuration changes persist
### 2. SSH Connector Test
1. Add SSH server in Fail2ban-UI
2. Test connection (should succeed)
3. Create a test jail on remote server
4. Verify jail appears in dashboard
5. Test ban/unban operations
6. Verify configuration changes sync to remote
### 3. Multi-Server Management
1. Enable both local and SSH connectors
2. Verify both servers appear in server selector
3. Switch between servers
4. Verify each server's jails are isolated
5. Test operations on each server independently
## Troubleshooting
### SSH Connection Fails
1. **Check SSH container is ready:**
```bash
podman logs DEV_fail2ban-ssh | tail -20
```
2. **Verify SSH keys exist:**
```bash
ls -la ./ssh-keys/
```
3. **Test SSH manually:**
```bash
podman exec -it DEV_fail2ban-ui ssh -v -i /config/.ssh/id_rsa -p 2222 testuser@127.0.0.1
```
4. **Check SSH container port:**
```bash
netstat -tlnp | grep 2222
```
### Local Connector Issues
1. **Check socket exists:**
```bash
ls -la ./f2b-run-local/
```
2. **Verify permissions:**
```bash
podman exec -it DEV_fail2ban-local ls -la /var/run/fail2ban/
```
3. **Check Fail2ban status:**
```bash
podman exec -it DEV_fail2ban-local fail2ban-client status
```
### Permission Errors
- Ensure volumes have correct SELinux labels (`:z` or `:Z`)
- Check container is running with required capabilities
- Verify file permissions in mounted directories
## Cleanup
To remove all containers and volumes:
```bash
podman compose down -v
# or
docker-compose down -v
```
This will remove:
- All containers
- Volume data (configs, SSH keys, databases)
**Note:** This deletes all development data. SSH keys will be regenerated on next start.
## Production Considerations
⚠️ **This setup is for development only!**
For production:
- Use proper SSH key management (not this auto-generated key)
- Use dedicated service accounts (not testuser)
- Use HTTPS/TLS (not HTTP) / Configure proper reverse proxy
- Use strong, randomly generated secrets
- Use secure session secrets
- Enable proper logging and monitoring

View File

@@ -0,0 +1,137 @@
services:
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
fail2ban-ui:
#image: registry.swissmakers.ch/infra/fail2ban-ui:latest
image: localhost/fail2ban-ui:dev
container_name: DEV_fail2ban-ui
privileged: true
network_mode: host
environment:
- PORT=3080
- BIND_ADDRESS=172.16.10.18
volumes:
# Required for fail2ban-ui: Stores SQLite database, application settings, and SSH keys of the fail2ban-ui container
- ./config:/config:Z
# Mount persistent SSH keys directory
- ./ssh-keys:/config/.ssh:z
# Required for fail2ban-ui: Used for testing, that logpath is working, before enabeling a jail. Without this read only access the fail2ban-ui will not be able to enable jails (logpath-test would fail)
- /var/log:/var/log:ro
- /var/log/httpd:/remotelogs/apache2:ro # this mounts the apache2 logs of a RPM based system (e.g. Rocky Linux) to the default location set by linuxserver-fail2ban. (on debian based systems this is /var/log/apache2 and currently hardcoded in the linuxserver-fail2ban container)
# 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
fail2ban-ssh:
image: lscr.io/linuxserver/fail2ban:latest
container_name: DEV_fail2ban-ssh
cap_add:
- NET_ADMIN
- NET_RAW
- SYS_ADMIN
network_mode: bridge
ports:
- "2222:22" # SSH port mapping
environment:
- TZ=Europe/Zurich
- VERBOSITY=-vv
- PUID=0 # Run as root for SSH setup
- PGID=0
volumes:
- ./fail2ban-config-ssh:/config:z
- /var/log:/var/log:ro
- /var/log/httpd:/remotelogs/apache2:ro
# Mount persistent SSH keys - shared between containers
- ./ssh-keys:/mnt/ssh-keys:z
# We use entrypoint override to run SSH setup before the container's init
entrypoint: /bin/sh
command: >
-c "
apk update && apk add --no-cache openssh-server openssh-keygen sudo acl;
ssh-keygen -A;
useradd -m -s /bin/bash testuser 2>/dev/null || true;
passwd -d testuser 2>/dev/null || usermod -U testuser 2>/dev/null || true;
chage -E -1 testuser 2>/dev/null || true;
mkdir -p /home/testuser/.ssh;
if [ ! -f /mnt/ssh-keys/id_rsa ]; then
echo 'Generating new SSH key pair...';
ssh-keygen -t rsa -b 4096 -m PEM -f /mnt/ssh-keys/id_rsa -N '';
chmod 600 /mnt/ssh-keys/id_rsa;
chmod 644 /mnt/ssh-keys/id_rsa.pub;
echo 'SSH key pair generated in persistent volume';
else
echo 'Using existing SSH key pair from persistent volume';
fi;
cp /mnt/ssh-keys/id_rsa /home/testuser/.ssh/id_rsa;
cp /mnt/ssh-keys/id_rsa.pub /home/testuser/.ssh/id_rsa.pub;
cat /mnt/ssh-keys/id_rsa.pub > /home/testuser/.ssh/authorized_keys;
chmod 700 /home/testuser/.ssh;
chmod 600 /home/testuser/.ssh/id_rsa;
chmod 644 /home/testuser/.ssh/id_rsa.pub;
chmod 600 /home/testuser/.ssh/authorized_keys;
chown -R testuser:testuser /home/testuser/.ssh;
echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config;
echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config;
echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config;
echo 'LogLevel VERBOSE' >> /etc/ssh/sshd_config;
echo 'AuthorizedKeysFile .ssh/authorized_keys' >> /etc/ssh/sshd_config;
echo 'SyslogFacility AUTH' >> /etc/ssh/sshd_config;
mkdir -p /etc/sudoers.d;
echo 'testuser ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client *' > /etc/sudoers.d/fail2ban-ui;
echo 'testuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart fail2ban' >> /etc/sudoers.d/fail2ban-ui;
echo 'testuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload fail2ban' >> /etc/sudoers.d/fail2ban-ui;
chmod 440 /etc/sudoers.d/fail2ban-ui;
mkdir -p /etc/fail2ban/jail.d /etc/fail2ban/filter.d /etc/fail2ban/action.d;
setfacl -Rm u:testuser:rwX /etc/fail2ban 2>/dev/null || true;
setfacl -dRm u:testuser:rwX /etc/fail2ban 2>/dev/null || true;
[ -d /etc/fail2ban/action.d ] && setfacl -m u:testuser:rwX /etc/fail2ban/action.d 2>/dev/null || true;
[ -d /etc/fail2ban/filter.d ] && setfacl -m u:testuser:rwX /etc/fail2ban/filter.d 2>/dev/null || true;
[ -d /etc/fail2ban/jail.d ] && setfacl -m u:testuser:rwX /etc/fail2ban/jail.d 2>/dev/null || true;
[ -d /config/fail2ban/action.d ] && setfacl -m u:testuser:rwX /config/fail2ban/action.d 2>/dev/null || true;
[ -d /config/fail2ban/filter.d ] && setfacl -m u:testuser:rwX /config/fail2ban/filter.d 2>/dev/null || true;
[ -d /config/fail2ban/jail.d ] && setfacl -m u:testuser:rwX /config/fail2ban/jail.d 2>/dev/null || true;
echo '========================================';
echo 'SSH Test Container Ready';
echo '========================================';
echo 'Host: 127.0.0.1';
echo 'Port: 2222';
echo 'User: testuser';
echo 'Key: /config/.ssh/id_rsa (in fail2ban-ui container)';
echo 'Test command: podman exec -it DEV_fail2ban-ui ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -i /config/.ssh/id_rsa -p 2222 testuser@127.0.0.1';
echo '========================================';
/usr/sbin/sshd -D -e &
exec /init
"
restart: unless-stopped