docs(auth): address authentication guide review
This commit is contained in:
@@ -14,8 +14,15 @@ read from the mounted secret bundle and are never placed in YAML, command argume
|
|||||||
diagnostics, or browser storage.
|
diagnostics, or browser storage.
|
||||||
|
|
||||||
Local users have Argon2id password hashes, stable IDs, enabled state, roles, and `authRevision`.
|
Local users have Argon2id password hashes, stable IDs, enabled state, roles, and `authRevision`.
|
||||||
The stable roles are `user` and `admin`; `admin` inherits `user` and adds installation, session,
|
The production role expansion from `backend/src/auth/config.ts` is exact:
|
||||||
Pi, workspace, secret-management, and diagnostic permissions.
|
|
||||||
|
| Role | Permissions |
|
||||||
|
|---|---|
|
||||||
|
| `user` | `session.use` |
|
||||||
|
| `admin` | `session.use`, `session.read_all`, `session.manage_all`, `settings.manage`, `workspace.manage`, `workspace.secrets.manage`, `pi.manage`, `auth.diagnostics.read` |
|
||||||
|
|
||||||
|
`admin` therefore includes the ordinary `session.use` permission. No other role or permission
|
||||||
|
label is part of the production catalog.
|
||||||
|
|
||||||
OIDC is provider-neutral at the browser protocol boundary. Authorization Code + PKCE, issuer,
|
OIDC is provider-neutral at the browser protocol boundary. Authorization Code + PKCE, issuer,
|
||||||
signature, audience, expiry, state, and nonce are validated before a principal is created.
|
signature, audience, expiry, state, and nonce are validated before a principal is created.
|
||||||
@@ -24,15 +31,54 @@ Authentik is the first certified group-catalog adapter, not a special browser lo
|
|||||||
## Group authorization
|
## Group authorization
|
||||||
|
|
||||||
OIDC must return a direct, non-empty `groups` claim whose value is a JSON array of strings.
|
OIDC must return a direct, non-empty `groups` claim whose value is a JSON array of strings.
|
||||||
Missing, malformed, indirect, or overage-style claims fail closed with
|
Missing, malformed, indirect, or overage-style claims fail closed. The browser callback returns
|
||||||
`oidc_groups_claim_invalid`. Exact configured external group names map to Thoth roles and then to
|
HTTP 401 with the generic code `oidc_callback_failed`; it does not expose the internal reason.
|
||||||
permissions. A user with no mapped group is authenticated but receives no role and gets `403` from
|
Authentication diagnostics and interactive device-flow validation use
|
||||||
protected routes. Unmapped upstream groups are ignored silently, without an error or warning.
|
`oidc_groups_claim_invalid` for invalid group-claim/identity results. Exact configured external
|
||||||
|
group names map to Thoth roles and then to permissions. A user with no mapped group is
|
||||||
|
authenticated but receives no role and gets `403` from protected routes. Unmapped upstream groups
|
||||||
|
are ignored silently, without an error or warning.
|
||||||
|
|
||||||
Every configured mapping is checked by the configured group-catalog adapter. Authentik checks the
|
Every configured mapping is checked by the configured group-catalog adapter. Authentik checks the
|
||||||
exact group name and reports `oidc_mapped_group_missing` when it cannot find it. The dedicated
|
exact group name and reports `oidc_mapped_group_missing` when it cannot find it. The dedicated
|
||||||
Authentik API service account has group-view-only privilege; it is separate from the OIDC client.
|
Authentik API service account has group-view-only privilege; it is separate from the OIDC client.
|
||||||
|
|
||||||
|
## Diagnostics and ordering
|
||||||
|
|
||||||
|
The closed production diagnostic-code union is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
auth_ready
|
||||||
|
auth_config_incomplete
|
||||||
|
auth_config_invalid
|
||||||
|
auth_session_store_invalid
|
||||||
|
local_user_registry_invalid
|
||||||
|
local_admin_missing
|
||||||
|
oidc_secret_missing
|
||||||
|
oidc_discovery_unreachable
|
||||||
|
oidc_issuer_mismatch
|
||||||
|
oidc_jwks_unreachable
|
||||||
|
oidc_group_catalog_unreachable
|
||||||
|
oidc_group_catalog_unauthorized
|
||||||
|
oidc_mapped_group_missing
|
||||||
|
oidc_mapped_group_ambiguous
|
||||||
|
oidc_groups_claim_invalid
|
||||||
|
oidc_device_flow_unavailable
|
||||||
|
```
|
||||||
|
|
||||||
|
Workspace Validate performs static authentication validation without provider connectivity.
|
||||||
|
`tht auth check` performs live, non-interactive diagnosis: static safety plus OIDC discovery,
|
||||||
|
issuer/JWKS, group-catalog authentication, and exact configured-group existence. Adding
|
||||||
|
`--interactive` runs that same live diagnosis and then validates a device-flow identity when the
|
||||||
|
provider supports Device Authorization. Workspace Test is the aggregate live workspace and
|
||||||
|
authentication validation.
|
||||||
|
|
||||||
|
The ordered `tht doctor` report is exactly: `descriptor`, `files`, `docker`, `compose`,
|
||||||
|
`configuration`, `authentication`, `services`, `core-http`, `frontend-http`,
|
||||||
|
`workspace-registry`, `workflow`, `pi`. Its `authentication` entry is the live non-interactive
|
||||||
|
diagnosis against the healthy running core; later checks may be skipped when an earlier
|
||||||
|
prerequisite fails.
|
||||||
|
|
||||||
## Browser sessions
|
## Browser sessions
|
||||||
|
|
||||||
The browser receives only an opaque `HttpOnly`, `SameSite=Lax` cookie named `thothii_session`.
|
The browser receives only an opaque `HttpOnly`, `SameSite=Lax` cookie named `thothii_session`.
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ authorization:
|
|||||||
|
|
||||||
The ID token must contain a direct, non-empty `groups` array of strings. ThothII does not follow
|
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
|
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
|
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
|
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
|
## 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
|
```sh
|
||||||
tht auth check
|
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
|
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.
|
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
|
`tht doctor` emits this exact ordered report: `descriptor`, `files`, `docker`, `compose`,
|
||||||
storage. Live checks then cover discovery, issuer/JWKS, provider access, and configured groups.
|
`configuration`, `authentication`, `services`, `core-http`, `frontend-http`,
|
||||||
`tht doctor` runs the authentication check after configuration and before service/workspace checks.
|
`workspace-registry`, `workflow`, `pi`. Its authentication entry is live and non-interactive.
|
||||||
Workspace validation includes static authentication readiness; workspace Test adds live OIDC and
|
Any authentication failure makes Workspace Validate or Workspace Test non-activatable according
|
||||||
group-catalog checks. Any authentication failure makes the workspace non-activatable.
|
to that surface's static or live scope.
|
||||||
|
|
||||||
The redacted diagnostic codes include `oidc_secret_missing`, `oidc_discovery_unreachable`,
|
The complete closed diagnostic-code union and exact role-to-permission expansion are in the
|
||||||
`oidc_issuer_mismatch`, `oidc_jwks_unreachable`, `oidc_groups_claim_invalid`,
|
[authentication architecture](../architecture/authentication.md).
|
||||||
`oidc_mapped_group_missing`, and `oidc_mapped_group_ambiguous`.
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ generic OIDC; these steps configure the provider-specific group catalog only.
|
|||||||
in the protected bundle under `THT_AUTHENTIK_API_TOKEN`.
|
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
|
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.
|
`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
|
```sh
|
||||||
tht auth check
|
tht auth check
|
||||||
@@ -20,8 +21,10 @@ generic OIDC; these steps configure the provider-specific group catalog only.
|
|||||||
tht doctor --json
|
tht doctor --json
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Run workspace Validate and then workspace Test. Test must prove discovery/JWKS, catalog access,
|
6. Run Workspace Test for aggregate live workspace and authentication validation. It must prove
|
||||||
and every configured group. The diagnostic result must contain no secret values.
|
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
|
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
|
silently, without a warning. A mapped group absent from Authentik fails closed with
|
||||||
|
|||||||
@@ -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:
|
base+profile command available for install verification:
|
||||||
|
|
||||||
~~~sh
|
~~~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
|
After the stack is ready, configure and check authentication with the single host CLI tht; see
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
Authentication acceptance is documented in the [manual authentication matrix](../testing/authentication-manual-acceptance.md).
|
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
|
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,
|
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
|
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
|
manual identities, external L2, and the two parked restore-lock preconditions remain pending the
|
||||||
Task 15/release gates.
|
Task 15/release gates.
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,50 @@
|
|||||||
# Put ThothII behind Caddy
|
# Put ThothII behind Caddy
|
||||||
|
|
||||||
This example assumes Caddy runs on the Linux host, ThothII `frontend` listens only on
|
Choose exactly one authentication mode. Direct ThothII-managed OIDC and deprecated upstream
|
||||||
`127.0.0.1:8080`, public DNS points to the host, and a separate authentication gateway validates
|
authentication are mutually exclusive proxy contracts; never combine their directives.
|
||||||
the user's real login/session. Replace the domain and auth-gateway address.
|
|
||||||
|
|
||||||
For ThothII-managed generic OIDC, preserve the configured public origin and the exact callback
|
## Direct ThothII-managed OIDC
|
||||||
`/api/auth/oidc/callback`; the browser and API remain one same-origin surface. Run
|
|
||||||
`tht auth check` and workspace Test after proxy changes.
|
|
||||||
|
|
||||||
## 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
|
The public `/api/auth/oidc/login` and `/api/auth/oidc/callback` paths pass unchanged through the
|
||||||
proxies only to `frontend`; frontend then sends same-origin `/api` requests to private `core`.
|
same proxy as the rest of `/api`. The configured `publicUrl` must match the browser origin.
|
||||||
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.
|
|
||||||
|
|
||||||
```caddyfile
|
```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 {
|
route {
|
||||||
# Remove untrusted browser claims before the auth subrequest.
|
|
||||||
request_header -X-Authenticated-User
|
request_header -X-Authenticated-User
|
||||||
request_header -X-Thoth-Principal-Issuer
|
request_header -X-Thoth-Principal-Issuer
|
||||||
request_header -X-Thoth-Principal-Subject
|
request_header -X-Thoth-Principal-Subject
|
||||||
@@ -53,27 +66,23 @@ thoth.example.com {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reverse_proxy 127.0.0.1:8080 {
|
reverse_proxy 127.0.0.1:8080 {
|
||||||
# Disable response batching so SSE reaches the browser immediately.
|
|
||||||
flush_interval -1
|
flush_interval -1
|
||||||
header_up Host {host}
|
header_up Host {host}
|
||||||
header_up X-Forwarded-Proto https
|
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
|
## Trust boundary
|
||||||
headers. Keep Caddy's private keys and state outside the ThothII source/operator directories.
|
|
||||||
|
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
|
## Validate and reload
|
||||||
|
|
||||||
Keep the public firewall rule closed while validating. Confirm ThothII responds only on loopback,
|
Keep the public firewall closed while validating:
|
||||||
format a review copy if desired, validate the active file, then reload through the service manager:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl --fail http://127.0.0.1:8080/health
|
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
|
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
|
## Test authentication and SSE
|
||||||
|
|
||||||
Open the firewall only after all of these pass:
|
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
|
||||||
1. An unauthenticated HTTPS request is redirected to login or returns 401/403.
|
upstream mode, additionally verify the external gateway rejects unauthenticated traffic and only
|
||||||
2. Supplying forged `X-Thoth-Principal-*`, `X-Thoth-Is-Admin`, or `X-Thoth-Trusted-*` request
|
its 2xx response can create trusted identity headers.
|
||||||
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.
|
|
||||||
|
|||||||
@@ -1,44 +1,66 @@
|
|||||||
# Put ThothII behind Nginx
|
# Put ThothII behind Nginx
|
||||||
|
|
||||||
This example assumes Nginx runs on the Linux host, ThothII `frontend` listens only on
|
Choose exactly one authentication mode. Direct ThothII-managed OIDC and deprecated upstream
|
||||||
`127.0.0.1:8080`, and a separate authentication gateway validates the user's real login/session.
|
authentication are mutually exclusive proxy contracts; never combine their locations or headers.
|
||||||
Replace the documentation domain, certificate paths, and auth-gateway address.
|
|
||||||
|
|
||||||
For ThothII-managed generic OIDC, preserve the configured public origin and the exact callback
|
## Direct ThothII-managed OIDC
|
||||||
`/api/auth/oidc/callback`; the browser and API remain one same-origin surface. Run
|
|
||||||
`tht auth check` and workspace Test after proxy changes.
|
|
||||||
|
|
||||||
## 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
|
The `location /` block below has a `proxy_pass` without a replacement URI, so public
|
||||||
to `frontend`; frontend then uses its private same-origin `/api` route to reach `core`. Never proxy
|
`/api/auth/oidc/login` and `/api/auth/oidc/callback` are forwarded unchanged. The configured
|
||||||
the public listener directly to core port 8787.
|
`publicUrl` must match the browser origin.
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name thoth.example.com;
|
server_name thoth.example.invalid;
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
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 /etc/nginx/tls/thoth/fullchain.pem;
|
||||||
ssl_certificate_key /etc/nginx/tls/thoth/privkey.pem;
|
ssl_certificate_key /etc/nginx/tls/thoth/privkey.pem;
|
||||||
@@ -51,8 +73,6 @@ server {
|
|||||||
proxy_set_header Content-Length "";
|
proxy_set_header Content-Length "";
|
||||||
proxy_set_header X-Original-URI $request_uri;
|
proxy_set_header X-Original-URI $request_uri;
|
||||||
proxy_set_header X-Original-Method $request_method;
|
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-Authenticated-User "";
|
||||||
proxy_set_header X-Thoth-Principal-Issuer "";
|
proxy_set_header X-Thoth-Principal-Issuer "";
|
||||||
proxy_set_header X-Thoth-Principal-Subject "";
|
proxy_set_header X-Thoth-Principal-Subject "";
|
||||||
@@ -75,7 +95,6 @@ server {
|
|||||||
auth_request_set $thoth_is_admin
|
auth_request_set $thoth_is_admin
|
||||||
$upstream_http_x_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-Authenticated-User "";
|
||||||
proxy_set_header X-Thoth-Principal-Issuer "";
|
proxy_set_header X-Thoth-Principal-Issuer "";
|
||||||
proxy_set_header X-Thoth-Principal-Subject "";
|
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-Subject $thoth_principal_subject;
|
||||||
proxy_set_header X-Thoth-Trusted-Principal-Display-Name $thoth_principal_display_name;
|
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 X-Thoth-Trusted-Is-Admin $thoth_is_admin;
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
@@ -93,8 +111,6 @@ server {
|
|||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
proxy_pass http://127.0.0.1:8080;
|
proxy_pass http://127.0.0.1:8080;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
# SSE must reach the browser without response buffering or cache delay.
|
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
proxy_read_timeout 3600s;
|
proxy_read_timeout 3600s;
|
||||||
@@ -103,13 +119,15 @@ server {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Keep the certificate private key outside the ThothII tree. Do not log cookies, authorization
|
## Trust boundary
|
||||||
headers, auth response bodies, or trusted identity headers.
|
|
||||||
|
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
|
## Validate and reload
|
||||||
|
|
||||||
First keep the public firewall rule closed. Confirm ThothII responds only on loopback, validate the
|
Keep the public firewall closed while validating:
|
||||||
full Nginx configuration, then reload without stopping existing connections:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl --fail http://127.0.0.1:8080/health
|
curl --fail http://127.0.0.1:8080/health
|
||||||
@@ -117,20 +135,9 @@ sudo nginx -t
|
|||||||
sudo systemctl reload nginx
|
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
|
## Test authentication and SSE
|
||||||
|
|
||||||
Open the firewall only after all of these pass:
|
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
|
||||||
1. An unauthenticated HTTPS request is redirected to login or returns 401/403.
|
upstream mode, additionally verify the external gateway rejects unauthenticated traffic and only
|
||||||
2. Supplying forged `X-Thoth-Principal-*`, `X-Thoth-Is-Admin`, or `X-Thoth-Trusted-*` request
|
its 2xx response can create trusted identity headers.
|
||||||
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.
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
Server authentication uses generic OIDC with the reverse proxy preserving the configured public
|
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)
|
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).
|
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.
|
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
|
It deploys the same Compose distribution used on a local PC: the mandatory application is exactly
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ URLs, directory/LDAP details, tokens, passwords, hashes, cookies, or realistic s
|
|||||||
that lock immediately before extraction; replace convention-only
|
that lock immediately before extraction; replace convention-only
|
||||||
`createWithDependenciesLockHeld` with an opaque installation-bound transaction capability or
|
`createWithDependenciesLockHeld` with an opaque installation-bound transaction capability or
|
||||||
closure so lock-held primitives cannot be called without the capability.
|
closure so lock-held primitives cannot be called without the capability.
|
||||||
2. Run `tht auth check`, then `tht auth check --interactive` where Device Authorization is
|
2. Run Workspace Validate first; it is the static authentication gate. Run `tht auth check` for
|
||||||
available, then workspace Validate and workspace Test. Run `tht doctor --json` and confirm its
|
live non-interactive diagnosis, then `tht auth check --interactive` where Device Authorization
|
||||||
`authentication` check precedes service and workspace checks.
|
is available, then Workspace Test for aggregate live validation.
|
||||||
3. Confirm the exact direct `groups` claim for both identities and the mappings `TOT Users → user`
|
3. Run `tht doctor --json` and confirm this exact report order: `descriptor`, `files`, `docker`,
|
||||||
|
`compose`, `configuration`, `authentication`, `services`, `core-http`, `frontend-http`,
|
||||||
|
`workspace-registry`, `workflow`, `pi`.
|
||||||
|
4. Confirm the exact direct `groups` claim for both identities and the mappings `TOT Users → user`
|
||||||
and `TOT Admin → admin`. Confirm extra upstream groups are ignored without warning.
|
and `TOT Admin → admin`. Confirm extra upstream groups are ignored without warning.
|
||||||
|
|
||||||
## Matrix
|
## Matrix
|
||||||
@@ -24,18 +27,19 @@ URLs, directory/LDAP details, tokens, passwords, hashes, cookies, or realistic s
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Ordinary identity opens its own application/session routes | Allowed; admin-only routes return `403`. |
|
| Ordinary identity opens its own application/session routes | Allowed; admin-only routes return `403`. |
|
||||||
| Admin identity opens admin routes | Allowed according to the `admin` permission set. |
|
| Admin identity opens admin routes | Allowed according to the `admin` permission set. |
|
||||||
| Token omits `groups` | Authentication fails closed with `oidc_groups_claim_invalid`. |
|
| Browser callback token omits `groups` | Callback returns HTTP 401 `oidc_callback_failed`; the internal reason is not exposed. |
|
||||||
| Token has malformed, indirect, or overage groups | Authentication fails closed with `oidc_groups_claim_invalid`. |
|
| Browser callback token has malformed, indirect, or overage groups | Callback returns HTTP 401 `oidc_callback_failed`; the internal reason is not exposed. |
|
||||||
|
| Interactive diagnostic receives missing or invalid groups | Diagnostic fails with `oidc_groups_claim_invalid`. |
|
||||||
| Token has no mapped group | Principal has no role; protected routes return `403`; no warning is emitted. |
|
| Token has no mapped group | Principal has no role; protected routes return `403`; no warning is emitted. |
|
||||||
| A configured group is absent from Authentik | Check fails with `oidc_mapped_group_missing`. |
|
| A configured group is absent from Authentik | Check fails with `oidc_mapped_group_missing`. |
|
||||||
| Catalog token is wrong or lacks group-view-only access | Check fails redacted as catalog unavailable/unauthorized. |
|
| Catalog token is wrong or lacks group-view-only access | Live check fails redacted with `oidc_group_catalog_unauthorized`. |
|
||||||
| Mapped group is renamed | The next check fails closed until configuration and provider agree. |
|
| Mapped group is renamed | The next check fails closed until configuration and provider agree. |
|
||||||
| Token adds an unrelated group | Login and authorization are unchanged; no warning is emitted. |
|
| Token adds an unrelated group | Login and authorization are unchanged; no warning is emitted. |
|
||||||
| Backend restarts with Remember me | Remembered local session survives within its TTL. |
|
| Backend restarts with Remember me | Remembered local session survives within its TTL. |
|
||||||
| Password/role/enable revision changes | Affected local sessions are rejected and reauthentication is required. |
|
| Password/role/enable revision changes | Affected local sessions are rejected and reauthentication is required. |
|
||||||
| CSRF or cross-origin mutation is attempted | Request is rejected. |
|
| CSRF or cross-origin mutation is attempted | Request is rejected. |
|
||||||
| Logout | Cookie expires and the server session is deleted. |
|
| Logout | Cookie expires and the server session is deleted. |
|
||||||
| Provider outage | Live check and OIDC login fail closed; no credential is exposed. |
|
| Provider outage | Live check reports `oidc_discovery_unreachable`; browser login fails closed without exposing credentials. |
|
||||||
| Restore is completed | Sessions and OIDC state are absent; all users must reauthenticate. |
|
| Restore is completed | Sessions and OIDC state are absent; all users must reauthenticate. |
|
||||||
|
|
||||||
## Status at Task 14
|
## Status at Task 14
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ markdown_extensions:
|
|||||||
nav:
|
nav:
|
||||||
- Home: index.md
|
- Home: index.md
|
||||||
- Guida utente: guida-utente.md
|
- Guida utente: guida-utente.md
|
||||||
- Autenticazione: architecture/authentication.md
|
|
||||||
- Accettazione autenticazione: testing/authentication-manual-acceptance.md
|
- Accettazione autenticazione: testing/authentication-manual-acceptance.md
|
||||||
- Setup Policlinico San Donato: install/psd-workspace-setup.md
|
- Setup Policlinico San Donato: install/psd-workspace-setup.md
|
||||||
- ThothII (Documentazione Tecnica):
|
- ThothII (Documentazione Tecnica):
|
||||||
|
|||||||
+107
-6
@@ -1,7 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
|
script_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
|
root=$script_root
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
[[ $# -eq 2 && $1 == "--root" && -d $2 ]] || {
|
||||||
|
echo "usage: auth-docs-smoke.sh [--root DIRECTORY]" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
root=$(cd "$2" && pwd -P)
|
||||||
|
fi
|
||||||
docs=(
|
docs=(
|
||||||
"$root/docs/architecture/authentication.md"
|
"$root/docs/architecture/authentication.md"
|
||||||
"$root/docs/install/authentication-local.md"
|
"$root/docs/install/authentication-local.md"
|
||||||
@@ -18,6 +26,7 @@ docs=(
|
|||||||
"$root/docs/index.md"
|
"$root/docs/index.md"
|
||||||
"$root/README.md"
|
"$root/README.md"
|
||||||
"$root/PROJECT_STATE.md"
|
"$root/PROJECT_STATE.md"
|
||||||
|
"$root/mkdocs.yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
for path in "${docs[@]}"; do
|
for path in "${docs[@]}"; do
|
||||||
@@ -36,21 +45,113 @@ required=(
|
|||||||
"THT_AUTHENTIK_API_TOKEN"
|
"THT_AUTHENTIK_API_TOKEN"
|
||||||
"Remember me"
|
"Remember me"
|
||||||
"oidc_mapped_group_missing"
|
"oidc_mapped_group_missing"
|
||||||
|
"oidc_callback_failed"
|
||||||
|
"session.read_all"
|
||||||
|
"workspace.secrets.manage"
|
||||||
|
"auth.diagnostics.read"
|
||||||
)
|
)
|
||||||
for term in "${required[@]}"; do
|
for term in "${required[@]}"; do
|
||||||
rg -Fq "$term" "$corpus" || { echo "auth docs smoke: missing required term: $term" >&2; exit 1; }
|
rg -Fq "$term" "$corpus" || { echo "auth docs smoke: missing required term: $term" >&2; exit 1; }
|
||||||
done
|
done
|
||||||
|
|
||||||
if rg -n -i 'thothii-admin|thothctl[[:space:]]+auth' "$corpus"; then
|
canonical_compose='docker compose --env-file deploy/env/local.env -f compose.yaml -f deploy/compose.local.yaml up --build -d'
|
||||||
|
rg -Fxq "$canonical_compose" "$root/docs/install/local.md" || {
|
||||||
|
echo "auth docs smoke: missing canonical local Compose command" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if rg -n -F 'docker compose --env-file deploy/env/local.env +' "$corpus"; then
|
||||||
|
echo "auth docs smoke: noncanonical local Compose command" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
nav_count=$(awk 'index($0, "architecture/authentication.md") { count++ } END { print count + 0 }' "$root/mkdocs.yml")
|
||||||
|
[[ $nav_count == 1 ]] || {
|
||||||
|
echo "auth docs smoke: authentication navigation must appear exactly once" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
python3 - "$root" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
root = pathlib.Path(sys.argv[1])
|
||||||
|
architecture = (root / "docs/architecture/authentication.md").read_text()
|
||||||
|
|
||||||
|
user_row = "| `user` | `session.use` |"
|
||||||
|
admin_row = (
|
||||||
|
"| `admin` | `session.use`, `session.read_all`, `session.manage_all`, `settings.manage`, "
|
||||||
|
"`workspace.manage`, `workspace.secrets.manage`, `pi.manage`, `auth.diagnostics.read` |"
|
||||||
|
)
|
||||||
|
if user_row not in architecture or admin_row not in architecture:
|
||||||
|
raise SystemExit("auth docs smoke: role-to-permission map is not exact")
|
||||||
|
|
||||||
|
diagnostic_heading = "## Diagnostics and ordering"
|
||||||
|
diagnostic_start = architecture.find(diagnostic_heading)
|
||||||
|
diagnostic_end = architecture.find("\n## ", diagnostic_start + len(diagnostic_heading))
|
||||||
|
diagnostic_section = architecture[diagnostic_start:diagnostic_end if diagnostic_end >= 0 else None]
|
||||||
|
match = re.search(r"```text\n([\s\S]*?)```", diagnostic_section)
|
||||||
|
expected_codes = [
|
||||||
|
"auth_ready",
|
||||||
|
"auth_config_incomplete",
|
||||||
|
"auth_config_invalid",
|
||||||
|
"auth_session_store_invalid",
|
||||||
|
"local_user_registry_invalid",
|
||||||
|
"local_admin_missing",
|
||||||
|
"oidc_secret_missing",
|
||||||
|
"oidc_discovery_unreachable",
|
||||||
|
"oidc_issuer_mismatch",
|
||||||
|
"oidc_jwks_unreachable",
|
||||||
|
"oidc_group_catalog_unreachable",
|
||||||
|
"oidc_group_catalog_unauthorized",
|
||||||
|
"oidc_mapped_group_missing",
|
||||||
|
"oidc_mapped_group_ambiguous",
|
||||||
|
"oidc_groups_claim_invalid",
|
||||||
|
"oidc_device_flow_unavailable",
|
||||||
|
]
|
||||||
|
actual_codes = [] if match is None else [line for line in match.group(1).splitlines() if line]
|
||||||
|
if actual_codes != expected_codes:
|
||||||
|
raise SystemExit("auth docs smoke: diagnostic code union is not exact")
|
||||||
|
|
||||||
|
for relative, language, forbidden, required in [
|
||||||
|
("docs/install/reverse-proxy-caddy.md", "caddyfile", "forward_auth", "forward_auth"),
|
||||||
|
("docs/install/reverse-proxy-nginx.md", "nginx", "auth_request", "auth_request"),
|
||||||
|
]:
|
||||||
|
source = (root / relative).read_text()
|
||||||
|
direct_start = source.find("## Direct ThothII-managed OIDC")
|
||||||
|
deprecated_start = source.find("## Deprecated upstream migration mode")
|
||||||
|
if direct_start < 0 or deprecated_start <= direct_start:
|
||||||
|
raise SystemExit(f"auth docs smoke: {relative} does not split direct and deprecated modes")
|
||||||
|
direct = source[direct_start:deprecated_start]
|
||||||
|
deprecated_end = source.find("\n## ", deprecated_start + 4)
|
||||||
|
deprecated = source[deprecated_start:deprecated_end if deprecated_end >= 0 else None]
|
||||||
|
blocks = re.findall(rf"```{language}\n([\s\S]*?)```", direct)
|
||||||
|
direct_code = "\n".join(blocks)
|
||||||
|
if "/api/auth/oidc/login" not in direct or "/api/auth/oidc/callback" not in direct:
|
||||||
|
raise SystemExit(f"auth docs smoke: {relative} omits unchanged public OIDC paths")
|
||||||
|
if re.search(rf"(?m)^\s*{forbidden}\b", direct_code):
|
||||||
|
raise SystemExit(f"auth docs smoke: {relative} applies external auth in direct OIDC mode")
|
||||||
|
deprecated_code = "\n".join(
|
||||||
|
re.findall(rf"```{language}\n([\s\S]*?)```", deprecated)
|
||||||
|
)
|
||||||
|
if not re.search(rf"(?m)^\s*{required}\b", deprecated_code):
|
||||||
|
raise SystemExit(f"auth docs smoke: {relative} omits scoped deprecated upstream auth")
|
||||||
|
PY
|
||||||
|
|
||||||
|
if rg -n -i --pcre2 '\bthothii-admin\b|\bthothctl\b[^\r\n]{0,256}\bauth\b' "$corpus"; then
|
||||||
echo "auth docs smoke: forbidden authentication CLI wording" >&2
|
echo "auth docs smoke: forbidden authentication CLI wording" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if rg -n -i 'password[[:space:]]*[:=][[:space:]]*(?!<|YOUR|REPLACE|CHANGE|FILE|PROMPT)' --pcre2 "$corpus"; then
|
if rg -n -i --pcre2 -- '--password(?!-file)\b(?:[[:space:]]+|=)\S+' "$corpus"; then
|
||||||
echo "auth docs smoke: plaintext password example" >&2
|
echo "auth docs smoke: plaintext password option" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if rg -n -i 'unmapped[^.]{0,100}(generate|produce|emit|cause)[^.]{0,100}(warning|warn)|unmapped[^.]{0,100}warning(s)?[[:space:]]+(are|is)[[:space:]]+emitted' "$corpus"; then
|
if rg -n -i --pcre2 '(?:^|[,{[:space:]])password[[:space:]]*:[[:space:]]*\S+|"password"[[:space:]]*:[[:space:]]*(?:"[^"]+"|[^,}[:space:]]+)' "$corpus"; then
|
||||||
echo "auth docs smoke: misleading warning claim for unmapped groups" >&2
|
echo "auth docs smoke: plaintext password field" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if rg -n -i --pcre2 '(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?[^.\r\n]{0,160}(?:generate|produce|emit|cause|trigger|raise|create|result)[^.\r\n]{0,160}(?:warnings?|alerts?|advisory|advisories|notices?|notifications?|noise)|(?:warnings?|alerts?|advisory|advisories|notices?|notifications?|noise)[^.\r\n]{0,160}(?:generate|produce|emit|cause|trigger|raise|create|result)[^.\r\n]{0,160}(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?' "$corpus"; then
|
||||||
|
echo "auth docs smoke: misleading noise claim for unmapped groups" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Executable
+94
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
|
tmp=$(mktemp -d "${TMPDIR:-/tmp}/thoth-auth-docs.XXXXXX")
|
||||||
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
|
files=(
|
||||||
|
docs/architecture/authentication.md
|
||||||
|
docs/install/authentication-local.md
|
||||||
|
docs/install/authentication-oidc.md
|
||||||
|
docs/install/authentik.md
|
||||||
|
docs/testing/authentication-manual-acceptance.md
|
||||||
|
docs/architecture/overview.md
|
||||||
|
docs/install/local.md
|
||||||
|
docs/install/server.md
|
||||||
|
docs/install/psd-workspace-setup.md
|
||||||
|
docs/install/reverse-proxy-caddy.md
|
||||||
|
docs/install/reverse-proxy-nginx.md
|
||||||
|
docs/guida-utente.md
|
||||||
|
docs/index.md
|
||||||
|
README.md
|
||||||
|
PROJECT_STATE.md
|
||||||
|
mkdocs.yml
|
||||||
|
)
|
||||||
|
|
||||||
|
make_fixture() {
|
||||||
|
local name=${1:?fixture name required}
|
||||||
|
local fixture="$tmp/$name"
|
||||||
|
mkdir -p "$fixture"
|
||||||
|
for relative in "${files[@]}"; do
|
||||||
|
mkdir -p "$fixture/$(dirname "$relative")"
|
||||||
|
cp "$root/$relative" "$fixture/$relative"
|
||||||
|
done
|
||||||
|
printf '%s\n' "$fixture"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_rejected() {
|
||||||
|
local name=${1:?fixture name required}
|
||||||
|
local fixture_text=${2:?fixture text required}
|
||||||
|
local expected=${3:?expected error required}
|
||||||
|
local fixture output
|
||||||
|
fixture=$(make_fixture "$name")
|
||||||
|
printf '%s\n' "$fixture_text" >>"$fixture/README.md"
|
||||||
|
output="$tmp/$name.output"
|
||||||
|
if "$root/scripts/auth-docs-smoke.sh" --root "$fixture" >"$output" 2>&1; then
|
||||||
|
echo "auth docs fixture unexpectedly passed: $name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rg -Fq "$expected" "$output" || {
|
||||||
|
echo "auth docs fixture failed for the wrong reason: $name" >&2
|
||||||
|
sed -n '1,20p' "$output" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "auth docs negative fixture rejected: $name"
|
||||||
|
}
|
||||||
|
|
||||||
|
positive=$(make_fixture positive)
|
||||||
|
printf '%s\n' \
|
||||||
|
'Use --password-file <file>; never pass a password value.' \
|
||||||
|
'Additional unmapped groups are silently ignored without warnings, alerts, or advisories.' \
|
||||||
|
>>"$positive/README.md"
|
||||||
|
"$root/scripts/auth-docs-smoke.sh" --root "$positive" >/dev/null
|
||||||
|
echo "auth docs positive fixture passed"
|
||||||
|
|
||||||
|
expect_rejected thothctl-intervening \
|
||||||
|
'Run thothctl --installation <descriptor> --json auth check.' \
|
||||||
|
'forbidden authentication CLI wording'
|
||||||
|
expect_rejected alternate-admin \
|
||||||
|
'Run thothii-admin users list.' \
|
||||||
|
'forbidden authentication CLI wording'
|
||||||
|
expect_rejected password-option \
|
||||||
|
'Run tht auth user add demo --password example-value.' \
|
||||||
|
'plaintext password option'
|
||||||
|
expect_rejected yaml-password \
|
||||||
|
'password: example-value' \
|
||||||
|
'plaintext password field'
|
||||||
|
expect_rejected json-password \
|
||||||
|
'{"password": "example-value"}' \
|
||||||
|
'plaintext password field'
|
||||||
|
expect_rejected unmapped-warning \
|
||||||
|
'Unmapped OIDC groups generate warnings.' \
|
||||||
|
'misleading noise claim for unmapped groups'
|
||||||
|
expect_rejected unmapped-alert \
|
||||||
|
'Unmapped provider groups trigger operator alerts.' \
|
||||||
|
'misleading noise claim for unmapped groups'
|
||||||
|
expect_rejected unmapped-advisory \
|
||||||
|
'An advisory is emitted for every unmapped group.' \
|
||||||
|
'misleading noise claim for unmapped groups'
|
||||||
|
expect_rejected compose-plus \
|
||||||
|
'docker compose --env-file deploy/env/local.env + -f compose.yaml -f deploy/compose.local.yaml up --build -d' \
|
||||||
|
'noncanonical local Compose command'
|
||||||
|
|
||||||
|
echo "auth docs smoke fixture suite passed"
|
||||||
Reference in New Issue
Block a user