Skip to content

feat(image): read an SSH catalog with a deploy key - #254

Merged
gusfcarvalho merged 1 commit into
mainfrom
fx/cat-a4-deploy-key-image
Oct 29, 2026
Merged

gusfcarvalho merged 1 commit into
mainfrom
fx/cat-a4-deploy-key-image

Conversation

@ccf-lisa

@ccf-lisa ccf-lisa Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Task: IMPROVEMENTS item 8, deploy-key follow-up (A4): fx/cat-a4-deploy-key-image on main.

Summary

A private catalog repo could already be read with an App installation token (#250) or, on a workstation, over SSH with a deploy key. The image had no SSH client, so the deploy-key path didn't work there. This PR adds the client and pins GitHub's host keys, and documents both ways.

  • Dockerfile, runtime stage: apk add gains openssh-client-default. That package exists on Alpine 3.23 (openssh-client-default-10.2_p1-r0, which pulls in openssh-client-common and openssh-keygen), so the openssh-client fallback wasn't needed. GitHub's host keys get copied from the new deploy/github_known_hosts to /etc/ssh/ssh_known_hosts (COPY --chmod=0644). The image still holds no secrets and fetches nothing at build time.
  • deploy/github_known_hosts: GitHub's ed25519, ECDSA and RSA host keys for github.com, taken from https://api.github.com/meta (ssh_keys) on 2026-10-29. A header comment gives the date and the steps to refresh the file.
  • No serve flag. The key reaches ssh only through git's standard GIT_SSH_COMMAND. The documented form is ssh -i /run/secrets/catalog-deploy-key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=yes.
  • deploy/poc/README.md:
    • §4 adds a "Private repository with a deploy key" option: the SSH URL, a read-only deploy key, a Host alias or GIT_SSH_COMMAND on a workstation, known_hosts, a key with no passphrase or loaded in the agent, and the App-vs-deploy-key trade-off.
    • §Run the image adds an example that mounts the key read-only at /run/secrets/catalog-deploy-key and passes -e GIT_SSH_COMMAND=…, with the key owned by uid 65532 and mode 0600 (install -o 65532 -m 0600, or a Swarm secret's uid/mode).
    • §12's image description now lists the SSH client and the host keys.
  • deploy/poc/.env.example: a commented SSH form of FACTORY_CATALOG_REPO and a commented GIT_SSH_COMMAND=… line. run.sh is unchanged: it already passes any FACTORY_CATALOG_REPO, and it sources .env with allexport, so GIT_SSH_COMMAND reaches serve.
  • Serve usage: one sentence saying an ssh --catalog-repo takes no App flags and is read with git's own SSH configuration.

Does serve log the environment? GIT_SSH_COMMAND

No. Serve never logs its environment. gitsource.git (internal/catalog/gitsource/gitsource.go:301) passes os.Environ() to git and never logs it. The key's path, which is not a secret, can still reach logs through ssh's stderr. When the key is missing or unreadable, ssh prints Warning: Identity file /run/secrets/catalog-deploy-key not accessible: … (checked in the image), and git's stderr becomes the error message (gitsource.go:308-312). That error then reaches:

  • the catalog source unreadable; keeping the live catalog warn line (internal/controllers/catalogsync.go:261);
  • CatalogStatus.SourceError (catalogsync.go:257);
  • the controller's reconcile failed; retrying warn line (internal/controllers/controller.go:234).

The README says so. The key itself is never in any of them.

Host-key fingerprint cross-check

ssh-keygen -lf deploy/github_known_hosts, compared with GitHub's docs page "GitHub's SSH key fingerprints" (docs.github.com/…/githubs-ssh-key-fingerprints) and with api.github.com/meta ssh_key_fingerprints:

Key ssh-keygen -lf Docs page meta API
ED25519 SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU same same
ECDSA SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM same same
RSA (3072) SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s same same

The docs page also lists SHA256:br9IjFspm1vxR3iA35FWE+4VTyz1hYVLIE2t1/CeyWQ as "DSA - closing down". It is left out on purpose: the meta API doesn't publish it, and OpenSSH 10 doesn't support DSA.

Verification

  • make reviewable: passes (generated-code check, lint, go test -race, rules test 66/66, vacuum 100/100). cmd/factory/deploykit_test.go passes, since only commented lines were added to .env.example.
  • gofmt -l on the Go files: clean.
  • docker build -t factory:a4 .: builds.
  • In the image (docker run --rm --entrypoint sh factory:a4 -c …):
    • ssh -V prints OpenSSH_10.2p1, OpenSSL 3.5.9;
    • ssh-keygen -F github.com -f /etc/ssh/ssh_known_hosts finds all three keys (file -rw-r--r-- root root), and ssh-keygen -lf there gives the fingerprints in the table above;
    • timeout 60 … GIT_SSH_COMMAND='ssh -o BatchMode=yes -o StrictHostKeyChecking=yes' git ls-remote git@github.com:ContainerSolutions/factory.git fails in about 1 s with git@github.com: Permission denied (publickey). (exit 128). There is no host-key error and no hang. No key was used, read or printed;
    • factory serve --help shows the new usage sentence.
  • Only Gustavo can check: a real read-only deploy key on compliance-framework/factory-catalog, read by the image. That means mounting the key at /run/secrets/catalog-deploy-key (uid 65532, 0600), passing GIT_SSH_COMMAND as documented and --catalog-repo git@github.com:compliance-framework/factory-catalog.git with no App flags, then confirming that catalog-sync loads it. A workstation run through run.sh with the SSH FACTORY_CATALOG_REPO is also untested here.

For triage

  • CI never builds the dispatcher image on a PR. .github/workflows/ci.yml only builds the kata image (line 138). The root Dockerfile is built only by release-image.yml on a published release, so a broken runtime stage (a renamed Alpine package, a missing deploy/github_known_hosts) would first fail at release time. Proposed fix: a ci.yml job (foundation's .github/) that runs make image and the three in-image checks above (ssh -V, ssh-keygen -F github.com -f /etc/ssh/ssh_known_hosts, and the ls-remote that must end in Permission denied (publickey)).
  • Ownership: poc-rollout has no Owns line in design/workstreams/definitions.md. The Dockerfile is a repo-root file (foundation, definitions.md:132), and deploy/github_known_hosts sits under deploy/, not deploy/poc/. Both are named by the spec, so they are changed here.

🤖 Generated with Claude Code

The runtime stage adds openssh-client-default and installs GitHub's
pinned SSH host keys (deploy/github_known_hosts) as
/etc/ssh/ssh_known_hosts, so an ssh --catalog-repo can be read with a
deploy key passed through GIT_SSH_COMMAND. deploy/poc documents the
deploy-key option next to the App one, and serve's usage says an ssh
--catalog-repo takes no App flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gusfcarvalho gusfcarvalho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ccf-review: COMMENT

no findings.

@gusfcarvalho
gusfcarvalho merged commit b222146 into main Oct 29, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant