docs(auth): address authentication guide review

This commit is contained in:
2026-08-18 03:33:00 +02:00
parent f4f38717e1
commit 91925d64bf
12 changed files with 406 additions and 142 deletions
+21 -10
View File
@@ -51,7 +51,10 @@ authorization:
The ID token must contain a direct, non-empty `groups` array of strings. ThothII does not follow
distributed claims, overage links, or indirect provider expansion. Missing or malformed claims fail
closed with `oidc_groups_claim_invalid`.
closed. A browser callback exposes only HTTP 401 `oidc_callback_failed`; it never reveals whether
the claim was missing, malformed, indirect, or overage-style. The redacted diagnostic and
interactive device-flow contract uses `oidc_groups_claim_invalid` for invalid group-claim or
device-flow identity results.
Configured group names are exact and case-sensitive. The union of matched mappings determines the
Thoth roles. A token with no mapped group is authenticated but has no role and cannot use protected
@@ -61,7 +64,16 @@ provider may authenticate while still failing installation readiness.
## Checks and diagnostics
Run the static check first:
The surfaces have distinct semantics and this order is recommended:
1. Workspace Validate performs static authentication validation without contacting the provider.
2. `tht auth check` performs live, non-interactive authentication diagnosis, including discovery,
issuer/JWKS, catalog credentials, and all configured mapped groups.
3. `tht auth check --interactive` repeats live diagnosis and additionally validates a device-flow
identity and its direct `groups` claim when Device Authorization is available.
4. Workspace Test performs aggregate live workspace and authentication validation.
The live CLI forms are:
```sh
tht auth check
@@ -72,12 +84,11 @@ For a provider that advertises Device Authorization, `tht auth check --interacti
verification URI and one-time user code on the terminal, waits for completion, and validates a
real ID token including `groups`. It is an operator check, not a replacement for browser login.
Static checks cover configuration, secret references, group mappings, file safety, and session
storage. Live checks then cover discovery, issuer/JWKS, provider access, and configured groups.
`tht doctor` runs the authentication check after configuration and before service/workspace checks.
Workspace validation includes static authentication readiness; workspace Test adds live OIDC and
group-catalog checks. Any authentication failure makes the workspace non-activatable.
`tht doctor` emits this exact ordered report: `descriptor`, `files`, `docker`, `compose`,
`configuration`, `authentication`, `services`, `core-http`, `frontend-http`,
`workspace-registry`, `workflow`, `pi`. Its authentication entry is live and non-interactive.
Any authentication failure makes Workspace Validate or Workspace Test non-activatable according
to that surface's static or live scope.
The redacted diagnostic codes include `oidc_secret_missing`, `oidc_discovery_unreachable`,
`oidc_issuer_mismatch`, `oidc_jwks_unreachable`, `oidc_groups_claim_invalid`,
`oidc_mapped_group_missing`, and `oidc_mapped_group_ambiguous`.
The complete closed diagnostic-code union and exact role-to-permission expansion are in the
[authentication architecture](../architecture/authentication.md).
+6 -3
View File
@@ -12,7 +12,8 @@ generic OIDC; these steps configure the provider-specific group catalog only.
in the protected bundle under `THT_AUTHENTIK_API_TOKEN`.
4. Create or confirm the exact groups `TOT Users` and `TOT Admin`. Map them explicitly in
`auth.yaml` to `user` and `admin`, respectively. Keep other upstream groups out of the mapping.
5. Run the static check and then the live interactive check:
5. Run Workspace Validate for static authentication validation. Then run live non-interactive
diagnosis, followed by the optional device-flow identity check:
```sh
tht auth check
@@ -20,8 +21,10 @@ generic OIDC; these steps configure the provider-specific group catalog only.
tht doctor --json
```
6. Run workspace Validate and then workspace Test. Test must prove discovery/JWKS, catalog access,
and every configured group. The diagnostic result must contain no secret values.
6. Run Workspace Test for aggregate live workspace and authentication validation. It must prove
discovery/JWKS, catalog access, and every configured group. The diagnostic result must contain
no secret values. `tht doctor --json` reports `authentication` after `configuration` and before
`services` in its exact ordered checklist.
Only configured exact group names are queried. Additional Authentik or directory groups are ignored
silently, without a warning. A mapped group absent from Authentik fails closed with
+1 -1
View File
@@ -173,7 +173,7 @@ The canonical local Compose smoke uses the base file plus the local profile. Kee
base+profile command available for install verification:
~~~sh
docker compose --env-file deploy/env/local.env + -f compose.yaml -f deploy/compose.local.yaml up --build -d
docker compose --env-file deploy/env/local.env -f compose.yaml -f deploy/compose.local.yaml up --build -d
~~~
After the stack is ready, configure and check authentication with the single host CLI tht; see
+2 -2
View File
@@ -2,8 +2,8 @@
Authentication acceptance is documented in the [manual authentication matrix](../testing/authentication-manual-acceptance.md).
Use generic OIDC with Authentik as the certified group catalog, map only the exact TOT Users and
TOT Admin groups, then run tht auth check, tht auth check --interactive, workspace Validate,
and workspace Test in that order. Browser callback E2E, native Windows execution, approved PSD
TOT Admin groups, then run Workspace Validate, `tht auth check`, `tht auth check --interactive`,
and Workspace Test in that order. Browser callback E2E, native Windows execution, approved PSD
manual identities, external L2, and the two parked restore-lock preconditions remain pending the
Task 15/release gates.
+49 -52
View File
@@ -1,37 +1,50 @@
# Put ThothII behind Caddy
This example assumes Caddy runs on the Linux host, ThothII `frontend` listens only on
`127.0.0.1:8080`, public DNS points to the host, and a separate authentication gateway validates
the user's real login/session. Replace the domain and auth-gateway address.
Choose exactly one authentication mode. Direct ThothII-managed OIDC and deprecated upstream
authentication are mutually exclusive proxy contracts; never combine their directives.
For ThothII-managed generic OIDC, preserve the configured public origin and the exact callback
`/api/auth/oidc/callback`; the browser and API remain one same-origin surface. Run
`tht auth check` and workspace Test after proxy changes.
## Direct ThothII-managed OIDC
## Trust boundary
Use this mode when `auth.yaml` has `mode: oidc`. Caddy terminates TLS and proxies every request to
`frontend`; ThothII performs login, callback validation, session creation, and authorization.
Caddy must not apply `forward_auth` or another external authentication gateway.
Caddy is the only public listener. It provides automatic HTTPS, performs `forward_auth`, and
proxies only to `frontend`; frontend then sends same-origin `/api` requests to private `core`.
Never send the public reverse proxy to core port 8787.
The authentication gateway must return 2xx only after validating a real credential or session.
The ordered `route` below deletes every public/private Thoth identity request header before auth.
Only on auth success does `copy_headers` rename normalized response claims into the private
`X-Thoth-Trusted-*` headers consumed by frontend.
Forwarding identity headers alone does not authenticate a user.
Do not replace the authentication gateway with static `header_up` values, a network allowlist, or
browser-provided identity. The admin claim must come from reviewed identity-provider authorization.
## Example configuration
Save a reviewed site block in the Caddyfile. Caddy obtains and renews TLS certificates for the real
DNS name; use the organization's approved ACME issuer or certificate policy.
The public `/api/auth/oidc/login` and `/api/auth/oidc/callback` paths pass unchanged through the
same proxy as the rest of `/api`. The configured `publicUrl` must match the browser origin.
```caddyfile
thoth.example.com {
thoth.example.invalid {
reverse_proxy 127.0.0.1:8080 {
# No URI rewrite: OIDC login and callback paths reach frontend unchanged.
flush_interval -1
header_up Host {host}
header_up X-Forwarded-Proto https
header_up X-Forwarded-Host {host}
}
log {
output file /var/log/caddy/thoth-access.log
format json
}
}
```
After reload, run Workspace Validate for static validation, `tht auth check` for live,
non-interactive authentication diagnosis, and then Workspace Test for aggregate live validation.
## Deprecated upstream migration mode
Use this section only while the installation explicitly uses deprecated `upstream` mode. Do not
use it with `mode: oidc` or `mode: local`. Here an external authentication gateway owns login and
Caddy applies `forward_auth` before forwarding normalized private identity headers to `frontend`.
Forwarding identity headers alone does not authenticate a user. The authentication gateway returns
2xx only after validating its own credential or session. Clear browser-supplied public and trusted
headers before the subrequest, and map identity only from the successful auth response.
```caddyfile
thoth.example.invalid {
route {
# Remove untrusted browser claims before the auth subrequest.
request_header -X-Authenticated-User
request_header -X-Thoth-Principal-Issuer
request_header -X-Thoth-Principal-Subject
@@ -53,27 +66,23 @@ thoth.example.com {
}
reverse_proxy 127.0.0.1:8080 {
# Disable response batching so SSE reaches the browser immediately.
flush_interval -1
header_up Host {host}
header_up X-Forwarded-Proto https
}
}
log {
output file /var/log/caddy/thoth-access.log
format json
}
}
```
Configure log processing to remove cookies, authorization data, query strings, and identity
headers. Keep Caddy's private keys and state outside the ThothII source/operator directories.
## Trust boundary
Caddy is the only public listener and proxies only to loopback `frontend`, never directly to
`core`. Configure access logs to omit cookies, authorization data, query strings, and identity
headers. Keep Caddy keys and state outside ThothII source and operator directories.
## Validate and reload
Keep the public firewall rule closed while validating. Confirm ThothII responds only on loopback,
format a review copy if desired, validate the active file, then reload through the service manager:
Keep the public firewall closed while validating:
```sh
curl --fail http://127.0.0.1:8080/health
@@ -81,21 +90,9 @@ caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy
```
Do not remove `forward_auth` if validation fails. Correct the Caddy version, adapter syntax,
authentication upstream, DNS, or certificate policy instead.
## Test authentication and SSE
Open the firewall only after all of these pass:
1. An unauthenticated HTTPS request is redirected to login or returns 401/403.
2. Supplying forged `X-Thoth-Principal-*`, `X-Thoth-Is-Admin`, or `X-Thoth-Trusted-*` request
headers does not grant access.
3. A real authenticated non-admin can use the application but cannot open Pi Management.
4. A real authenticated admin can use Pi Management.
5. A browser session receives live model updates without batching; a protected
`curl --no-buffer` request using a real short-lived login cookie is also acceptable. Delete the
cookie jar immediately afterward.
Re-run these checks after changing the identity provider, authentication gateway, Caddy, or
ThothII release.
For direct OIDC, verify the login path redirects to the configured provider, the callback reaches
ThothII unchanged, forged identity headers grant nothing, and SSE is unbuffered. For deprecated
upstream mode, additionally verify the external gateway rejects unauthenticated traffic and only
its 2xx response can create trusted identity headers.
+59 -52
View File
@@ -1,44 +1,66 @@
# Put ThothII behind Nginx
This example assumes Nginx runs on the Linux host, ThothII `frontend` listens only on
`127.0.0.1:8080`, and a separate authentication gateway validates the user's real login/session.
Replace the documentation domain, certificate paths, and auth-gateway address.
Choose exactly one authentication mode. Direct ThothII-managed OIDC and deprecated upstream
authentication are mutually exclusive proxy contracts; never combine their locations or headers.
For ThothII-managed generic OIDC, preserve the configured public origin and the exact callback
`/api/auth/oidc/callback`; the browser and API remain one same-origin surface. Run
`tht auth check` and workspace Test after proxy changes.
## Direct ThothII-managed OIDC
## Trust boundary
Use this mode when `auth.yaml` has `mode: oidc`. Nginx terminates TLS and proxies every request
to loopback `frontend`. ThothII owns OIDC login, callback validation, browser sessions, and
authorization. No external `auth_request` or authentication gateway belongs in this server.
Nginx is the only public listener. It terminates TLS, performs an `auth_request`, and proxies only
to `frontend`; frontend then uses its private same-origin `/api` route to reach `core`. Never proxy
the public listener directly to core port 8787.
The authentication gateway must return 2xx only after validating a real credential or session. It
returns normalized `X-Thoth-Principal-*` and `X-Thoth-Is-Admin` response headers. Nginx clears all
client-supplied public and private-hop identity headers and copies only those successful auth
response values into `X-Thoth-Trusted-*` on the private hop to frontend.
Forwarding identity headers alone does not authenticate a user.
Do not substitute a static header, network allowlist, or client-provided header for the
authentication gateway. The admin value must come from reviewed identity-provider authorization,
not from a username supplied by the browser.
## Example configuration
Install an Nginx build that includes `ngx_http_auth_request_module`. Save a reviewed version of
this server block under the host's Nginx configuration directory:
The `location /` block below has a `proxy_pass` without a replacement URI, so public
`/api/auth/oidc/login` and `/api/auth/oidc/callback` are forwarded unchanged. The configured
`publicUrl` must match the browser origin.
```nginx
server {
listen 80;
server_name thoth.example.com;
server_name thoth.example.invalid;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name thoth.example.com;
server_name thoth.example.invalid;
ssl_certificate /etc/nginx/tls/thoth/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/thoth/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
# No auth_request and no URI rewrite: ThothII receives OIDC paths unchanged.
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Connection "";
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
add_header X-Accel-Buffering no always;
}
}
```
After reload, run Workspace Validate for static validation, `tht auth check` for live,
non-interactive authentication diagnosis, and then Workspace Test for aggregate live validation.
## Deprecated upstream migration mode
Use this section only while the installation explicitly uses deprecated `upstream` mode. Do not
use it with `mode: oidc` or `mode: local`. In this mode an external authentication gateway owns
login, and Nginx applies `auth_request` before forwarding normalized private identity headers.
Forwarding identity headers alone does not authenticate a user. The authentication gateway returns
2xx only after validating its own credential or session.
```nginx
server {
listen 443 ssl;
server_name thoth.example.invalid;
ssl_certificate /etc/nginx/tls/thoth/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/thoth/privkey.pem;
@@ -51,8 +73,6 @@ server {
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
# The auth service derives identity from the real login/session, never these headers.
proxy_set_header X-Authenticated-User "";
proxy_set_header X-Thoth-Principal-Issuer "";
proxy_set_header X-Thoth-Principal-Subject "";
@@ -75,7 +95,6 @@ server {
auth_request_set $thoth_is_admin
$upstream_http_x_thoth_is_admin;
# Discard browser claims. Carry only successful auth-subrequest values to frontend.
proxy_set_header X-Authenticated-User "";
proxy_set_header X-Thoth-Principal-Issuer "";
proxy_set_header X-Thoth-Principal-Subject "";
@@ -85,7 +104,6 @@ server {
proxy_set_header X-Thoth-Trusted-Principal-Subject $thoth_principal_subject;
proxy_set_header X-Thoth-Trusted-Principal-Display-Name $thoth_principal_display_name;
proxy_set_header X-Thoth-Trusted-Is-Admin $thoth_is_admin;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
@@ -93,8 +111,6 @@ server {
proxy_set_header Connection "";
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# SSE must reach the browser without response buffering or cache delay.
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
@@ -103,13 +119,15 @@ server {
}
```
Keep the certificate private key outside the ThothII tree. Do not log cookies, authorization
headers, auth response bodies, or trusted identity headers.
## Trust boundary
Nginx is the only public listener and proxies only to loopback `frontend`, never directly to
`core`. Keep private keys outside the ThothII tree. Do not log cookies, authorization headers,
OIDC callback query values, authentication bodies, or trusted identity headers.
## Validate and reload
First keep the public firewall rule closed. Confirm ThothII responds only on loopback, validate the
full Nginx configuration, then reload without stopping existing connections:
Keep the public firewall closed while validating:
```sh
curl --fail http://127.0.0.1:8080/health
@@ -117,20 +135,9 @@ sudo nginx -t
sudo systemctl reload nginx
```
If `nginx -t` reports that `auth_request` is unknown, install the distribution package/module that
provides it. Do not remove authentication to make the syntax check pass.
## Test authentication and SSE
Open the firewall only after all of these pass:
1. An unauthenticated HTTPS request is redirected to login or returns 401/403.
2. Supplying forged `X-Thoth-Principal-*`, `X-Thoth-Is-Admin`, or `X-Thoth-Trusted-*` request
headers does not grant access.
3. A real authenticated non-admin can use the application but cannot open Pi Management.
4. A real authenticated admin can use Pi Management.
5. A browser session receives live model updates for longer than the default proxy timeout without
batching; a protected `curl --no-buffer` test using a real short-lived login cookie is also
acceptable. Delete the cookie jar immediately afterward.
Re-run these checks after changing the identity provider, auth gateway, Nginx, or ThothII release.
For direct OIDC, verify login redirects to the configured provider, callback traffic reaches
ThothII unchanged, forged identity headers grant nothing, and SSE is unbuffered. For deprecated
upstream mode, additionally verify the external gateway rejects unauthenticated traffic and only
its 2xx response can create trusted identity headers.
+3 -1
View File
@@ -3,7 +3,9 @@
Server authentication uses generic OIDC with the reverse proxy preserving the configured public
origin and callback path. Follow the [OIDC guide](authentication-oidc.md), [Authentik guide](authentik.md)
when applicable, and the [authentication acceptance matrix](../testing/authentication-manual-acceptance.md).
The host authentication CLI is tht; use tht auth check before workspace Validate/Test.
The host authentication CLI is `tht`: Workspace Validate is static, `tht auth check` is live and
non-interactive, `--interactive` adds device-flow identity validation, and Workspace Test is the
aggregate live gate.
This guide is for an installer with basic Linux administration and very basic Docker knowledge.
It deploys the same Compose distribution used on a local PC: the mandatory application is exactly