recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)

Sanitized single-commit public mirror of recipe-maintainer.
- Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders.
- Removed plans/ and planned-updates/ (deployment-planning docs) so no client/
  deployment domains appear in the public repo.
- All other secret stores were already gitignored.
- docs.coopcloud.tech retained as a submodule (public upstream).
This commit is contained in:
2026-06-16 20:18:24 +00:00
commit f283a371bb
253 changed files with 15975 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
name = "authentik"
[dependencies]
requires = []
test_requires = ["ld2"] # oidc_integration test needs the ld2 lasuite-docs instance
+30
View File
@@ -0,0 +1,30 @@
# Authentik — First-Time Setup
## Prerequisites
- DNS: `authentik.<domain_suffix>` must resolve to the server
## Steps
1. **Create the app:**
```bash
abra app new authentik --server <SERVER> --domain authentik.<DOMAIN_SUFFIX> --no-input
```
2. **Generate secrets:**
```bash
abra app secret generate authentik.<DOMAIN_SUFFIX> --all -m --no-input
```
Save output to `recipe-info/testsecrets/authentik.<DOMAIN_SUFFIX>`.
3. **Deploy:**
```bash
abra app deploy authentik.<DOMAIN_SUFFIX> --chaos --force --no-input
```
4. **Verify:** curl `https://authentik.<DOMAIN_SUFFIX>` returns HTTP 200.
## Notes
- Admin credentials: username `akadmin`, password from `admin_pass` secret in testsecrets.
- The bootstrap token (`AK_TOKEN`) is the `bootstrap_token` secret — needed by SSO setup scripts for other recipes.
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Setup Authentik OIDC integration for La Suite Docs (ld2).
Creates an OAuth2 provider, application, and test user in Authentik,
then inserts the client secret and updates the Docs env file with
Authentik OIDC endpoints.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from lib.abra import app_secret_insert
from lib.authentik import AuthentikAdmin
from lib.env import apply_env_overrides, get_abra_env_path, read_env_file
from lib.models import load_default_instance
from lib.secrets import load_secrets
# Configuration
PROVIDER_NAME = "lasuite-docs"
APP_SLUG = "lasuite-docs"
CLIENT_ID = "docs"
TEST_USER = "testuser"
TEST_PASS = "testpass123"
TEST_EMAIL = f"{TEST_USER}@test.example.com"
def main():
inst = load_default_instance()
ak_domain = inst.default_domain("authentik")
# Docs instance uses a custom domain (ld2) to avoid conflict with keycloak-backed docs
DOCS_DOMAIN = f"ld2.{inst.domain_suffix}"
ak_url = f"https://{ak_domain}"
# Get Authentik admin token from synced secrets
ak_secrets = load_secrets(ak_domain)
ak_token = ak_secrets["admin_token"]
ak = AuthentikAdmin(ak_url, ak_token)
# Resolve Authentik UUIDs
uuids = ak.resolve_uuids()
# Step 1: Create OAuth2 provider
provider_pk, client_secret = ak.ensure_provider(
PROVIDER_NAME, CLIENT_ID,
redirect_uris=[{
"matching_mode": "strict",
"url": f"https://{DOCS_DOMAIN}/api/v1.0/callback/",
}],
uuids=uuids,
)
# Step 2: Create application
ak.ensure_application("La Suite Docs", APP_SLUG, provider_pk,
f"https://{DOCS_DOMAIN}")
# Step 3: Create test user
user_pk = ak.ensure_user(TEST_USER, TEST_EMAIL, TEST_PASS)
app_password = ak.ensure_app_password(user_pk)
# Step 4: Insert client secret into Docs via abra
print("=== Insert OIDC client secret into Docs (ld2) ===", flush=True)
env_path = get_abra_env_path(inst.server, DOCS_DOMAIN)
if not env_path.exists():
print(f" WARNING: Docs env file not found at {env_path}", flush=True)
print(f" Create the app first: abra app new lasuite-docs --server {inst.server} --domain {DOCS_DOMAIN} --no-input", flush=True)
print(" Skipping secret insertion and env update", flush=True)
else:
env_data = read_env_file(env_path)
current_version = env_data.get("SECRET_OIDC_RPCS_VERSION", "v1")
next_num = int(current_version.lstrip("v")) + 1
next_version = f"v{next_num}"
print(f" Current secret version: {current_version}", flush=True)
print(f" Inserting as: {next_version}", flush=True)
app_secret_insert(DOCS_DOMAIN, "oidc_rpcs", next_version, client_secret)
# Step 5: Update Docs env with Authentik OIDC settings
print("=== Update Docs OIDC settings in env file ===", flush=True)
apply_env_overrides(env_path, {
"SECRET_OIDC_RPCS_VERSION": next_version,
"AUTH_DOMAIN": ak_domain,
"OIDC_OP_JWKS_ENDPOINT": f"https://{ak_domain}/application/o/{APP_SLUG}/jwks/",
"OIDC_OP_AUTHORIZATION_ENDPOINT": f"https://{ak_domain}/application/o/authorize/",
"OIDC_OP_TOKEN_ENDPOINT": f"https://{ak_domain}/application/o/token/",
"OIDC_OP_USER_ENDPOINT": f"https://{ak_domain}/application/o/userinfo/",
"OIDC_RP_CLIENT_ID": CLIENT_ID,
})
# Step 6: Write credentials file
script_dir = os.path.dirname(os.path.abspath(__file__))
creds_file = os.path.join(script_dir, f"authentik-test-credentials.{inst.domain_suffix}.toml")
print(f"=== Write credentials to {creds_file} ===", flush=True)
with open(creds_file, "w") as f:
f.write(f'# Authentik OIDC credentials for La Suite Docs (ld2) test instance\n')
f.write(f'#\n')
f.write(f'# Authentik instance: {ak_domain}\n')
f.write(f'# Application slug: {APP_SLUG}\n')
f.write(f'# Created by: setup_docs_integration.py\n')
f.write(f'\n')
f.write(f'# Authentik admin\n')
f.write(f'ak_token = "{ak_token}"\n')
f.write(f'\n')
f.write(f'# OIDC provider\n')
f.write(f'ak_app_slug = "{APP_SLUG}"\n')
f.write(f'ak_client_id = "{CLIENT_ID}"\n')
f.write(f'ak_client_secret = "{client_secret}"\n')
f.write(f'\n')
f.write(f'# Authentik OIDC endpoints\n')
f.write(f'ak_token_endpoint = "https://{ak_domain}/application/o/token/"\n')
f.write(f'ak_userinfo_endpoint = "https://{ak_domain}/application/o/userinfo/"\n')
f.write(f'ak_discovery_endpoint = "https://{ak_domain}/application/o/{APP_SLUG}/.well-known/openid-configuration"\n')
f.write(f'\n')
f.write(f'# Test user (password for browser login, app_password for password grant)\n')
f.write(f'ak_test_user = "{TEST_USER}"\n')
f.write(f'ak_test_pass = "{TEST_PASS}"\n')
f.write(f'ak_test_app_password = "{app_password}"\n')
f.write(f'ak_test_email = "{TEST_EMAIL}"\n')
f.write(f'\n')
f.write(f'# Docs instance\n')
f.write(f'docs_domain = "{DOCS_DOMAIN}"\n')
print(f" Written to {creds_file}", flush=True)
print("", flush=True)
print("=== Authentik OIDC integration setup complete ===", flush=True)
print("", flush=True)
print("Next steps:", flush=True)
print(f" 1. Redeploy Docs: abra app deploy {DOCS_DOMAIN} --chaos --force --no-input", flush=True)
print(f" 2. Run OIDC test: python3 recipe-info/authentik/tests/oidc_integration.py", flush=True)
if __name__ == "__main__":
main()
+140
View File
@@ -0,0 +1,140 @@
# Authentik Migration Test
Test that upgrading from 10.1.4+2025.10.2 (using `media:/media` mount) to 10.2.0+2026.2.1 (using `media:/data/media` mount) works correctly and preserves data.
## Overview
1. Deploy the old version
2. Seed test data into the media volume
3. Upgrade to the new version
4. Verify media files are accessible at the new path
## Steps
### 1. Deploy old version
Check out the pre-migration tag and deploy:
```bash
cd ~/.abra/recipes/authentik
git checkout 10.1.4+2025.10.2
```
Deploy:
```bash
abra app deploy authentik.<DOMAIN_SUFFIX> --chaos --force --no-input
```
Wait for the app to become healthy:
```bash
abra app ps authentik.<DOMAIN_SUFFIX> --chaos --no-input -m
```
### 2. Seed test data into the media volume
Create a marker file in the old `/media` volume:
```bash
script -qefc "abra app run authentik.<DOMAIN_SUFFIX> app -- sh -c 'echo migration-test-marker > /media/test-migration-marker.txt && echo created'" /dev/null
```
Verify the file was created:
```bash
script -qefc "abra app run authentik.<DOMAIN_SUFFIX> app -- cat /media/test-migration-marker.txt" /dev/null
```
Expected output: `migration-test-marker`
Record existing files in `/media` for comparison:
```bash
script -qefc "abra app run authentik.<DOMAIN_SUFFIX> app -- ls -la /media/" /dev/null
```
### 3. Upgrade to new version
Switch to the new version (with `media:/data/media` mount point change):
```bash
cd ~/.abra/recipes/authentik
git checkout <new-tag-or-branch>
```
Deploy the upgrade:
```bash
abra app deploy authentik.<DOMAIN_SUFFIX> --chaos --force --no-input
```
Wait for convergence (authentik runs database migrations on startup):
```bash
sleep 60
abra app ps authentik.<DOMAIN_SUFFIX> --chaos --no-input -m
```
### 4. Verify post-upgrade state
#### 4a. Health check
```bash
python3 recipe-info/authentik/tests/health_check.py
```
#### 4b. Marker file preserved at new path
The mount point change means `media:/data/media` — files that were at the volume root now appear under `/data/media/`:
```bash
script -qefc "abra app run authentik.<DOMAIN_SUFFIX> app -- cat /data/media/test-migration-marker.txt" /dev/null
```
Expected output: `migration-test-marker`
#### 4c. Directory listing
Verify the old files are now under `/data/media/`:
```bash
script -qefc "abra app run authentik.<DOMAIN_SUFFIX> app -- ls -la /data/media/" /dev/null
```
Compare with the listing from step 2 — all files from the old `/media/` should now appear under `/data/media/`.
#### 4d. Version label
```bash
abra app ps authentik.<DOMAIN_SUFFIX> --chaos --no-input -m
```
Confirm the version label shows `10.2.0+2026.2.1`.
#### 4e. Full test suite
```bash
python3 recipe-info/authentik/tests/health_check.py
python3 recipe-info/authentik/tests/oidc_integration.py
```
### 5. Clean up test marker
```bash
script -qefc "abra app run authentik.<DOMAIN_SUFFIX> app -- rm /data/media/test-migration-marker.txt" /dev/null
```
## Pass/fail criteria
| Check | Expected |
|-------|----------|
| Pre-upgrade health check | HTTP 200 |
| Marker file created in old `/media` | `migration-test-marker` |
| Post-upgrade health check | HTTP 200 |
| Marker file at `/data/media/test-migration-marker.txt` | `migration-test-marker` |
| `/data/media/` contains old files | Matches pre-upgrade listing |
| Version label updated | `10.2.0+2026.2.1` |
| OIDC integration test | PASS |
All checks must pass for the migration test to pass.
+85
View File
@@ -0,0 +1,85 @@
# Authentik Test Plan
Target: `https://authentik.<DOMAIN_SUFFIX>`
## Services
| Service | Image | Purpose |
|---------|-------|---------|
| app | `ghcr.io/goauthentik/server` | Web server (port 9000) |
| worker | `ghcr.io/goauthentik/server` | Background worker |
| db | `postgres:15` | PostgreSQL database |
## Test Setup
Before running all tests, the following must be in place:
### 1. Deploy authentik
```bash
abra app deploy authentik.<DOMAIN_SUFFIX> --chaos --force --no-input
```
### 2. Deploy the ld2 instance (test dependency)
The OIDC integration test uses a second La Suite Docs instance (`ld2`) as the relying party. This is separate from the primary `lasuite-docs` instance (which uses Keycloak for SSO).
If `ld2` does not exist yet, create it:
```bash
abra app new lasuite-docs --server <SERVER> --domain ld2.<DOMAIN_SUFFIX> --no-input
abra app secret generate ld2.<DOMAIN_SUFFIX> --all -m --no-input
abra app deploy ld2.<DOMAIN_SUFFIX> --chaos --force --no-input
```
If it already exists, just deploy:
```bash
abra app deploy ld2.<DOMAIN_SUFFIX> --chaos --force --no-input
```
### 3. Run the Authentik-Docs integration setup
```bash
python3 recipe-info/authentik/setup_docs_integration.py
```
This configures authentik as the OIDC provider for ld2:
1. Creates an OAuth2 provider (`lasuite-docs`) via the authentik REST API
2. Creates an Application linked to the provider
3. Creates a test user (`testuser` / `testpass123`) with an APP_PASSWORD token
4. Inserts the OIDC client secret into the ld2 Docs app via `abra app secret insert`
5. Updates the ld2 env file with authentik OIDC endpoints
6. Writes credentials to `authentik-test-credentials.<DOMAIN_SUFFIX>.toml`
### 4. Redeploy ld2 with OIDC config
```bash
abra app deploy ld2.<DOMAIN_SUFFIX> --chaos --force --no-input
```
## Automated Tests
- `tests/health_check.py` — HTTP 200 check on the main URL
- `tests/oidc_integration.py` — Full OIDC flow: obtains a token from authentik for a test user, then authenticates against the ld2 La Suite Docs API
### Credentials
| Key | Description |
|-----|-------------|
| `ak_token` | Authentik admin bootstrap token |
| `ak_client_id` / `ak_client_secret` | OIDC client ID and secret |
| `ak_test_user` / `ak_test_pass` | Test user credentials (password for browser login) |
| `ak_test_app_password` | APP_PASSWORD token for password grant (authentik requires this instead of regular passwords) |
| `ak_test_email` | Test user email |
Stored in `authentik-test-credentials.<DOMAIN_SUFFIX>.toml`.
## Manual Verification
1. Open `https://authentik.<DOMAIN_SUFFIX>` in a browser — should show the authentik login page
2. Log in with admin credentials: `akadmin` / `<admin_pass from testsecrets>`
3. Navigate to Admin Interface — should load the admin dashboard
4. Check System → System Tasks — background worker should be processing tasks
5. Navigate to Applications → Providers — verify `lasuite-docs` OAuth2 provider exists
6. Navigate to Applications → Applications — verify `lasuite-docs` application exists
7. Open `https://ld2.<DOMAIN_SUFFIX>` — click Login and verify the OIDC redirect to authentik works
8. Log in as `testuser` / `testpass123` — should redirect back to Docs as the authenticated user
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""Health check for Authentik."""
import argparse
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from utils.tests.helpers import http_get, resolve_domain
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN'))
args = parser.parse_args()
domain = args.domain or resolve_domain('authentik')
url = f"https://{domain}"
print(f"Checking Authentik at {url} ...")
status, _ = http_get(url)
if status == 200:
print(f"PASS: Authentik returned HTTP {status}")
else:
print(f"FAIL: Authentik returned HTTP {status} (expected 200)")
sys.exit(1)
if __name__ == '__main__':
main()
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Authentik OIDC integration test."""
import argparse
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from utils.tests.helpers import (
http_get, http_post, load_toml_credentials, resolve_domain, _load_settings,
)
def main():
if os.environ.get('SKIP_INTEGRATION') == '1':
print("SKIP: OIDC integration test (SKIP_INTEGRATION=1)")
return
parser = argparse.ArgumentParser()
parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN'))
args = parser.parse_args()
# Docs (ld2) domain — computed from settings
settings = _load_settings()
instance = settings["default_instance"]
suffix = settings["instances"][instance]["domain_suffix"]
docs_domain = f"ld2.{suffix}"
docs_url = f"https://{docs_domain}"
ak_domain = args.domain or resolve_domain('authentik')
ak_url = f"https://{ak_domain}"
recipe_dir = os.path.join(os.path.dirname(__file__), '..')
creds = load_toml_credentials(recipe_dir, 'authentik')
if creds is None:
print("FAIL: Credentials file not found: authentik-test-credentials.<domain_suffix>.toml")
print("Run setup_docs_integration.py first.")
sys.exit(1)
print("=== Authentik OIDC Integration Test ===")
print()
# Step 1: Check lasuite-docs (ld2) is deployed
print("Step 1: Checking lasuite-docs (ld2) is deployed ...")
status, _ = http_get(docs_url)
if status == 0:
print(f" FAIL: lasuite-docs is not reachable at {docs_url}")
sys.exit(1)
elif status >= 500:
print(f" FAIL: lasuite-docs returned HTTP {status}")
sys.exit(1)
print(f" OK: lasuite-docs (ld2) is reachable (HTTP {status})")
# Step 2: Verify Authentik OIDC discovery
print("Step 2: Checking Authentik OIDC discovery ...")
discovery_url = creds["ak_discovery_endpoint"]
status, _ = http_get(discovery_url)
if status != 200:
print(f" FAIL: OIDC discovery returned HTTP {status}")
print(f" URL: {discovery_url}")
sys.exit(1)
print(f" PASS: OIDC discovery endpoint OK (app '{creds['ak_app_slug']}')")
# Step 3: Obtain token from Authentik
print("Step 3: Obtaining token from Authentik for test user ...")
print(" Using APP_PASSWORD for password grant (authentik requirement)")
status, data = http_post(
creds["ak_token_endpoint"],
data={
"grant_type": "password",
"client_id": creds["ak_client_id"],
"client_secret": creds["ak_client_secret"],
"username": creds["ak_test_user"],
"password": creds["ak_test_app_password"],
"scope": "openid email profile",
},
content_type="application/x-www-form-urlencoded",
)
access_token = (data or {}).get("access_token", "")
if not access_token:
error = (data or {}).get("error_description", (data or {}).get("error", "unknown"))
print(f" FAIL: Token request failed: {error}")
sys.exit(1)
print(f" PASS: Got access token ({len(access_token)} chars)")
# Step 4: Authenticate against Docs API
print("Step 4: Calling Docs API with Authentik token ...")
status, body = http_get(
f"{docs_url}/api/v1.0/users/me/",
headers={"Authorization": f"Bearer {access_token}"},
timeout=30,
)
if status != 200:
print(f" FAIL: Docs API returned HTTP {status} (expected 200)")
sys.exit(1)
user_email = (body or {}).get("email", "")
expected_email = creds["ak_test_email"]
if user_email == expected_email:
print(f" PASS: Docs returned authenticated user '{user_email}'")
else:
print(f" FAIL: Expected email '{expected_email}', got '{user_email}'")
sys.exit(1)
print()
print("PASS: Authentik OIDC integration test passed")
print(" Authentik issued a valid token, Docs (ld2) accepted it and returned the correct user.")
if __name__ == '__main__':
main()
+15
View File
@@ -0,0 +1,15 @@
# Authentik Upstream
## Main Project
- **Repository:** https://github.com/goauthentik/authentik
- **Releases:** https://github.com/goauthentik/authentik/releases
- **Website:** https://goauthentik.io
- **Docs:** https://docs.goauthentik.io
## Images
| Service | Image | Release Notes |
|---------|-------|---------------|
| app, worker | `ghcr.io/goauthentik/server` | https://github.com/goauthentik/authentik/releases |
| db | `postgres` | https://www.postgresql.org/docs/release/ |