oidc-ssh-ca

A small SSH certificate authority that issues short-lived OpenSSH user certificates to OIDC-authenticated callers — primarily GitHub Actions.

Instead of storing a long-term SSH private key in GitHub Secrets, a workflow generates an ephemeral key pair on every run, proves its identity with the GitHub OIDC token, and receives a certificate valid for a few minutes. Target servers trust only the CA public key; there are no authorized_keys to distribute, rotate, or clean up after a leak.

GitHub Actions
  │  GitHub OIDC JWT + ephemeral public key
  ▼
oidc-ssh-ca  POST /sign
  │  verify JWT → match policy.yaml → sign in memory
  ▼
short-lived OpenSSH user certificate
  │  ssh / ansible / rsync / scp
  ▼
target servers (trust only the CA public key)

This is not a replacement for Vault, OpenBao, or Teleport. It is a small, single-binary tool that replaces long-lived SSH keys in GitHub Actions with short-lived, OIDC-issued certificates.

Design guarantees

  • No subprocesses, no temporary files, no external SSH tools. All key parsing and certificate signing happens in memory via golang.org/x/crypto/ssh. The CA private key is never written to disk.

  • The policy decides everything. Principals, TTL, extensions, and the certificate key ID are derived from verified JWT claims and policy.yaml. The request body carries only the public key — a caller cannot request a principal or a longer TTL.

  • Deny by default, exactly one match. A request is allowed only if exactly one enabled rule matches. Zero matches deny; multiple matches deny (no first-match-wins surprises). A claim referenced by a rule but absent from the token denies.

  • Fail safe. An invalid policy prevents startup; a failed reload keeps the previous policy; a JWKS outage without cached keys denies.

  • Generic errors. Callers get a fixed message and a request ID. Denial reasons and details go only to the server’s audit log, so the policy cannot be probed by varying claims.