From 62ab6dede32efbe6008c5734e135b380c67ea478 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Mon, 19 Jan 2026 18:21:24 +0100 Subject: [PATCH] Adding development-containers that we used to develop fail2ban-ui, can also be used for easy and fast stack-testing --- development/README.md | 50 ++ development/oidc/.env.example | 25 + development/oidc/.gitignore | 12 + development/oidc/README.md | 483 ++++++++++++++++++ development/oidc/container-compose.yml | 293 +++++++++++ development/oidc/init-keycloak.sh | 166 ++++++ development/ssh_and_local/.gitignore | 5 + development/ssh_and_local/README.md | 250 +++++++++ .../ssh_and_local/container-compose.yml | 137 +++++ 9 files changed, 1421 insertions(+) create mode 100644 development/README.md create mode 100644 development/oidc/.env.example create mode 100644 development/oidc/.gitignore create mode 100644 development/oidc/README.md create mode 100644 development/oidc/container-compose.yml create mode 100755 development/oidc/init-keycloak.sh create mode 100644 development/ssh_and_local/.gitignore create mode 100644 development/ssh_and_local/README.md create mode 100644 development/ssh_and_local/container-compose.yml diff --git a/development/README.md b/development/README.md new file mode 100644 index 0000000..61f6c5f --- /dev/null +++ b/development/README.md @@ -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 diff --git a/development/oidc/.env.example b/development/oidc/.env.example new file mode 100644 index 0000000..d195bc4 --- /dev/null +++ b/development/oidc/.env.example @@ -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 diff --git a/development/oidc/.gitignore b/development/oidc/.gitignore new file mode 100644 index 0000000..834dbd0 --- /dev/null +++ b/development/oidc/.gitignore @@ -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 diff --git a/development/oidc/README.md b/development/oidc/README.md new file mode 100644 index 0000000..4cc2adb --- /dev/null +++ b/development/oidc/README.md @@ -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//` + +## 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//.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 diff --git a/development/oidc/container-compose.yml b/development/oidc/container-compose.yml new file mode 100644 index 0000000..c257e85 --- /dev/null +++ b/development/oidc/container-compose.yml @@ -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 diff --git a/development/oidc/init-keycloak.sh b/development/oidc/init-keycloak.sh new file mode 100755 index 0000000..c95d7cb --- /dev/null +++ b/development/oidc/init-keycloak.sh @@ -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 diff --git a/development/ssh_and_local/.gitignore b/development/ssh_and_local/.gitignore new file mode 100644 index 0000000..9183331 --- /dev/null +++ b/development/ssh_and_local/.gitignore @@ -0,0 +1,5 @@ +fail2ban-config-local +fail2ban-config-ssh +f2b-run-local +f2b-run-ssh +ssh-keys \ No newline at end of file diff --git a/development/ssh_and_local/README.md b/development/ssh_and_local/README.md new file mode 100644 index 0000000..2762e0d --- /dev/null +++ b/development/ssh_and_local/README.md @@ -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 diff --git a/development/ssh_and_local/container-compose.yml b/development/ssh_and_local/container-compose.yml new file mode 100644 index 0000000..f1b3439 --- /dev/null +++ b/development/ssh_and_local/container-compose.yml @@ -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