Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
---
|
|
|
|
|
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"
|
2026-09-08 09:52:42 +02:00
|
|
|
state_hub_workstream_id: "d2b04d59-41b1-531b-8cab-a980328611b4"
|
Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
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
|
2026-09-08 09:52:42 +02:00
|
|
|
state_hub_task_id: "510a4603-337f-5ceb-b6b7-b9126d8605f0"
|
Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`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
|
2026-09-08 09:52:42 +02:00
|
|
|
state_hub_task_id: "679e21a1-e37b-574b-bc25-b9fd46bdfd16"
|
Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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
|
2026-09-08 09:52:42 +02:00
|
|
|
state_hub_task_id: "5252a13c-cd5d-5686-a7f5-04587d7d9b1d"
|
Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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
|
2026-09-08 09:52:42 +02:00
|
|
|
state_hub_task_id: "45b8c386-41bd-514c-9247-5eba9663d8fb"
|
Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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
|
2026-09-08 09:52:42 +02:00
|
|
|
state_hub_task_id: "a750ac2f-cad2-5e7f-a505-2ca6b94f1a5a"
|
Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`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
|
2026-09-08 09:52:42 +02:00
|
|
|
state_hub_task_id: "cc7c86ae-9c78-5422-8f3c-19441ceeb513"
|
Reconcile packaging, bootstrap and migration credential handling
Closes gap G09: five loosely related defects.
lldap-export took the service account password on argv, where ps exposes it to
any local user and shell history and process accounting capture it. It now
prefers KEYCAPE_LLDAP_BIND_PW or --bind-pw-file; --bind-pw still works but warns,
deprecated rather than removed because existing runbooks use it and breaking them
silently would be worse than one more cycle of exposure. Conflicting sources are
rejected instead of silently ranked, since an operator otherwise cannot tell
which bind was attempted. Both migration scripts pass the password by
environment now.
The canonical export, generated LDIF and Keycloak realm were written 0644. None
carries credential material, but the snapshot is every username, display name,
email and group membership in the estate, and it tends to land in /tmp. All three
are 0600.
The image packaged keycape alone, so the validator and migration binaries needed
a Go toolchain on the host -- which defeats shipping an image for the cutover
work they exist to support. All five ship; the issuer stays the entrypoint.
Verified by building the image and running each binary inside it.
The publish workflow named 92.205.130.254:32166 while the cluster runs
forgejo.coulomb.social/coulomb/key-cape. It now defaults to the recorded name and
stays overridable by a repository variable. This repository cannot verify that
the runner resolves that hostname or that the registry credentials are valid for
it; if the next publish fails, set the REGISTRY variable back to the address.
docker-compose.dev.yml mounts a private key and Authelia material that are
correctly absent from the checkout. scripts/bootstrap-dev.sh generates them
locally under a restrictive umask rather than chmodding afterwards, so the key is
never briefly world-readable. Everything it writes is git-ignored.
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:51:59 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`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.
|