diff options
| author | Arseney300 <Arseney300@gmail.com> | 2026-03-04 09:16:29 +0700 |
|---|---|---|
| committer | Arseney300 <Arseney300@gmail.com> | 2026-03-04 09:16:29 +0700 |
| commit | 24ce3335f9aef46f5d9cff412e09b7d4c20bba44 (patch) | |
| tree | 3ecbff70b068fd4ec8017e93eed3d7bd9b4208dd | |
| parent | 7081a34337d3cb758c6ffa738bb7e6b655076aa8 (diff) | |
Add Authelia SSO, Jenkins OIDC, Gerrit HTTP auth, and related config
- authelia/: Full Authelia stack (docker-compose, config, setup guide, nginx snippet)
- Forward-auth for Netdata, Cockpit, Nexus, registry
- OIDC provider for Jenkins (oic-auth plugin, authorization_policy: one_factor)
- HTTP header auth for Gerrit (X-Forwarded-User on /login/ only)
- authelia_network: 172.26.0.0/16
- nginx/nginx.conf:
- auth.swave.lol server block
- /_authelia-auth internal subrequest locations (resolver 127.0.0.11)
- auth_request on Gerrit /login/, Jenkins, Nexus, registry
- OIDC finishLogin bypass for Jenkins
- swave.lol/gerrit and swave.lol/jenkins redirect to subdomains
- proxy_redirect for Gerrit container hostname rewrites
- nginx/docker-compose.yaml: joined authelia_network
- nginx/run_certbot.sh: added auth.swave.lol
- jenkins/docker-compose.yaml: removed --prefix=/jenkins (Jenkins now at /)
- cockpit/nginx-authelia.conf, netdata/nginx-netdata.conf: added auth_request
- git-server/gerrit.config.example: Gerrit config template (real config gitignored)
- .gitignore: added users_database.yml, gerrit.config
- CLAUDE.md: updated Jenkins, Gerrit, Authelia entries; added key files
- doc/setup-guide.md: Authelia section, updated startup order and service table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | CLAUDE.md | 46 | ||||
| -rw-r--r-- | authelia/config/configuration.yml | 134 | ||||
| -rw-r--r-- | authelia/config/users_database.yml.example | 28 | ||||
| -rw-r--r-- | authelia/docker-compose.yaml | 39 | ||||
| -rw-r--r-- | authelia/nginx-authelia.conf | 44 | ||||
| -rw-r--r-- | authelia/setup.md | 306 | ||||
| -rw-r--r-- | cockpit/nginx-cockpit.conf | 2 | ||||
| -rw-r--r-- | doc/setup-guide.md | 98 | ||||
| -rw-r--r-- | git-server/gerrit.config.example | 35 | ||||
| -rw-r--r-- | jenkins/docker-compose.yaml | 2 | ||||
| -rw-r--r-- | netdata/nginx-netdata.conf | 2 | ||||
| -rw-r--r-- | nginx/nginx.conf | 147 |
13 files changed, 825 insertions, 59 deletions
@@ -2,3 +2,4 @@ doc/servers.drawio doc/Servers.drawio authelia/config/users_database.yml +git-server/gerrit.config @@ -5,7 +5,7 @@ Bastion is a self-hosted server system on Debian that runs multiple services in ## Domain - Primary domain: `swave.lol` -- Subdomains: `blog.swave.lol`, `ghost.swave.lol`, `jenkins.swave.lol`, `cgit.swave.lol`, `gerrit.swave.lol`, `nexus.swave.lol`, `registry.swave.lol` +- Subdomains: `blog.swave.lol`, `ghost.swave.lol`, `jenkins.swave.lol`, `cgit.swave.lol`, `gerrit.swave.lol`, `nexus.swave.lol`, `registry.swave.lol`, `auth.swave.lol` ## Services / Stacks @@ -48,7 +48,11 @@ Bastion is a self-hosted server system on Debian that runs multiple services in - Web UI on port 8080 internally, SSH on 29418 - No host ports (accessed only via main nginx reverse proxy) - Volumes: `/var/gerrit` (etc, git, db, index, cache), shared `/var/git/repos` -- Accessible at `https://gerrit.swave.lol` and `https://swave.lol/gerrit` +- Accessible at `https://gerrit.swave.lol` (swave.lol/gerrit redirects to subdomain) +- Auth: HTTP header auth (`X-Forwarded-User` from Authelia), anonymous read allowed +- nginx auth_request only on `/login/` path — Gerrit manages its own session cookie after login +- `loginUrl = https://gerrit.swave.lol/login/` in gerrit.config triggers Authelia SSO via nginx +- Config template: `git-server/gerrit.config.example` (real config gitignored, lives at `/var/gerrit/etc/gerrit.config`) ### Jenkins - Dir: `jenkins/` @@ -56,10 +60,14 @@ Bastion is a self-hosted server system on Debian that runs multiple services in - Network: `jenkins_network` (172.23.0.0/16, static IP 172.23.0.2) - Uses Docker-out-of-Docker (DooD): mounts `/var/run/docker.sock` to spawn sibling build containers - Dockerfile extends `jenkins/jenkins:lts` with Docker CLI and `docker-workflow` plugin -- `--prefix=/jenkins` set via JENKINS_OPTS for path-based access -- Accessible at `https://jenkins.swave.lol` and `https://swave.lol/jenkins` +- No path prefix (JENKINS_OPTS is empty); accessible at `https://jenkins.swave.lol/` +- `swave.lol/jenkins` redirects to `jenkins.swave.lol/` (strips the prefix) - Ports: 8080 (web UI), 50000 (agent communication via JNLP, used by remote build agents) - Volume: `/var/jenkins_home` +- Auth: Authelia OIDC (`oic-auth` plugin) — `authorization_policy: one_factor` required in Authelia client config +- OIDC redirect URI: `https://jenkins.swave.lol/securityRealm/finishLogin` (must bypass nginx auth_request) +- Username field: `preferred_username` (maps Authelia username to Jenkins user) +- Jenkins URL setting must be `https://jenkins.swave.lol/` - Pipeline durability: set to "Performance-optimized" to prevent flow execution corruption - Startup script: `jenkins/init.groovy.d/clear-stuck-builds.groovy` auto-cleans stuck builds on restart - Currently runs all builds on the controller via DooD; supports adding permanent SSH agents for scaling (see setup guide 6.6) @@ -76,10 +84,25 @@ Bastion is a self-hosted server system on Debian that runs multiple services in - Docker registry accessible at `https://registry.swave.lol` - Docker registry requires manual setup after first login: create a "docker (hosted)" repository with HTTP connector on port 5000 +### Authelia (SSO / authentication) +- Dir: `authelia/` +- Compose: `authelia/docker-compose.yaml` +- Image: `authelia/authelia:latest` +- Network: `authelia_network` (172.26.0.0/16, static IP 172.26.0.2) +- Services: authelia (port 9091), authelia-redis (session store) +- Volumes: `/var/authelia/config`, `/var/authelia/data`, `/var/authelia/secrets` (ro), `/var/authelia/redis` +- Secrets loaded from files in `/var/authelia/secrets/` (JWT, session, storage encryption, OIDC HMAC, OIDC RSA key) +- Accessible at `https://auth.swave.lol` +- Forward-auth for: Gerrit, Jenkins, Nexus, Netdata, Cockpit +- OIDC provider for Jenkins (full SSO) +- HTTP header injection for Gerrit (`X-Forwarded-User`) +- Ghost and Cgit remain public (no auth) +- Setup guide: `authelia/setup.md` + ### Nginx (reverse proxy) - Dir: `nginx/` - Compose: `nginx/docker-compose.yaml` -- Network: joins `git-network` (IP 172.22.0.254), `ghost_network`, `jenkins_network`, `nexus_network` +- Network: joins `git-network` (IP 172.22.0.254), `ghost_network`, `jenkins_network`, `nexus_network`, `authelia_network` - Ports: 80 (HTTP, redirects to HTTPS), 443 (HTTPS) - Config mounted from `/var/nginx/conf/` on host - SSL: Let's Encrypt certs via `run_certbot.sh`, dhparam at `/var/dh_param/` @@ -94,6 +117,7 @@ Each stack has its own Docker network. Nginx joins all of them to reverse proxy: - `ghost_network` — ghost + ghost-db + nginx - `jenkins_network` — jenkins + git-server + gerrit + nginx - `nexus_network` — nexus + nginx +- `authelia_network` — authelia + authelia-redis + nginx ## Startup Order 1. Portainer (standalone) @@ -101,8 +125,9 @@ Each stack has its own Docker network. Nginx joins all of them to reverse proxy: 3. Ghost (creates ghost_network) 4. Jenkins (creates jenkins_network) 5. Nexus (creates nexus_network) -6. Gerrit (joins git-network + jenkins_network, both must exist) -7. Nginx (joins all networks, must be last) +6. Authelia (creates authelia_network) +7. Gerrit (joins git-network + jenkins_network, both must exist) +8. Nginx (joins all networks, must be last) ## Key Files - `doc/setup-guide.md` — full build instructions from fresh Debian @@ -113,10 +138,15 @@ Each stack has its own Docker network. Nginx joins all of them to reverse proxy: - `jenkins/init.groovy.d/clear-stuck-builds.groovy` — startup script to clean stuck pipeline executions - `nexus/docker-compose.yaml` — Nexus Repository Manager + Docker registry - `doc/Jenkinsfile.nexus-example` — example pipelines for uploading artifacts and Docker images to Nexus +- `authelia/setup.md` — Authelia SSO setup guide (secrets, OIDC, Gerrit HTTP auth, Jenkins OIDC) +- `authelia/nginx-authelia.conf` — nginx server block snippet for auth.swave.lol +- `authelia/config/configuration.yml` — Authelia config (OIDC_RSA_KEY_PLACEHOLDER replaced by awk on server) +- `authelia/config/users_database.yml.example` — user database template (real file gitignored) +- `git-server/gerrit.config.example` — Gerrit config template (real file gitignored, lives at `/var/gerrit/etc/gerrit.config`) - `ghost/.env.example` — template for Ghost env vars - `doc/git-performance.md` — git server performance tuning guide - `nginx/error-pages/unknown-host.html` — 404 page for unknown subdomains -- `.gitignore` — excludes `.env` files (secrets) +- `.gitignore` — excludes `.env` files and other secrets ## Conventions - Each service lives in its own directory with its own compose file diff --git a/authelia/config/configuration.yml b/authelia/config/configuration.yml new file mode 100644 index 0000000..ace649e --- /dev/null +++ b/authelia/config/configuration.yml @@ -0,0 +1,134 @@ +--- +# Authelia configuration for swave.lol + +server: + address: 'tcp://0.0.0.0:9091' + +log: + level: info + +# Session cookie covers all *.swave.lol subdomains — one login, all services +session: + name: authelia_session + expiration: 3600 # 1 hour + inactivity: 300 # 5 minutes idle timeout + remember_me: 1M + redis: + host: authelia-redis + port: 6379 + cookies: + - domain: swave.lol + authelia_url: https://auth.swave.lol + default_redirection_url: https://swave.lol + +# SQLite storage — simple, no extra container needed +storage: + local: + path: /data/db.sqlite3 + +# Filesystem notifier — upgrade to SMTP later (see setup.md) +notifier: + filesystem: + filename: /data/notification.txt + +# User database (argon2id hashed passwords) +authentication_backend: + file: + path: /config/users_database.yml + password: + algorithm: argon2id + iterations: 3 + memory: 65536 + parallelism: 4 + key_length: 32 + salt_length: 16 + +# Default deny — only explicitly bypassed or permitted resources are accessible +access_control: + default_policy: deny + rules: + # Auth portal itself — must be accessible without auth + - domain: auth.swave.lol + policy: bypass + + # Public services — no auth required + - domain: + - swave.lol + - blog.swave.lol + - ghost.swave.lol + policy: bypass + + # Cgit — public read-only git browser + - domain: cgit.swave.lol + policy: bypass + - domain: swave.lol + resources: + - '^/cgit(/.*)?$' + policy: bypass + + # Netdata — requires login + - domain: swave.lol + resources: + - '^/netdata(/.*)?$' + policy: one_factor + + # Cockpit — requires login + - domain: swave.lol + resources: + - '^/cockpit(/.*)?$' + policy: one_factor + + # Gerrit login — auth_request on /login/ injects X-Forwarded-User once, Gerrit + # maintains its own session from there; anonymous users never hit /login/ directly + - domain: gerrit.swave.lol + resources: + - '^/login(/.*)?$' + policy: one_factor + + # Gerrit general — public read, no auth_request needed on other paths + - domain: gerrit.swave.lol + policy: bypass + + + # Jenkins — requires login (OIDC) + - domain: jenkins.swave.lol + policy: one_factor + + # Nexus — requires login + - domain: nexus.swave.lol + policy: one_factor + - domain: swave.lol + resources: + - '^/nexus(/.*)?$' + policy: one_factor + + # Docker registry — requires login + - domain: registry.swave.lol + policy: one_factor + +# OIDC provider — used by Jenkins for true SSO +identity_providers: + oidc: + # HMAC secret loaded via AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE env var + # RSA key loaded from file via template below + jwks: + - key: | + OIDC_RSA_KEY_PLACEHOLDER + clients: + - client_id: jenkins + client_name: Jenkins CI + # Hash generated with: authelia crypto hash generate argon2 --password 'YOUR_SECRET' + client_secret: '$argon2id$v=19$m=65536,t=3,p=4$REPLACE_WITH_REAL_HASH' + authorization_policy: one_factor + redirect_uris: + - https://jenkins.swave.lol/securityRealm/finishLogin + scopes: + - openid + - profile + - email + - groups + grant_types: + - authorization_code + response_types: + - code + userinfo_signed_response_alg: none diff --git a/authelia/config/users_database.yml.example b/authelia/config/users_database.yml.example new file mode 100644 index 0000000..1cd73f3 --- /dev/null +++ b/authelia/config/users_database.yml.example @@ -0,0 +1,28 @@ +--- +# Authelia users database example +# +# Copy this file to users_database.yml and fill in real values. +# The actual users_database.yml is gitignored (contains password hashes). +# +# Generate an argon2id hash for a password: +# docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword' +# +# Group membership controls access if you add group-based rules later. + +users: + admin: + displayname: Admin + # Replace with hash from: authelia crypto hash generate argon2 --password 'yourpassword' + password: '$argon2id$v=19$m=65536,t=3,p=4$REPLACE_WITH_REAL_HASH$REPLACE_WITH_REAL_HASH' + email: admin@swave.lol + groups: + - admins + - developers + + # Example: add more users here + # alice: + # displayname: Alice + # password: '$argon2id$v=19$m=65536,t=3,p=4$...$...' + # email: alice@swave.lol + # groups: + # - developers diff --git a/authelia/docker-compose.yaml b/authelia/docker-compose.yaml new file mode 100644 index 0000000..dfba48d --- /dev/null +++ b/authelia/docker-compose.yaml @@ -0,0 +1,39 @@ +version: '3' + +services: + authelia: + image: authelia/authelia:latest + container_name: authelia + restart: always + environment: + # Secrets loaded from files — never commit the actual secret files + AUTHELIA_JWT_SECRET_FILE: /secrets/jwt_secret + AUTHELIA_SESSION_SECRET_FILE: /secrets/session_secret + AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/storage_encryption_key + AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE: /secrets/oidc_hmac_secret + volumes: + - /var/authelia/config:/config + - /var/authelia/data:/data + - /var/authelia/secrets:/secrets:ro + networks: + authelia_network: + ipv4_address: 172.26.0.2 + + authelia-redis: + image: redis:alpine + container_name: authelia-redis + restart: always + command: redis-server --appendonly yes + volumes: + - /var/authelia/redis:/data + networks: + authelia_network: + ipv4_address: 172.26.0.3 + +networks: + authelia_network: + name: authelia_network + driver: bridge + ipam: + config: + - subnet: 172.26.0.0/16 diff --git a/authelia/nginx-authelia.conf b/authelia/nginx-authelia.conf new file mode 100644 index 0000000..367746f --- /dev/null +++ b/authelia/nginx-authelia.conf @@ -0,0 +1,44 @@ +# Authelia — auth.swave.lol +# Add this server block to /var/nginx/conf/nginx.conf +# Also add auth.swave.lol to the HTTP redirect block's server_name list. + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name auth.swave.lol; + + server_tokens off; + + ssl_certificate /var/letsencrypt/etc/live/swave.lol/fullchain.pem; + ssl_certificate_key /var/letsencrypt/etc/live/swave.lol/privkey.pem; + + ssl_buffer_size 8k; + + ssl_dhparam /etc/ssl/certs/dhparam-2048.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + + ssl_ecdh_curve secp384r1; + ssl_session_tickets off; + + # OCSP stapling + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8; + + charset utf-8; + + location / { + proxy_pass http://authelia:9091; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # Required for Authelia to construct correct redirect URLs + proxy_set_header X-Forwarded-Host $http_host; + } +} diff --git a/authelia/setup.md b/authelia/setup.md new file mode 100644 index 0000000..9faf7a3 --- /dev/null +++ b/authelia/setup.md @@ -0,0 +1,306 @@ +# Authelia Setup Guide + +Authelia provides unified authentication (SSO) for all protected services on swave.lol. + +**Architecture:** +- Forward-auth for Netdata, Cockpit (no native auth) +- Forward-auth + HTTP header injection for Gerrit +- OIDC provider for Jenkins (full SSO — one login covers Jenkins session) +- Ghost and cgit remain public (no auth) + +--- + +## 1. DNS record + +Add an A record for `auth.swave.lol` pointing to the server IP. + +--- + +## 2. Create host directories + +```bash +mkdir -p /var/authelia/{config,data,redis,secrets} +``` + +--- + +## 3. Generate secrets + +Authelia needs 5 secrets. Generate them and write each to a file: + +```bash +# JWT secret (signs short-lived tokens) +openssl rand -hex 64 > /var/authelia/secrets/jwt_secret + +# Session secret (encrypts session cookies) +openssl rand -hex 64 > /var/authelia/secrets/session_secret + +# Storage encryption key (encrypts SQLite DB) +openssl rand -hex 64 > /var/authelia/secrets/storage_encryption_key + +# OIDC HMAC secret (signs OIDC tokens) +openssl rand -hex 64 > /var/authelia/secrets/oidc_hmac_secret + +# OIDC RSA private key (signs OIDC JWTs) +openssl genrsa -out /var/authelia/secrets/oidc_rsa_key.pem 4096 + +chmod 600 /var/authelia/secrets/* +``` + +--- + +## 4. Generate Jenkins OIDC client secret + +Jenkins needs a plaintext secret (sent in OIDC requests) and the configuration +needs the argon2id hash of that secret. + +```bash +# Generate a random plaintext secret — save this, you'll enter it in Jenkins UI +openssl rand -hex 32 + +# Hash it for authelia config (replace YOUR_PLAINTEXT_SECRET with the value above) +docker run --rm authelia/authelia:latest \ + authelia crypto hash generate argon2 --password 'YOUR_PLAINTEXT_SECRET' +``` + +Edit `authelia/config/configuration.yml` and replace the placeholder hash under +`identity_providers.oidc.clients[jenkins].secret` with the argon2id output. + +--- + +## 5. Create users database + +```bash +cp /root/Projects/bastion/authelia/config/users_database.yml.example \ + /var/authelia/config/users_database.yml +``` + +Generate a password hash for each user: + +```bash +docker run --rm authelia/authelia:latest \ + authelia crypto hash generate argon2 --password 'YOUR_PASSWORD' +``` + +Edit `/var/authelia/config/users_database.yml` and fill in the hashes. + +--- + +## 6. Copy Authelia configuration + +```bash +cp /root/Projects/bastion/authelia/config/configuration.yml /var/authelia/config/ +``` + +--- + +## 7. Get SSL certificate for auth.swave.lol + +Add `-d auth.swave.lol` when running certbot, or if certs already exist, run: + +```bash +cd /root/Projects/bastion/nginx +bash run_certbot.sh +``` + +The `run_certbot.sh` script already includes `auth.swave.lol`. Make sure the DNS +record from step 1 is propagated before running this. + +--- + +## 8. Start Authelia + +```bash +cd /root/Projects/bastion/authelia +docker compose up -d +``` + +Check logs: + +```bash +docker logs authelia +``` + +Authelia is healthy when you see: `Startup complete` + +--- + +## 9. Update nginx config and restart nginx + +### 9a. Add Authelia server block + +Append the contents of `authelia/nginx-authelia.conf` to `/var/nginx/conf/nginx.conf`. + +Also add `auth.swave.lol` to the `server_name` list in the HTTP→HTTPS redirect block: + +```nginx +server_name swave.lol blog.swave.lol ghost.swave.lol jenkins.swave.lol cgit.swave.lol gerrit.swave.lol nexus.swave.lol registry.swave.lol auth.swave.lol; +``` + +### 9b. Add auth_request to protected location blocks + +For each protected location (Netdata, Cockpit, Gerrit, Jenkins, Nexus, registry), +add these lines **before** the `proxy_pass` directive: + +```nginx +auth_request /_authelia-auth; +auth_request_set $authelia_user $upstream_http_remote_user; +``` + +For Gerrit locations only, also add the header injection **after** the auth lines: + +```nginx +proxy_set_header X-Forwarded-User $authelia_user; +``` + +### 9c. Add the internal auth subrequest location and error handler + +In each server block that uses `auth_request`, add these two locations: + +```nginx +location = /_authelia-auth { + internal; + proxy_pass http://authelia:9091/api/verify; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + proxy_set_header X-Forwarded-Method $request_method; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-Uri $request_uri; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +} + +error_page 401 = @authelia_login_redirect; +location @authelia_login_redirect { + return 302 https://auth.swave.lol/?rd=$scheme://$http_host$request_uri; +} +``` + +### 9d. Restart nginx + +```bash +cd /root/Projects/bastion/nginx +docker compose restart +``` + +Or if nginx is already running with the new network: + +```bash +docker exec nginx nginx -s reload +``` + +--- + +## 10. Configure Gerrit HTTP header auth + +Edit `/var/gerrit/etc/gerrit.config`. Change the `[auth]` section to: + +```ini +[auth] + type = HTTP + httpHeader = X-Forwarded-User + emailFormat = {0}@swave.lol + registerEmailPrivateKey = <output of: openssl rand -hex 20> +``` + +Generate the key: + +```bash +openssl rand -hex 20 +``` + +Restart Gerrit: + +```bash +docker restart gerrit +``` + +**Important:** The first user to log in via Authelia becomes Gerrit's administrator. +Log in immediately after restarting to claim the admin account. + +--- + +## 11. Configure Jenkins OIDC + +1. Install the plugin: **Manage Jenkins** > **Plugins** > search `oic-auth` + (OpenID Connect Authentication Plugin) > Install + +2. Go to **Manage Jenkins** > **Security** > **Security Realm** + +3. Select **Login with OpenID Connect** + +4. Configure: + - **Well-known configuration URL**: `https://auth.swave.lol/.well-known/openid-configuration` + - **Client ID**: `jenkins` + - **Client Secret**: the plaintext secret from step 4 (not the hash) + - **Override scope**: `openid profile email groups` + +5. Under **Advanced** > **Username field**: set to `preferred_username` + +6. Ensure **Jenkins URL** is set to `https://jenkins.swave.lol/jenkins` in + **Manage Jenkins** > **System** > **Jenkins URL** + +7. Save and test by logging out and back in. + +--- + +## 12. Enable TOTP two-factor auth (optional, later) + +When ready to require 2FA, change `one_factor` to `two_factor` in +`/var/authelia/config/configuration.yml` for the rules you want to upgrade, then: + +```bash +docker restart authelia +``` + +Users will be prompted to enroll their TOTP app on next login. + +--- + +## 13. Set up SMTP notifier (optional, later) + +The filesystem notifier writes emails to `/var/authelia/data/notification.txt`. +To send real emails, replace the `notifier` section in `configuration.yml`: + +```yaml +notifier: + smtp: + username: your-email@gmail.com + password: your-app-password # or use _FILE env var for secret + host: smtp.gmail.com + port: 587 + sender: Authelia <your-email@gmail.com> +``` + +Then restart Authelia. + +--- + +## Verification + +After completing all steps: + +```bash +# Authelia portal accessible +curl -I https://auth.swave.lol/ + +# Netdata redirects to auth portal +curl -I https://swave.lol/netdata/ + +# Cockpit redirects to auth portal +curl -I https://swave.lol/cockpit/ + +# Gerrit subdomain redirects to auth portal +curl -I https://gerrit.swave.lol/ + +# Jenkins subdomain redirects to auth portal +curl -I https://jenkins.swave.lol/ + +# Authelia logs show no errors +docker logs authelia +``` + +Log in at `https://auth.swave.lol/` with your credentials from `users_database.yml`. +After login, Netdata, Cockpit, Gerrit, and Jenkins should be accessible without +re-entering credentials (single sign-on). diff --git a/cockpit/nginx-cockpit.conf b/cockpit/nginx-cockpit.conf index 6796cf5..c7fce0a 100644 --- a/cockpit/nginx-cockpit.conf +++ b/cockpit/nginx-cockpit.conf @@ -11,6 +11,8 @@ location = /cockpit { } location /cockpit/ { + auth_request /_authelia-auth; + auth_request_set $authelia_user $upstream_http_remote_user; proxy_pass https://host.docker.internal:9090/cockpit/; proxy_ssl_verify off; proxy_http_version 1.1; diff --git a/doc/setup-guide.md b/doc/setup-guide.md index 9a5c9d0..daa06b2 100644 --- a/doc/setup-guide.md +++ b/doc/setup-guide.md @@ -14,6 +14,7 @@ Full instruction to build the server from a fresh Debian installation. - `gerrit.swave.lol` - `nexus.swave.lol` - `registry.swave.lol` + - `auth.swave.lol` ## 1. Install Docker @@ -581,24 +582,74 @@ docker compose restart All services should now be accessible: -| URL | Service | -|-----|---------| -| `https://swave.lol` | Ghost (blog) | -| `https://blog.swave.lol` | Ghost (blog, alias) | -| `https://swave.lol/jenkins` | Jenkins (path-based) | -| `https://jenkins.swave.lol` | Jenkins (subdomain) | -| `https://swave.lol/cgit` | Cgit (path-based) | -| `https://cgit.swave.lol` | Cgit (subdomain) | -| `https://swave.lol/gerrit` | Gerrit (path-based) | -| `https://gerrit.swave.lol` | Gerrit (subdomain) | -| `https://swave.lol/nexus` | Nexus (path-based) | -| `https://nexus.swave.lol` | Nexus (subdomain) | -| `https://registry.swave.lol` | Docker Registry (via Nexus) | -| `https://swave.lol/cockpit/` | Cockpit (server admin) | -| `https://swave.lol/netdata/` | Netdata (metrics) | -| `http://<server-ip>:9000` | Portainer | -| `ssh://git@<server-ip>/repos/<repo>.git` | Git (SSH) | -| `git://<server-ip>/<repo>.git` | Git (daemon, read-only) | +| URL | Service | Auth | +|-----|---------|------| +| `https://swave.lol` | Ghost (blog) | Public | +| `https://blog.swave.lol` | Ghost (blog, alias) | Public | +| `https://swave.lol/jenkins` | Jenkins (path-based) | Authelia (one_factor) | +| `https://jenkins.swave.lol` | Jenkins (subdomain) | Authelia OIDC | +| `https://swave.lol/cgit` | Cgit (path-based) | Public | +| `https://cgit.swave.lol` | Cgit (subdomain) | Public | +| `https://swave.lol/gerrit` | Gerrit (path-based) | Authelia (one_factor) | +| `https://gerrit.swave.lol` | Gerrit (subdomain) | Authelia (one_factor) | +| `https://swave.lol/nexus` | Nexus (path-based) | Authelia (one_factor) | +| `https://nexus.swave.lol` | Nexus (subdomain) | Authelia (one_factor) | +| `https://registry.swave.lol` | Docker Registry (via Nexus) | Authelia (one_factor) | +| `https://swave.lol/cockpit/` | Cockpit (server admin) | Authelia (one_factor) | +| `https://swave.lol/netdata/` | Netdata (metrics) | Authelia (one_factor) | +| `https://auth.swave.lol` | Authelia (SSO portal) | — | +| `http://<server-ip>:9000` | Portainer | — | +| `ssh://git@<server-ip>/repos/<repo>.git` | Git (SSH) | — | +| `git://<server-ip>/<repo>.git` | Git (daemon, read-only) | — | + + +## 11. Authelia (SSO) + +Authelia provides a unified authentication layer for protected services. +See `authelia/setup.md` for the full step-by-step guide. + +Authelia must start **after** Netdata (which creates `monitoring_network`) and +**before** Nginx. + +### 11.1 Quick start + +```bash +# Create host directories +mkdir -p /var/authelia/{config,data,redis,secrets} + +# Generate secrets (see authelia/setup.md for details) +openssl rand -hex 64 > /var/authelia/secrets/jwt_secret +openssl rand -hex 64 > /var/authelia/secrets/session_secret +openssl rand -hex 64 > /var/authelia/secrets/storage_encryption_key +openssl rand -hex 64 > /var/authelia/secrets/oidc_hmac_secret +openssl genrsa -out /var/authelia/secrets/oidc_rsa_key.pem 4096 +chmod 600 /var/authelia/secrets/* + +# Copy config and create users database +cp /root/Projects/bastion/authelia/config/configuration.yml /var/authelia/config/ +cp /root/Projects/bastion/authelia/config/users_database.yml.example \ + /var/authelia/config/users_database.yml +# Edit /var/authelia/config/users_database.yml — fill in real password hashes + +# Start Authelia +cd /root/Projects/bastion/authelia +docker compose up -d +``` + +### 11.2 Configured auth methods + +| Service | Auth method | +|---------|-------------| +| Ghost | Public (no auth) | +| cgit | Public (no auth) | +| Netdata | Forward-auth (Authelia) | +| Cockpit | Forward-auth (Authelia) | +| Gerrit | Forward-auth + HTTP header (`X-Forwarded-User`) | +| Jenkins | OIDC (Authelia as provider) | +| Nexus | Forward-auth (Authelia) | +| Docker Registry | Forward-auth (Authelia) | + +For Gerrit HTTP header auth and Jenkins OIDC plugin setup, see `authelia/setup.md`. ## Network Architecture @@ -630,6 +681,11 @@ monitoring_network (172.24.0.0/16) ├── netdata (172.24.0.2) └── nginx +authelia_network (172.26.0.0/16) +├── authelia (172.26.0.2, port 9091) +├── authelia-redis (172.26.0.3, port 6379) +└── nginx + Cockpit runs natively on the host (port 9090). Nginx reaches it via host.docker.internal (host-gateway). ``` @@ -666,7 +722,10 @@ docker compose -f /root/Projects/bastion/git-server/server.yaml up -d gerrit # 7. Netdata (creates monitoring_network) docker compose -f /root/Projects/bastion/netdata/docker-compose.yaml up -d -# 8. Nginx (joins all networks — must be last) +# 8. Authelia (creates authelia_network) +docker compose -f /root/Projects/bastion/authelia/docker-compose.yaml up -d + +# 9. Nginx (joins all networks — must be last) docker compose -f /root/Projects/bastion/nginx/docker-compose.yaml up -d ``` @@ -683,5 +742,6 @@ docker compose -f /root/Projects/bastion/nginx/docker-compose.yaml up -d | 9000 | Portainer (web UI) | TCP | | 8081 | Nexus (direct, for testing) | TCP | | 9090 | Cockpit (native, host only — proxied via nginx) | TCP | +| 9091 | Authelia (internal only — proxied via nginx) | TCP | | 9418 | Git Server (git daemon) | TCP | | 50000 | Jenkins (agent communication) | TCP | diff --git a/git-server/gerrit.config.example b/git-server/gerrit.config.example new file mode 100644 index 0000000..07f8ec3 --- /dev/null +++ b/git-server/gerrit.config.example @@ -0,0 +1,35 @@ +[gerrit] + basePath = git + canonicalWebUrl = https://gerrit.swave.lol + # serverId is auto-generated by Gerrit on first run — do not set manually + +[auth] + type = HTTP + httpHeader = X-Forwarded-User + emailFormat = {0}@swave.lol + # registerEmailPrivateKey is auto-generated by Gerrit on first run + # Generate manually with: openssl rand -hex 20 + # Do NOT commit the real value — keep it only in /var/gerrit/etc/gerrit.config + gitBasicAuthPolicy = LDAP + loginUrl = https://gerrit.swave.lol/login/ + +[sshd] + listenAddress = *:29418 + +[httpd] + listenUrl = http://*:8080/ + +[receive] + enableSignedPush = false + +[sendemail] + smtpServer = localhost + +[index] + type = lucene + +[cache] + directory = cache + +# Note: the [container] section (javaOptions, javaHome, user) is auto-generated +# by Gerrit on first startup and should not be manually configured. diff --git a/jenkins/docker-compose.yaml b/jenkins/docker-compose.yaml index c688331..8b69492 100644 --- a/jenkins/docker-compose.yaml +++ b/jenkins/docker-compose.yaml @@ -5,7 +5,7 @@ services: restart: always container_name: jenkins environment: - - JENKINS_OPTS=--prefix=/jenkins + - JENKINS_OPTS= - JAVA_OPTS=-Xmx512m ports: - 8080:8080 diff --git a/netdata/nginx-netdata.conf b/netdata/nginx-netdata.conf index 9151d4c..d0d5f06 100644 --- a/netdata/nginx-netdata.conf +++ b/netdata/nginx-netdata.conf @@ -10,6 +10,8 @@ location = /netdata { } location /netdata/ { + auth_request /_authelia-auth; + auth_request_set $authelia_user $upstream_http_remote_user; proxy_pass http://172.24.0.2:19999/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 382ee62..7ac081c 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -115,27 +115,21 @@ server { proxy_redirect / /cgit/; } - # Gerrit via path prefix on main domain - location /gerrit { - auth_request /_authelia-auth; - auth_request_set $authelia_user $upstream_http_remote_user; - proxy_pass http://gerrit:8080/gerrit; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-User $authelia_user; + + # Gerrit — redirect to subdomain (Gerrit doesn't support subpath proxying) + location = /gerrit { + return 301 https://gerrit.swave.lol/; + } + location ~ ^/gerrit/(.*)$ { + return 301 https://gerrit.swave.lol/$1; } - # Jenkins via path prefix on main domain - location /jenkins { - auth_request /_authelia-auth; - auth_request_set $authelia_user $upstream_http_remote_user; - proxy_pass http://jenkins:8080/jenkins; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + # Jenkins — redirect to subdomain (strips /jenkins prefix) + location = /jenkins { + return 301 https://jenkins.swave.lol/; + } + location ~ ^/jenkins/(.*)$ { + return 301 https://jenkins.swave.lol/$1; } # Nexus via path prefix on main domain @@ -162,7 +156,9 @@ server { # Authelia forward-auth subrequest endpoint location = /_authelia-auth { internal; - proxy_pass http://authelia:9091/api/verify; + resolver 127.0.0.11 valid=30s; + set $authelia_upstream http://authelia:9091/api/verify; + proxy_pass $authelia_upstream; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; @@ -210,16 +206,19 @@ server { charset utf-8; - # Jenkins requires --prefix=/jenkins, so all URLs must go through /jenkins - # Redirect root to /jenkins, then proxy /jenkins to Jenkins - location = / { - return 302 https://$host/jenkins/; + # OIDC callback — must bypass auth_request so Jenkins can process the code/state + location = /securityRealm/finishLogin { + proxy_pass http://jenkins:8080/securityRealm/finishLogin; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; } - location /jenkins { + location / { auth_request /_authelia-auth; auth_request_set $authelia_user $upstream_http_remote_user; - proxy_pass http://jenkins:8080/jenkins; + proxy_pass http://jenkins:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -229,7 +228,9 @@ server { # Authelia forward-auth subrequest endpoint location = /_authelia-auth { internal; - proxy_pass http://authelia:9091/api/verify; + resolver 127.0.0.11 valid=30s; + set $authelia_upstream http://authelia:9091/api/verify; + proxy_pass $authelia_upstream; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; @@ -317,21 +318,37 @@ server { charset utf-8; - location / { + # /login/ — auth_request fires here, X-Forwarded-User injected, Gerrit creates session + location /login { auth_request /_authelia-auth; auth_request_set $authelia_user $upstream_http_remote_user; - proxy_pass http://gerrit:8080; + proxy_pass http://gerrit:8080/login; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-User $authelia_user; + proxy_redirect http://gerrit:8080/ https://gerrit.swave.lol/; + proxy_redirect ~^http://[^/]+/(.*)$ https://gerrit.swave.lol/$1; + } + + # All other paths — no auth_request, anonymous access allowed, Gerrit session cookie handles login state + location / { + proxy_pass http://gerrit:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect http://gerrit:8080/ https://gerrit.swave.lol/; + proxy_redirect ~^http://[^/]+/(.*)$ https://gerrit.swave.lol/$1; } # Authelia forward-auth subrequest endpoint location = /_authelia-auth { internal; - proxy_pass http://authelia:9091/api/verify; + resolver 127.0.0.11 valid=30s; + set $authelia_upstream http://authelia:9091/api/verify; + proxy_pass $authelia_upstream; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; @@ -397,7 +414,9 @@ server { # Authelia forward-auth subrequest endpoint location = /_authelia-auth { internal; - proxy_pass http://authelia:9091/api/verify; + resolver 127.0.0.11 valid=30s; + set $authelia_upstream http://authelia:9091/api/verify; + proxy_pass $authelia_upstream; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; @@ -448,10 +467,76 @@ server { chunked_transfer_encoding on; location / { + auth_request /_authelia-auth; + auth_request_set $authelia_user $upstream_http_remote_user; proxy_pass http://nexus:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } + + # Authelia forward-auth subrequest endpoint + location = /_authelia-auth { + internal; + resolver 127.0.0.11 valid=30s; + set $authelia_upstream http://authelia:9091/api/verify; + proxy_pass $authelia_upstream; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + proxy_set_header X-Forwarded-Method $request_method; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-Uri $request_uri; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + error_page 401 = @authelia_login_redirect; + location @authelia_login_redirect { + return 302 https://auth.swave.lol/?rd=$scheme://$http_host$request_uri; + } +} + +# Authelia — auth.swave.lol +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name auth.swave.lol; + + server_tokens off; + + ssl_certificate /var/letsencrypt/etc/live/swave.lol/fullchain.pem; + ssl_certificate_key /var/letsencrypt/etc/live/swave.lol/privkey.pem; + + ssl_buffer_size 8k; + + ssl_dhparam /etc/ssl/certs/dhparam-2048.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + + ssl_ecdh_curve secp384r1; + ssl_session_tickets off; + + # OCSP stapling + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8; + + charset utf-8; + + location / { + resolver 127.0.0.11 valid=30s; + set $authelia_upstream http://authelia:9091; + proxy_pass $authelia_upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + } } |
