Files
ThothII/docs/plans/2026-08-20-psd-server-project-b-authentik.md
T

438 lines
18 KiB
Markdown

# PSD Server Project B Authentik Integration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Convert the accepted Project A installation to public OIDC mode, store owned work sessions in the existing Supabase database's `thoth_sessions` schema, and restore the established Aritmolab-sidebar user journey through the load balancer and Nginx.
**Architecture:** Keep the same installation-descriptor path and Compose project so Project A Qdrant/Ollama volumes and bind state remain authoritative. Close ingress, snapshot Project A configuration, configure Authentik and Supabase, replace the protected installation configuration transactionally at the same paths, validate privately, then open the production route and sidebar link.
**Tech Stack:** Authentik OAuth2/OIDC Authorization Code + PKCE, ThothII OIDC/session store, Supabase PostgreSQL schema migrations/RLS, Docker Compose, Nginx, load balancer, Aritmolab.
---
## Preconditions
- Project A automated and human reports are PASS and explicitly owner-approved.
- Application SHA, workspace SHA, images, Project A report digest, and rollback configuration match
the accepted evidence.
- The production route is closed before authentication/session-storage changes.
- All Authentik operations use the installed version's API/OpenAPI contract. Official current
references include [OAuth2/OIDC providers](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/),
[provider property mappings](https://docs.goauthentik.io/add-secure-apps/providers/property-mappings/),
[application bindings](https://docs.goauthentik.io/add-secure-apps/applications/manage_apps/), and
[blueprint export](https://docs.goauthentik.io/customize/blueprints/export); installed-version
behavior wins over newer documentation.
### Task 1: Freeze Project A and close ingress
**Files:**
- Read: accepted Project A report
- Create: protected Project B transaction root
**Step 1: Verify exact Project A state**
Run status, doctor, auth check, workspace inspect, vector inspect, Pi status/test, Git SHA, and image
identity checks from Project A. Expected: all match the accepted report.
**Step 2: Create a protected transaction root**
Use `mktemp -d` under the survey-approved protected parent, mode `0700`. Record its path and do not
place it in Git.
**Step 3: Close production and temporary ingress**
Keep or restore a maintenance response at the production ThothII route. Disable the optional
Project A test route before changing authentication unless it is needed for a separately approved
private preflight. Confirm neither route reaches ThothII.
**Step 4: Stop and back up Project A**
```bash
<tht> --installation <stable-installation-path> backup \
--output <project-b-transaction-root>/project-a-backup.tar --drain
<tht> --installation <stable-installation-path> stop
```
Verify the archive using the controller's manifest/checksum contract. Preserve a copy of the exact
installation descriptor, env file, override files, auth directory, Nginx fragment, load-balancer
route, Aritmolab sidebar file/revision, and relevant Authentik export metadata.
### Task 2: Decide exact Authentik names and roles
**Files:**
- Create: protected `authentik-change-manifest.yaml`
**Step 1: Select the final public origin**
Use the live survey result, not historical `.it`/`.com` assumptions. Record exactly one HTTPS origin
and callback `<origin>/api/auth/oidc/callback`.
**Step 2: Select exact groups**
Default to dedicated `TOT Users` and `TOT Admin`. Reuse existing groups only if their membership
semantics match and the owner approves. Record exact case-sensitive names.
**Step 3: Define least privilege**
Map user group → `user`, admin group → `admin`. Define a separate service account/token with only
the installed Authentik permission needed to view exact group objects. No write, user-management,
directory-administration, or superuser permission.
**Step 4: Obtain owner approval of the manifest**
The manifest contains object names, slugs, intended bindings, callback, scopes, grant types,
credential destinations, and rollback action—but no secret values. Do not mutate Authentik before
approval.
### Task 3: Export and prepare Authentik
**Files:**
- Create: protected pre-change Authentik export
- Modify: Authentik objects named in the approved manifest
**Step 1: Export relevant configuration**
Use the installed version's supported blueprint/API export. A worker command such as
`ak export_blueprint` is valid only if present in that version. Protect export mode `0600`; remember
write-only provider secrets are not included, so backup their custody separately without printing.
**Step 2: Verify API credential scope**
Use a read-only call to list relevant groups/applications. Expected: administrative creation access
for the setup identity and a distinct path for the future group-view service account. Stop if the
credential is missing or ambiguous.
**Step 3: Create or confirm exact groups**
Create missing dedicated groups, or record approved existing group IDs. Do not bulk-copy LDAP or
unrelated Authentik memberships.
**Step 4: Create the group-catalog service account**
Grant only exact group-view permission. Create its token through the approved protected-secret
mechanism; write it directly to the ThothII secret destination without displaying it.
**Step 5: Create the OIDC provider**
Configure a confidential OAuth2/OIDC provider with the exact callback, issuer mode observed as
appropriate, Authorization Code, PKCE support, and Device Code only when required for
`tht auth check --interactive` and supported by the installed release. Do not enable implicit flow.
**Step 6: Configure scopes and direct groups claim**
Select `openid`, `profile`, and `email`. Inspect a disposable identity's decoded claim keys through
a protected verifier; retain only a redacted shape. Expected: ID token includes direct non-empty
`groups: [string, ...]`.
If the installed default profile mapping already provides that exact claim, reuse it. Otherwise add
a provider scope/property mapping under the requested `profile` scope that returns:
```python
return {"groups": [group.name for group in request.user.ak_groups.all()]}
```
Verify the installed mapping merge semantics before activation. Do not add a custom unrequested
scope because ThothII requests only `openid`, `profile`, and `email`.
**Step 7: Create the Authentik application**
Bind it to the provider. Configure display metadata according to local Aritmolab conventions. Do not
use an Authentik proxy provider or Nginx forward-auth for ThothII.
**Step 8: Create and store the client secret**
Write the client secret directly into the protected ThothII secret bundle key
`THT_OIDC_CLIENT_SECRET`. Store the service-account token as `THT_AUTHENTIK_API_TOKEN`. Never place
either value in the change manifest, shell history, Compose environment, or evidence.
### Task 4: Prepare Supabase schema roles and backup
**Files:**
- Read: `harness/tht/migrations/sessions/001_schema.sql`
- Read: `harness/tht/migrations/sessions/002_security.sql`
- Create: protected Supabase backup/evidence
- Create: runtime and migrator credential files
**Step 1: Confirm the database/schema boundary**
Expected: use the surveyed existing Supabase PostgreSQL database; clinical data remains in
`datawarehouse`; application sessions use schema `thoth_sessions`; no new database is created.
**Step 2: Back up database metadata/data consistently**
Use the existing Supabase/PostgreSQL backup procedure before DDL. Record backup ID, timestamp,
checksum, and restore command. Do not put a dump in the Git repository.
**Step 3: Create or validate dedicated roles**
Create one migrator login and one runtime login according to the migration contract. The runtime
role must not be superuser, owner, BYPASSRLS, CREATEROLE, CREATEDB, or a member of DWH write roles.
The migrator credential remains unavailable to core.
**Step 4: Write protected credential files**
Create separate mode-0640 runtime-password, migrator-password, and session-CA files with surveyed
ownership. Do not use command-line password arguments.
**Step 5: Confirm PostgREST exclusion before migration**
Record the exact exposed schema list. Expected: `thoth_sessions` absent. If the system exposes all
schemas implicitly, stop and resolve the boundary before migration.
### Task 5: Prepare the stable Project B installation configuration
**Files:**
- Modify at the same stable paths: operator env, installation descriptor, authentication directory
- Create: reviewed session-server override copied from `deploy/compose.session-server.yaml.example`
- Create: protected server-session workspace config copied from `deploy/workspaces/server-sessions.yaml.example`
**Step 1: Preserve the Compose project name**
The native controller derives the project name from the absolute installation-descriptor path.
Keep that exact path. Do not point Project B at a second descriptor path, because that would create
new Qdrant/Ollama named volumes instead of using the Project A accepted state.
**Step 2: Stage Project B files beside the live files**
Prepare new env/descriptor/override/auth inputs in the protected transaction root. Add the session
DB host/port/existing database name/runtime role/migrator role/TLS mode and three secret source
paths. Use `verify-full` where hostname/SAN permits; any `verify-ca` exception requires explicit
survey evidence and owner approval.
**Step 3: Add the server-session override**
Copy the current example to a reviewed local file and add it to the existing stable descriptor's
overrides before the Git transport override ordering required by the installation. Do not edit the
tracked example.
**Step 4: Replace local auth state transactionally**
With the stack stopped, move the complete Project A auth directory into the protected transaction
root, recreate an empty private directory at the same path/ownership/mode, and configure OIDC:
```bash
<tht> --installation <stable-installation-path> auth configure \
--mode oidc --public-url <final-https-origin> \
--issuer <authentik-issuer> --client-id <oidc-client-id> \
--authentik-base-url <authentik-base-url> \
--user-group '<exact-user-group>' --admin-group '<exact-admin-group>'
```
Expected: non-secret `auth.yaml` only; secrets resolved from the protected bundle.
**Step 5: Atomically install staged path-only files**
Use same-filesystem rename and preserve required ownership/mode. Keep the Project A originals in
the transaction root. Run `update --check-only`; on failure restore the originals immediately.
### Task 6: Run and verify session migrations
**Files:**
- Modify through one-shot migrator: existing database schema `thoth_sessions`
**Step 1: Validate migration rendering**
```bash
<tht> --installation <stable-installation-path> update --check-only
```
Expected: core and `session-migrate` resolve the same core image; core lacks migrator password;
only the one-shot service sees it.
**Step 2: Run migrations once**
```bash
<tht> --installation <stable-installation-path> sessions migrate --yes
```
Expected JSON: `"pending":[]` and `"drifted":[]`; `applied` may list `001` and `002` on first use.
**Step 3: Run migration status/idempotency again**
Run the same command. Expected: no new application and both pending/drifted remain empty.
**Step 4: Verify database security**
Through bounded catalog queries, prove forced RLS, policies on session tables, runtime role without
BYPASSRLS/ownership/DDL, migrator absent from core, and no runtime privileges on unrelated schemas.
**Step 5: Recheck PostgREST exclusion**
Expected: `thoth_sessions` still absent from exposed schemas and REST endpoints cannot address it.
### Task 7: Validate Authentik and start privately
**Files:**
- Record: Project B protected evidence
**Step 1: Run static configuration validation**
Run `update --check-only` and redacted `auth status --json`. Expected: mode OIDC, exact public origin,
issuer/client ID/group names, and no secret values.
**Step 2: Start while public ingress remains closed**
```bash
<tht> --installation <stable-installation-path> start
<tht> --installation <stable-installation-path> status
<tht> --installation <stable-installation-path> auth check --json
<tht> --installation <stable-installation-path> doctor --json
<tht> --installation <stable-installation-path> pi test
```
Expected: OIDC discovery/issuer/JWKS, client-secret access, Authentik catalog token, exact mapped
groups, PostgreSQL session storage, workspace, services, workflow, and Pi pass.
**Step 3: Run interactive device check when supported**
```bash
<tht> --installation <stable-installation-path> auth check --interactive
```
Expected: approved identity completes Device Authorization and direct groups claim validates. If
the installed provider does not support device flow, record PENDING rather than substituting a token.
### Task 8: Prepare Nginx, TLS, load balancer, and sidebar
**Files:**
- Modify only survey-approved ThothII Nginx fragment
- Modify only survey-approved load-balancer route
- Modify only exact Aritmolab sidebar source when its target must change
**Step 1: Prepare direct-OIDC Nginx configuration**
Follow `docs/install/reverse-proxy-nginx.md`, direct OIDC section. Required behavior: no
`auth_request`, no callback rewrite, frontend loopback upstream, Host and HTTPS forwarded headers,
HTTP/1.1, buffering/cache off, long SSE read timeout, and `X-Accel-Buffering: no`.
**Step 2: Validate the managed certificate**
Expected: SAN matches final hostname, validity is current, chain is trusted by approved clients,
private-key permissions match local policy, and renewal/generation ownership is recorded. Never
copy the key into ThothII.
**Step 3: Validate Nginx without opening traffic**
```bash
sudo nginx -t
curl --fail http://127.0.0.1:<frontend-port>/health
```
Use local `--resolve`/Host tests only when they do not bypass the identity behavior being tested.
**Step 4: Prepare the load-balancer route**
Configure backend/health/TLS according to the surveyed owner procedure, initially disabled or
operator-only. Confirm it targets Nginx, never core/Qdrant/Ollama directly.
**Step 5: Preserve the Aritmolab link contract**
If the existing sidebar target already equals the final origin/path, leave source unchanged and
record proof. Otherwise make the smallest reviewed change, test it in Aritmolab's own test/build
system, and commit in that repository before deployment.
### Task 9: Open ingress and run OIDC acceptance
**Files:**
- Complete: `docs/testing/psd-server-project-b-manual.md`
**Step 1: Reload Nginx through the established mechanism**
Run `nginx -t` immediately before reload. Expected: reload succeeds and unrelated virtual hosts
remain healthy.
**Step 2: Enable the final load-balancer route**
Expected: HTTP redirects to HTTPS; TLS is valid; `/api/auth/oidc/login` redirects to the correct
Authentik provider; callback returns to the exact public origin.
**Step 3: Test ordinary and admin identities**
Start at Aritmolab, authenticate once, then use the sidebar. Expected: no second credential prompt;
ordinary user can use sessions but receives 403 for admin operations; admin has only documented
permissions.
**Step 4: Test no-role and malformed cases**
An identity with no mapped group authenticates but receives no application role/403. Missing,
malformed, indirect, or ambiguous group claims fail closed with generic browser errors and redacted
diagnostics. Do not retain raw claims.
**Step 5: Test logout and restart**
Verify ThothII logout revokes its own cookie. Document whether the Authentik SSO session remains and
therefore allows immediate re-login without credentials; do not claim global logout unless
configured and tested. Restart core and verify expected OIDC session behavior.
### Task 10: Verify PostgreSQL ownership and complete F1-F8
**Files:**
- Create: protected Project B session evidence
**Step 1: Create sessions under two identities**
Expected: ordinary users see only their own sessions; cross-user access returns the documented
not-found boundary; admin behavior matches `session.read_all/manage_all` permissions.
**Step 2: Verify RLS with the runtime path**
Use application/API tests and bounded catalog evidence. Never disable RLS for diagnosis.
**Step 3: Complete one harmless OIDC PSD session**
Use the same approved read-only question or another owner-approved one. Complete F1-F8, validate
final SQL, resume once, and confirm session/artifacts/decisions are stored in `thoth_sessions`.
**Step 4: Verify ephemeral boundaries**
Expected: no chat transcript or SSE stream stored as session artifacts; no vectors in PostgreSQL;
Qdrant remains the semantic store.
### Task 11: Test controlled failures and rollback
**Files:**
- Record: protected rollback evidence
**Step 1: Test a reversible provider/catalog failure**
Use a controlled, owner-approved method such as a temporary disabled test credential or test object.
Expected: auth diagnostics and browser login fail closed, redacted, then pass after restoration.
Never break unrelated Authentik applications.
**Step 2: Rehearse ingress-first rollback**
Close the production route, validate Nginx restoration commands, and prove the protected Project A
configuration snapshot is complete. A full rollback need not destroy `thoth_sessions`.
**Step 3: Verify additive database rollback boundary**
Expected: rollback leaves schema/data intact for evidence and future recovery. No automatic DROP
SCHEMA or role deletion.
### Task 12: Close Project B
**Files:**
- Complete: `docs/testing/evidence/psd-server-project-b-report-template.md`
**Step 1: Run final diagnostics**
Run status, doctor, auth check, interactive check when supported, workspace inspect, vector inspect,
Pi test, Nginx validation, load-balancer health, Supabase migration/security checks, and sidebar test.
**Step 2: Remove the Project A temporary endpoint**
Remove only its load-balancer route, Nginx fragment, and managed certificate reference according to
their owners. Validate/reload and prove the hostname no longer routes.
**Step 3: Complete the human guide and report**
Every mandatory row must be PASS. Record exact source/image/workspace identities, Authentik object
names/IDs, Supabase database plus `thoth_sessions`, public origin, Aritmolab revision, report digest,
and `PROJECT_B_PASS` or `PROJECT_B_FAIL`.
**Step 4: Handle FAIL safely**
On FAIL, close ingress first. Restore Project A files at the same stable paths, move OIDC auth state
to protected evidence, restore the local-auth directory, validate, and start Project A privately.
Disable new Authentik objects; do not delete them or drop the session schema automatically.