key-cape/workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md
tegwick 2ab70924a1 chore(consistency): register KEY-WP-0026 [auto]
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713576@bnt-lap001
Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-08 09:52:42 +02:00

123 lines
4.5 KiB
Markdown

---
id: KEY-WP-0026
type: workplan
title: "Reconcile packaging, bootstrap and migration credential handling"
domain: infotech
repo: key-cape
status: finished
owner: claude
topic_slug: packaging-bootstrap-and-credential-handling
created: "2026-09-08"
updated: "2026-09-08"
state_hub_workstream_id: "d2b04d59-41b1-531b-8cab-a980328611b4"
---
Closes gap G09. Five separate defects, only loosely related: the dev stack could
not start from a clean checkout, the image shipped one binary of five, the
publish workflow named a different registry than the one the cluster pulls from,
the exporter took a password on argv, and every migration artifact was written
world-readable.
## Take the bind password off argv
```task
id: KEY-WP-0026-T01
status: done
priority: medium
state_hub_task_id: "510a4603-337f-5ceb-b6b7-b9126d8605f0"
```
`lldap-export` took `--bind-pw`, so the service account password was visible to
any local user through `ps`, and captured by shell history and process
accounting. It now prefers `KEYCAPE_LLDAP_BIND_PW`, accepts `--bind-pw-file`, and
keeps `--bind-pw` working with a warning — deprecated rather than removed,
because existing runbooks use it and silently breaking them would be worse than
the exposure for one more cycle.
Conflicting sources are rejected rather than silently ranked: with two supplied,
an operator cannot tell which bind was attempted. A password file has its
trailing newline trimmed, since editors and heredocs add one and binding with it
fails confusingly. Both migration scripts now pass the password by environment.
## Stop writing migration artifacts world-readable
```task
id: KEY-WP-0026-T02
status: done
priority: medium
state_hub_task_id: "679e21a1-e37b-574b-bc25-b9fd46bdfd16"
```
The canonical export, the generated LDIF and the Keycloak realm were all written
`0644`. None carries credential material, but the snapshot is a directory dump —
every username, display name, email and group membership in the estate — and it
tended to land in `/tmp`. All three are `0600` now.
## Ship every binary in the image
```task
id: KEY-WP-0026-T03
status: done
priority: medium
state_hub_task_id: "5252a13c-cd5d-5686-a7f5-04587d7d9b1d"
```
The image packaged `keycape` alone, so the validator and the three migration
binaries needed a Go toolchain on the host — which defeats shipping an image for
the cutover work they exist to support. All five are built and copied; the issuer
remains the entrypoint and the rest are reachable by overriding it. Verified by
building the image and running each binary inside it.
## Reconcile the publish target
```task
id: KEY-WP-0026-T04
status: done
priority: medium
state_hub_task_id: "45b8c386-41bd-514c-9247-5eba9663d8fb"
```
The workflow published to `92.205.130.254:32166/coulomb/key-cape` while the
cluster runs `forgejo.coulomb.social/coulomb/key-cape` (KEY-WP-0013). Whether
those are one registry behind two names or two registries is not determinable
from this repository, which is exactly the problem: a push could not be assumed
to have deployed the current source. Now defaults to the recorded Forgejo name
and stays overridable through a repository variable.
**Unverified, and needs an operator's eye:** this repository cannot test that the
runner resolves that hostname or that `REGISTRY_USER`/`REGISTRY_TOKEN` are valid
for it. If the next publish fails, set the `REGISTRY` repository variable back to
the address.
## Make the dev stack bootstrappable
```task
id: KEY-WP-0026-T05
status: done
priority: medium
state_hub_task_id: "a750ac2f-cad2-5e7f-a505-2ca6b94f1a5a"
```
`docker-compose.dev.yml` mounts `config/dev-key.pem` and `config/authelia/`,
neither in the checkout — correctly, since one is a private key and the other
carries a password hash. `scripts/bootstrap-dev.sh` generates both locally: an
RSA key created under `umask 077` rather than chmodded afterwards, so it is never
briefly world-readable, plus an Authelia configuration, its OIDC signing key and
a user fixture. Everything it writes is git-ignored, and re-running it leaves
existing material alone.
The fixture password is published in the script deliberately: a reader can see
exactly what it unlocks, which is nothing deployed.
## Reconcile the records
```task
id: KEY-WP-0026-T06
status: done
priority: medium
state_hub_task_id: "cc7c86ae-9c78-5422-8f3c-19441ceeb513"
```
`SCOPE.md` and G09's status record what is fixed and what is not: the registry
change is unverified from here, and a documented external bootstrap remains the
route for anything beyond development.