yau-plant-assistant/authelia/access-rules.md
Claude 16c4bba3bd Mark cube.yokogawa.tech as the hostname nothing depends on
Kept, but labelled. ai-api reaches Cube as cube:4000 over the proxy network and
never by name, and the operator UI never touches Cube - the hostname exists so
an engineer can open the playground to hand-verify a measure, which a tunnel
does equally well. So it is the first one to drop if this host is being tidied
or the name is wanted elsewhere.

The note is in three places because whoever tidies this host will be reading
one of them and not this repo: above the block in ~/Caddyfile, inline on the
Authelia rule, and here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 15:44:47 +10:00

142 lines
6.5 KiB
Markdown

# Authelia access rules — the additions, as text
**This file is documentation, not configuration. Never commit the real
`~/authelia/configuration.yml`, and never generate a replacement for it.**
It contains the AD bind account, session secrets and the Duo integration for
every service on the host. The four hostnames below are the only part of it
this project touches.
## What has to change
Four hostnames join the existing `HTTPS_UserAccess` `two_factor` rule in
`~/authelia/configuration.yml`, under `access_control.rules`:
| Hostname | Phase | Serves |
|---|---|---|
| `lf.yokogawa.tech` | 2 | Langfuse — traces, prompts, eval runs |
| `cube.yokogawa.tech` | 5 | Cube semantic layer, playground and REST API — **not essential**, see below |
| `api.yokogawa.tech` | 6 | `ai-api` FastAPI |
| `ai.yokogawa.tech` | 7 | `ai-web` operator UI |
Phase 9 adds no hostname. It adds one **path-scoped rule** on an existing one —
see "Phase 9: the document publisher rule" below.
The shape of the addition — the domain list on the existing trailing rule gains
these entries, the policy and subject stay exactly as they already are:
```yaml
access_control:
rules:
# ... existing rules unchanged ...
- domain:
# ... existing domains unchanged ...
- lf.yokogawa.tech # added <date>, AI PoC Phase 2
- cube.yokogawa.tech # added <date>, AI PoC Phase 5
- api.yokogawa.tech # added <date>, AI PoC Phase 6
- ai.yokogawa.tech # added <date>, AI PoC Phase 7
policy: two_factor
subject:
- group:HTTPS_UserAccess
```
Add each hostname at the phase that needs it. Every domain added here must also
have a Caddyfile block with `import authelia` (`caddy/ai-routes.caddy`), and
every Caddyfile block must have a rule here. One without the other is a hole.
**`cube.yokogawa.tech` is the one to drop first** if the host is being tidied or the
hostname is wanted for something else. Nothing depends on it — `ai-api` reaches Cube as
`cube:4000` over the proxy network and never by name, and the operator UI never touches
Cube. It exists so an engineer can open the playground to hand-verify a measure, which an
SSH tunnel does equally well. Removing it means deleting the Caddy block **and** this
domain; batch that with an Authelia restart that is happening anyway rather than spending
an all-user logout on it. The same note is on the host, in `~/Caddyfile` above the block
and inline on this rule.
**Applied on lin001:** `lf` on 2026-08-20; `cube`, `api` and `ai` on
2026-08-27, in one Authelia restart. Backup
`~/authelia/configuration.yml.bak-ai-20260827`. All four now return 302 to the
auth portal. Between the Caddy blocks going in and this rule landing, the three
new hostnames returned **403** — Caddy serving a hostname Authelia has no rule
for means `default_policy: deny`, which is the safe direction but is not
obvious from the status code alone. `verify.sh` names that case explicitly.
## Phase 9: the document publisher rule
Operator document upload splits the API in two. Anyone in `HTTPS_UserAccess`
may ask a question and upload a document for review. **Approving** a document —
which is what makes it citable, and what decides whether a superseded revision
stops being citable — needs a second group, `AI_DocPublishers`.
```yaml
access_control:
rules:
# ... existing rules unchanged ...
# MUST come BEFORE the general api.yokogawa.tech rule. Authelia applies the
# FIRST matching rule and stops. Below it, this rule is dead and every
# authenticated user can approve a procedure revision.
- domain: api.yokogawa.tech
resources:
- '^/docs/.*'
policy: two_factor
subject:
- group:AI_DocPublishers # added <date>, AI PoC Phase 9
# ... the existing trailing rule, unchanged, still carries
# api.yokogawa.tech for everyone in HTTPS_UserAccess ...
```
Three things about this rule specifically:
- **`AI_DocPublishers` must be created in AD with DIRECT membership.** The same
trap as `HTTPS_UserAccess`: a user inside a nested group is silently denied,
with no useful log line. `svc-authelia` is read-only and cannot fix it.
- **It is not the only check.** `ai-api` re-reads `Remote-Groups` and returns
403 on approve, reject and supersede. This rule protects the whole `/docs`
path; the API protects the three operations that matter, and survives this
rule being reordered or dropped in a future edit of a file nobody diffs.
- **Verify it by calling the API directly**, as a user who is authenticated but
not a publisher. The UI hides the buttons from that user, which proves
nothing at all:
```bash
curl -si https://api.yokogawa.tech/docs/uploads/<id>/approve -X POST -H 'Content-Type: application/json' -d '{}' # expect 403
```
## How to apply it
```bash
# 1. Back up first. There are plenty of .bak-* precedents on the host.
sudo cp ~/authelia/configuration.yml ~/authelia/configuration.yml.bak-ai-$(date +%Y%m%d)
# 2. Edit with sudo - the file is root-owned. ~/apply_rule.py rewrites the
# trailing rule if you prefer it to hand-editing.
sudo nano ~/authelia/configuration.yml
# 3. ANNOUNCE FIRST - this logs out every active user on every service.
docker compose -f ~/authelia-compose.yml restart authelia
# 4. Verify. "Up" is not proof.
curl -sI https://ai.yokogawa.tech # expect 302 -> auth portal
docker logs --tail 50 authelia
```
## Things that bite
- **Restarting Authelia logs out every active user on the host**, including
whoever is mid-demo on Grafana. Announce it, and batch the domain additions
so you restart once per phase rather than once per hostname.
- **AD group membership must be DIRECT.** Authelia resolves direct membership
only; a user inside a nested group silently gets denied with no useful log
line. Before Phase 7, confirm the demo operator account is a direct member of
`HTTPS_UserAccess` and is Duo-enrolled. `svc-authelia` is read-only and
cannot fix membership for you.
- **Rule order is a security control, not a style choice.** First match wins.
A path-scoped rule placed after the domain rule it narrows is inert, and
nothing warns you — the service keeps working, for everybody.
- **A missing rule fails open at the wrong layer.** Caddy will happily serve a
hostname that has `import authelia` before the rule exists — Authelia then
applies its default policy. Add the rule in the same change as the Caddyfile
block and verify the 302 before telling anyone the URL.
- Restarting Authelia is also the supported way to refresh someone's group
membership after an AD change.