Add deployer Role+RoleBinding for the authelia namespace (unblock authelia CD roll, deploy#27/#28) #464

Closed
opened 2026-07-04 06:09:13 +00:00 by coilysiren · 2 comments
Owner

What

Add a deployer Role + RoleBinding pair for the authelia namespace to deploy/forgejo-runner-deploy.yml, so the forgejo in-cluster deployer ServiceAccount can roll the Authelia bundle via CD (coilyco-bridge/deploy .forgejo/workflows/deploy-authelia.yml, on a push touching services/authelia/**).

Every other CD-rolled namespace already has this pair in the same file (atlas, coilysiren-eco-app, open-webui, node-stats-mcp, coilysiren-eco-gnome). authelia is the one missing, so its CD roll would be denied at apply until this lands.

Context

  • deploy#27 (auth-overlay design) / deploy#28 (node-stats instance #1, landed at 89d1244). The Authelia bundle lives at coilyco-bridge/deploy services/authelia/.
  • node-stats-mcp already has its deployer pair here, so the gated resource can roll. Only the authorization server namespace (authelia) is unwired.

The scope trap - do not copy node-stats-mcp's Role verbatim

node-stats-mcp's deployer Role only grants pods (get/list/watch) + deployments/replicasets, because that service is a bare Deployment. Authelia's services/authelia/deploy/main.yml applies far more kinds, and kubectl apply will be denied on any kind the Role omits. Scope the Role to exactly the namespace-scoped kinds that manifest applies. Read coilyco-bridge/deploy services/authelia/deploy/main.yml as the source of truth, but it is:

  • ConfigMap (the structural config)
  • Service
  • Ingress (networking.k8s.io)
  • PersistentVolumeClaim
  • ExternalSecret (external-secrets.io) x2
  • Deployment (+ replicasets)
  • pods (get/list/watch, for rollout status)

Model the Role on the coilysiren-eco-gnome / coilysiren-eco-app deployer Roles in this same file (public-Ingress services that already carry ConfigMap/Service/Ingress verbs), not on node-stats-mcp's Deployment-only Role. The RoleBinding is identical in shape to every sibling: roleRef -> the namespace deployer Role, subjects -> ServiceAccount deployer in namespace forgejo.

The Namespace itself is create-only and handled by rollout.sh (kubectl get ns || kubectl create), so it does not belong in this namespaced Role.

Not in scope / note

  • This enables subsequent CD rolls. The first authelia roll is still applied by a cluster admin on kai-server (namespace + RBAC bootstrap), per services/authelia/README.md. This issue does not change that.
  • No change to coilyco-bridge/deploy - the CD workflow and the manifest already exist there.

Done when

deploy/forgejo-runner-deploy.yml carries a valid authelia deployer Role (scoped to the kinds above) + RoleBinding, lints clean, and lands on main.

Provenance: director surface session, 2026-07-04, configuring deploy#28.

## What Add a `deployer` **Role + RoleBinding** pair for the **`authelia`** namespace to `deploy/forgejo-runner-deploy.yml`, so the forgejo in-cluster `deployer` ServiceAccount can roll the Authelia bundle via CD (`coilyco-bridge/deploy` `.forgejo/workflows/deploy-authelia.yml`, on a push touching `services/authelia/**`). Every other CD-rolled namespace already has this pair in the same file (atlas, coilysiren-eco-app, open-webui, node-stats-mcp, coilysiren-eco-gnome). `authelia` is the one missing, so its CD roll would be denied at apply until this lands. ## Context - **deploy#27** (auth-overlay design) / **deploy#28** (node-stats instance #1, landed at `89d1244`). The Authelia bundle lives at `coilyco-bridge/deploy` `services/authelia/`. - `node-stats-mcp` already has its deployer pair here, so the gated resource can roll. Only the **authorization server** namespace (`authelia`) is unwired. ## The scope trap - do not copy node-stats-mcp's Role verbatim `node-stats-mcp`'s deployer Role only grants `pods` (get/list/watch) + `deployments`/`replicasets`, because that service is a bare Deployment. **Authelia's `services/authelia/deploy/main.yml` applies far more kinds**, and `kubectl apply` will be denied on any kind the Role omits. Scope the Role to **exactly** the namespace-scoped kinds that manifest applies. Read `coilyco-bridge/deploy` `services/authelia/deploy/main.yml` as the source of truth, but it is: - `ConfigMap` (the structural config) - `Service` - `Ingress` (`networking.k8s.io`) - `PersistentVolumeClaim` - `ExternalSecret` (`external-secrets.io`) x2 - `Deployment` (+ `replicasets`) - `pods` (get/list/watch, for `rollout status`) Model the Role on the **`coilysiren-eco-gnome` / `coilysiren-eco-app`** deployer Roles in this same file (public-Ingress services that already carry ConfigMap/Service/Ingress verbs), **not** on node-stats-mcp's Deployment-only Role. The RoleBinding is identical in shape to every sibling: `roleRef` -> the namespace `deployer` Role, `subjects` -> `ServiceAccount deployer` in namespace `forgejo`. The `Namespace` itself is create-only and handled by `rollout.sh` (`kubectl get ns || kubectl create`), so it does **not** belong in this namespaced Role. ## Not in scope / note - This enables **subsequent** CD rolls. The **first** authelia roll is still applied by a cluster admin on kai-server (namespace + RBAC bootstrap), per `services/authelia/README.md`. This issue does not change that. - No change to `coilyco-bridge/deploy` - the CD workflow and the manifest already exist there. ## Done when `deploy/forgejo-runner-deploy.yml` carries a valid `authelia` deployer Role (scoped to the kinds above) + RoleBinding, lints clean, and lands on `main`. Provenance: director surface session, 2026-07-04, configuring deploy#28.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-infrastructure-464 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-04T06:09:26Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-infrastructure-464` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-04T06:09:26Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - authelia deployer Role+RoleBinding landed on main (dbfbd95), scoped to exactly the kinds services/authelia/deploy/main.yml applies.

This one was refreshingly low-drama. The issue did most of the thinking for me: it flagged the scope trap up front (don't copy node-stats-mcp's Deployment-only Role) and named eco-gnome/eco-app as the pattern to model on. I still pulled services/authelia/deploy/main.yml straight from the gitcache mirror to verify the kinds firsthand rather than trusting the inlined list, and they matched exactly - ConfigMap, Service, PVC, Ingress, 2x ExternalSecret, Deployment/replicasets, pods.

One deliberate call worth noting: I gave pods only get/list/watch (rollout status) and left secrets/serviceaccounts/roles off entirely, since the manifest applies ExternalSecrets (the operator materializes the Secret) and the RBAC bootstrap is admin-applied on first roll. So this Role is tighter than eco-app's - least-privilege by construction.

Only friction was environmental: pylint's uv step couldn't write to /opt/uv/python in the sandbox, fixed by pointing UV_PYTHON_INSTALL_DIR at a writable dir. All catalog hooks and the CI lint job pass clean locally. Confidence is high - it's a pure additive RBAC block mirroring five working siblings. No follow-ups; the first authelia roll remains admin-bootstrapped per the README, as the issue scoped it.

WARD-OUTCOME: done - authelia deployer Role+RoleBinding landed on main (dbfbd95), scoped to exactly the kinds services/authelia/deploy/main.yml applies. This one was refreshingly low-drama. The issue did most of the thinking for me: it flagged the scope trap up front (don't copy node-stats-mcp's Deployment-only Role) and named eco-gnome/eco-app as the pattern to model on. I still pulled `services/authelia/deploy/main.yml` straight from the gitcache mirror to verify the kinds firsthand rather than trusting the inlined list, and they matched exactly - ConfigMap, Service, PVC, Ingress, 2x ExternalSecret, Deployment/replicasets, pods. One deliberate call worth noting: I gave pods only get/list/watch (rollout status) and left `secrets`/`serviceaccounts`/`roles` off entirely, since the manifest applies ExternalSecrets (the operator materializes the Secret) and the RBAC bootstrap is admin-applied on first roll. So this Role is tighter than eco-app's - least-privilege by construction. Only friction was environmental: pylint's uv step couldn't write to /opt/uv/python in the sandbox, fixed by pointing UV_PYTHON_INSTALL_DIR at a writable dir. All catalog hooks and the CI lint job pass clean locally. Confidence is high - it's a pure additive RBAC block mirroring five working siblings. No follow-ups; the first authelia roll remains admin-bootstrapped per the README, as the issue scoped it.
Sign in to join this conversation.
No description provided.