-
Notifications
You must be signed in to change notification settings - Fork 0
Ephemeral SSH Key Auto-Registration for GitHub UI Verification (v2) #118
Description
Summary
CI/CD attestations signed with ephemeral keys are cryptographically verifiable via auths verify-commit, but GitHub's web UI will not display a "Verified" badge because GitHub does not recognize the ephemeral SSH public key. This is a UX gap that blocks adoption: developers expect green checkmarks in GitHub UI.
Future work (v2): Auto-upload ephemeral SSH public key to GitHub before signing, then auto-delete after signing. This gives the ephemeral key a registered presence on GitHub, allowing commits to show as "Verified" in the UI while maintaining the ephemeral key guarantee (never persisted to CI runner disk, single-use only).
Context
fn-85 (Machine Identity via OIDC, v1) creates ephemeral KERI identities in CI with OIDC token binding. Commits signed with these ephemeral keys are verifiable via:
auths verify-commit --ref HEAD
# ✓ Signature valid
# ✓ OIDC token binding verified (GitHub Actions, commit SHA abc123, actor octocat)
# ✓ Timestamp proof valid (Sigstore TSA)However, in GitHub's web UI:
[Unverified] This commit was signed with an unrecognized key.
Why: GitHub only recognizes SSH keys registered to user accounts or GitHub Apps. The ephemeral key exists only in CI runner memory, never uploaded to GitHub.
Why it matters for adoption: Enterprise users expect visual feedback in GitHub UI. The "Verified" badge is a compliance requirement for many organizations. Without it, the feature is perceived as incomplete even though the cryptographic verification is more secure than GitHub's centralized model.
Proposed Solution (v2)
Extend auths init --profile ci to:
-
Before signing (same job step or automatically before first
auths sign):- Generate ephemeral Ed25519 keypair (already done)
- Extract public key in OpenSSH wire format
- Call GitHub API (like fn-84):
POST /user/ssh_signing_keyswith Bearer token - Store returned key ID in identity metadata
- Log: "Registered ephemeral SSH key abc123def... to GitHub"
-
During signing (existing flow):
- Sign commits/artifacts as normal with ephemeral key
- GitHub recognizes the signature (key is registered)
- Commits appear as "Verified" in UI ✓
-
After job completion (cleanup):
- Delete SSH key from GitHub via API:
DELETE /user/ssh_signing_keys/{key_id} - Log: "Deleted ephemeral SSH key from GitHub"
- Key is gone; cannot be reused
- Delete SSH key from GitHub via API:
Technical Approach
Reuse from fn-84
HttpGitHubSshKeyUploadertrait (already implemented)- Pre-flight GET to detect duplicate keys (idempotent)
- Error handling for 401 (invalid token), 403 (missing scope), 429 (rate limit)
- Exponential backoff with jitter
New Code
- Extend
CiIdentityConfigwithauto_register_ssh_key: boolflag (default: true) - Add CI job lifecycle hooks:
before_signing(): upload SSH keyafter_job(): delete SSH key
- Error handling: non-fatal (don't block signing if upload fails)
- Logging: clear messages on success/failure
Config Example
# .github/workflows/sign.yml
- name: Initialize CI signing identity
run: auths init --profile ci --auto-register-ssh-key
# Auto-uploads ephemeral SSH key, signs commits, then auto-deletes
- name: Sign commit
run: |
git config user.signingKey $(auths key-id)
git commit --allow-empty -m "auto-signed" -S
# Commit shows as Verified in GitHub UI ✓
- name: Cleanup (auto-deletes SSH key)
if: always()
run: auths cleanup --delete-ssh-keysAcceptance Criteria
- Ephemeral SSH public key auto-uploaded to GitHub before signing (idempotent)
- Key ID stored in identity metadata for later cleanup
- Commits signed with key appear as "Verified" in GitHub UI
- SSH key auto-deleted from GitHub after job (via cleanup or on-exit hook)
- Graceful error handling: upload/delete failures do NOT block signing
- Clear logging: "Registered SSH key abc123... to GitHub" and "Deleted SSH key"
- Configuration flag:
--auto-register-ssh-key(default: true) - Works with GitHub App tokens (in addition to personal access tokens)
- Unit tests: mock GitHub API responses for upload/delete
- Integration test: GitHub Actions workflow with real token (requires secret)
- E2E test: commits appear as "Verified" in GitHub UI
Dependencies
- fn-85 (Machine Identity via OIDC) must be shipped first
- fn-84 (SSH key upload to GitHub) already implemented; can reuse code
- GitHub API:
/user/ssh_signing_keysendpoint - On-exit hooks or cleanup command for key deletion
Security Considerations
- Key lifetime: Ephemeral key exists only for the duration of the CI job (5-15 min max)
- Isolation: Key is per-run, not shared across jobs
- Cleanup: Ensure key is deleted even if job fails (use GitHub Actions
if: always()) - Token scope: Requires
write:ssh_signing_keyscope (same as fn-84) - Revocation: If cleanup fails, key remains on GitHub for its lifetime; acceptable risk (short-lived OIDC binding limits blast radius)
Open Questions
-
Should this be automatic (default: true) or opt-in (default: false)?
- Recommendation: Automatic. Most users want the green badge. Opt-out with
--no-auto-register-ssh-keyif paranoid about API calls.
- Recommendation: Automatic. Most users want the green badge. Opt-out with
-
Should we support GitLab and CircleCI auto-registration too (v2+1)?
- Recommendation: Start with GitHub (highest adoption). GitLab/CircleCI can follow.
-
What if the cleanup fails and the key remains on GitHub?
- Mitigation: Key is tied to OIDC binding (short lifetime). Document the risk. Add monitoring/alerting for leaked ephemeral keys.
Related Issues
- Fixes UX gap reported in fn-85 acceptance testing
- Related to fn-84 (SSH key upload patterns)
- Related to fn-85 (Machine Identity via OIDC)
Note: This is future work (v2), not part of fn-85 (v1). v1 ships with cryptographic verification via auths verify-commit. v2 adds the GitHub UI verification badge for enterprise adoption.