summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArseney300 <Arseney300@gmail.com>2026-03-04 09:16:29 +0700
committerArseney300 <Arseney300@gmail.com>2026-03-04 09:16:29 +0700
commit24ce3335f9aef46f5d9cff412e09b7d4c20bba44 (patch)
tree3ecbff70b068fd4ec8017e93eed3d7bd9b4208dd
parent7081a34337d3cb758c6ffa738bb7e6b655076aa8 (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--.gitignore1
-rw-r--r--CLAUDE.md46
-rw-r--r--authelia/config/configuration.yml134
-rw-r--r--authelia/config/users_database.yml.example28
-rw-r--r--authelia/docker-compose.yaml39
-rw-r--r--authelia/nginx-authelia.conf44
-rw-r--r--authelia/setup.md306
-rw-r--r--cockpit/nginx-cockpit.conf2
-rw-r--r--doc/setup-guide.md98
-rw-r--r--git-server/gerrit.config.example35
-rw-r--r--jenkins/docker-compose.yaml2
-rw-r--r--netdata/nginx-netdata.conf2
-rw-r--r--nginx/nginx.conf147
13 files changed, 825 insertions, 59 deletions
diff --git a/.gitignore b/.gitignore
index ddb6888..c52ba27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
doc/servers.drawio
doc/Servers.drawio
authelia/config/users_database.yml
+git-server/gerrit.config
diff --git a/CLAUDE.md b/CLAUDE.md
index 0ab9222..c892d9f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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;
+ }
}