Adopt the agent environment orientation for KeyCape's live objects.
Write Secrets with kubectl replace in the rotation script, since apply copies the data into the last-applied annotation. Record in operations.md the live-change rules that apply here and the open finding: four live Secrets carry that annotation, and removing it waits for the founder's go-ahead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 352750@bnt-lap001 Assistant-Session: de41ef1c-2113-4dd2-9b92-f318ffa7f98b
This commit is contained in:
parent
012e695947
commit
51e541c9a1
3 changed files with 50 additions and 4 deletions
|
|
@ -3,6 +3,10 @@
|
||||||
Dev Hub (State Hub API): http://127.0.0.1:8000
|
Dev Hub (State Hub API): http://127.0.0.1:8000
|
||||||
MCP server name in `~/.claude.json`: `dev-hub`
|
MCP server name in `~/.claude.json`: `dev-hub`
|
||||||
|
|
||||||
|
**Before production, credential or GitOps work** read
|
||||||
|
`~/the-custodian/docs/agent-environment-orientation.md`; KeyCape specifics are
|
||||||
|
in `docs/operations.md`, "Before any live change".
|
||||||
|
|
||||||
**Step 1 — Orient**
|
**Step 1 — Orient**
|
||||||
|
|
||||||
Read the offline-safe brief first — it works without a live hub connection:
|
Read the offline-safe brief first — it works without a live hub connection:
|
||||||
|
|
|
||||||
|
|
@ -125,12 +125,42 @@ curl -s -H "Host: auth.coulomb.social" \
|
||||||
| jq -r .issuer
|
| jq -r .issuer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Before any live change
|
||||||
|
|
||||||
|
The estate-wide rules are in
|
||||||
|
`the-custodian/docs/agent-environment-orientation.md` (revision 2026-09-21);
|
||||||
|
read it first. The parts that apply to KeyCape's objects in `sso` and `mfa`:
|
||||||
|
|
||||||
|
- The founder's go-ahead comes first, recorded as
|
||||||
|
`ADMINISTER @ realm:kubernetes/railiance01`, `activation=APPROVED`. Run changes
|
||||||
|
as `ssh railiance01 '…'`. A harness block is a stop signal, not something to
|
||||||
|
route around.
|
||||||
|
- Check CPU headroom before a restart or rollout
|
||||||
|
(`kubectl describe node | grep -A4 "Allocated resources"`). A rolling update
|
||||||
|
starts the new pod before stopping the old one.
|
||||||
|
- Diff first: client-side `kubectl diff`, then `--dry-run=server`.
|
||||||
|
- **Never `kubectl apply` a Secret, and never read one's metadata.** `apply`
|
||||||
|
copies the whole Secret into the `last-applied-configuration` annotation, so
|
||||||
|
even a metadata read prints the signing key and client secrets. Write Secrets
|
||||||
|
with `kubectl replace`. To test for the annotation without printing it:
|
||||||
|
`kubectl get secret <n> -n sso -o go-template='{{ if index .metadata.annotations "kubectl.kubernetes.io/last-applied-configuration" }}HAS-ANNOTATION{{ else }}clean{{ end }}'`.
|
||||||
|
|
||||||
|
**Open finding, 2026-09-23:** `sso/keycape-config`, `sso/authelia-secrets`,
|
||||||
|
`sso/lldap-secrets` and `mfa/privacyidea-config` all carry that annotation. The
|
||||||
|
earlier rotation script wrote them with `apply`; it now uses `replace`. Anyone
|
||||||
|
who can `get` these Secrets could read the data anyway, so this is not a new
|
||||||
|
reader. What it breaks is the assumption that a metadata read is safe. The fix
|
||||||
|
is a live change and waits for the founder's go-ahead:
|
||||||
|
`kubectl annotate secret <n> -n <ns> kubectl.kubernetes.io/last-applied-configuration-`
|
||||||
|
for each of the four. It prints no value and leaves `.data` untouched, so pods
|
||||||
|
need no restart. Re-check with the template above afterwards.
|
||||||
|
|
||||||
## Unreleased fail-closed startup changes (read before the next rollout)
|
## Unreleased fail-closed startup changes (read before the next rollout)
|
||||||
|
|
||||||
The deployed image at the time of writing is
|
The deployed image at the time of writing is
|
||||||
`sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611`, built
|
`sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611`, built
|
||||||
from `dcebd46`. Two changes on `main` postdate it, both affect startup or
|
from `dcebd46`. The changes below on `main` postdate it, all affect startup or
|
||||||
issuance, and **both fail closed**, so they land together on the next rollout and
|
issuance, and **all fail closed**, so they land together on the next rollout and
|
||||||
a mistake in either presents as a refusal rather than as a warning.
|
a mistake in either presents as a refusal rather than as a warning.
|
||||||
|
|
||||||
**1. Browser clients reject service-identity fields (`74b35b6`, KEY-WP-0028).**
|
**1. Browser clients reject service-identity fields (`74b35b6`, KEY-WP-0028).**
|
||||||
|
|
@ -155,7 +185,15 @@ grant today, so nothing changes on rollout; the refusal only becomes reachable
|
||||||
once the approver client is registered. See
|
once the approver client is registered. See
|
||||||
[the tenant contract](tenant-claim-contract.md).
|
[the tenant contract](tenant-claim-contract.md).
|
||||||
|
|
||||||
Neither has been exercised against a running issuer. The honest proof for both is
|
**3. KeyCape enforces login freshness itself (`11ce29a`, KEY-WP-0033).**
|
||||||
|
`prompt=login` is no longer forwarded to Authelia, because 4.38 refuses it for
|
||||||
|
every real login. `prompt=login` and `max_age=0` go upstream as `max_age=10`,
|
||||||
|
and the callback refuses any upstream `auth_time` that misses the requirement,
|
||||||
|
or is absent (`stale_upstream_authentication`). This changes nothing for clients
|
||||||
|
that do not ask for freshness. The proof is a completed fresh-login journey,
|
||||||
|
not a redirect check.
|
||||||
|
|
||||||
|
None of these has been exercised against a running issuer. The honest proof for both is
|
||||||
a live boot with the new binary, which belongs to the attended rollout window and
|
a live boot with the new binary, which belongs to the attended rollout window and
|
||||||
not to a session running against production on its own.
|
not to a session running against production on its own.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -283,8 +283,12 @@ sys.stdout.write(updated)
|
||||||
kubectl create configmap authelia-config -n sso --from-file=configuration.yml="$work_dir/authelia-configuration.updated.yml" --dry-run=client -o yaml |
|
kubectl create configmap authelia-config -n sso --from-file=configuration.yml="$work_dir/authelia-configuration.updated.yml" --dry-run=client -o yaml |
|
||||||
kubectl apply -f - >/dev/null
|
kubectl apply -f - >/dev/null
|
||||||
|
|
||||||
|
# replace, not apply: apply copies the full Secret data into the
|
||||||
|
# last-applied-configuration annotation, where any metadata read prints it.
|
||||||
|
# replace writes only the manifest, which also drops an annotation left by an
|
||||||
|
# earlier apply.
|
||||||
kubectl create secret generic keycape-config -n sso --from-file=config.yaml="$work_dir/config.yaml" --from-file=key.pem="$work_dir/key.pem" --dry-run=client -o yaml |
|
kubectl create secret generic keycape-config -n sso --from-file=config.yaml="$work_dir/config.yaml" --from-file=key.pem="$work_dir/key.pem" --dry-run=client -o yaml |
|
||||||
kubectl apply -f - >/dev/null
|
kubectl replace -f - >/dev/null
|
||||||
|
|
||||||
kubectl rollout restart deployment/authelia deployment/keycape -n sso >/dev/null
|
kubectl rollout restart deployment/authelia deployment/keycape -n sso >/dev/null
|
||||||
kubectl rollout status deployment/authelia -n sso --timeout=180s
|
kubectl rollout status deployment/authelia -n sso --timeout=180s
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue