Skip to content

login: load certificate into ssh-agent with configurable lifetime - #611

Open
sini wants to merge 6 commits into
openpubkey:mainfrom
sini:feat/agent-lifetime
Open

login: load certificate into ssh-agent with configurable lifetime#611
sini wants to merge 6 commits into
openpubkey:mainfrom
sini:feat/agent-lifetime

Conversation

@sini

@sini sini commented Aug 20, 2026

Copy link
Copy Markdown

Summary

First of the two PRs agreed in #606: when SSH_AUTH_SOCK points at a running ssh-agent, opkssh login now also adds the freshly minted certificate and its private key to the agent, so the key is usable immediately without pointing ssh at the key files. Best-effort and non-fatal — the keys are still written to disk first, and any agent problem is a warning rather than a login failure.

The key is always added with a lifetime, since ssh-agent has no replace operation and keys would otherwise accumulate with every login. The lifetime is resolved as:

  1. --lifetime flag (opkssh login --lifetime 12h)
  2. agent_lifetime in the client config (~/.opk/config.yml)
  3. default of 24h, matching the server's default certificate expiration policy

Both duration strings (12h, 45m) and raw seconds (28800) are accepted. An invalid value fails up front, before the browser dance.

Per the discussion in #606, this PR makes no change to certificate ValidBefore — the client cannot know the server's expiration policy (the server computes it from the ID token's iat), so the agent lifetime is a client-side bound to keep keys from accumulating, not a statement of the certificate's validity. Syncing expiries dynamically is the follow-up refresh-daemon PR's job.

Relates to #6, #96. Split out of #606.

Notes

  • The lifetime is validated twice on purpose: eagerly in Run() (fail before the browser dance) and again in addCertToAgent, because Login/LoginWithRefresh are exported and reachable without Run() (the integration tests call them directly). Sub-second and overflowing values are rejected — a truncated LifetimeSecs of 0 would mean an immortal key, the exact state the lifetime exists to prevent.
  • The agent exchange carries a 5s I/O deadline so a dead forwarded-agent socket degrades to a warning instead of hanging the login.
  • With --auto-refresh the key is added to the agent once at initial login; refreshed certificates only go to disk as today. Live agent rotation on refresh lands with the refresh daemon (PR 2).
  • The agent add is skipped under --print-key (that mode's contract is stdout only).
  • Tests: lifetime resolution is unit-tested; the agent add is tested against an in-process agent.ServeAgent server over a unix socket (skipped on Windows), asserting the added key carries the certificate and the requested LifetimeSecs. Both the unit login-flow tests and the integration suite now pin SSH_AUTH_SOCK empty so go test never touches the developer's real agent.
  • On Windows the default OpenSSH agent listens on a named pipe and SSH_AUTH_SOCK is typically unset, so the agent add is silently skipped (now noted in docs/config.md); named-pipe support is planned alongside the daemon work.

sini added 2 commits August 20, 2026 13:01
…time

When SSH_AUTH_SOCK points at a running ssh-agent, opkssh login now also
adds the freshly minted certificate and its private key to the agent, so
the key is usable immediately without pointing ssh at the key files.
Best-effort and non-fatal: the keys are still written to disk first, and
any agent problem is reported as a warning rather than failing the login.

The key is always added with a lifetime, since ssh-agent has no replace
operation and keys would otherwise accumulate with every login. The
lifetime is resolved as: --lifetime flag, then agent_lifetime in the
client config (~/.opk/config.yml), then a default of 24h matching the
server's default certificate expiration policy. Both duration strings
(12h, 45m) and raw seconds (28800) are accepted, and an invalid value
fails before the browser dance rather than after authentication.

Relates to openpubkey#6 and openpubkey#96; first of the two PRs agreed in openpubkey#606 (the second
adds a refresh daemon that rotates agent keys as tokens are refreshed).
Review findings on the agent-lifetime feature:

- Reject lifetimes under one second: uint32(d / time.Second) truncated
  sub-second durations (500ms, 900ms) to LifetimeSecs 0, which the agent
  protocol treats as no lifetime at all — an immortal key, the exact
  state the lifetime exists to prevent. Bound raw seconds values before
  multiplying, since time.Duration(sec) * time.Second wraps int64 for
  large inputs and could land on a small bogus duration.
- Set a 5s deadline on the ssh-agent exchange so a dead socket (e.g. a
  forwarded agent whose upstream connection is gone) degrades to a
  warning instead of hanging the login after authentication succeeded.
- Unset SSH_AUTH_SOCK in the integration suite's TestMain so its seven
  login flows can never inject test keys into a developer's real agent.
- Document that the agent is not currently reachable on Windows.
sini added 4 commits August 20, 2026 18:41
… in CLI docs

Post-review cleanup, behavior-preserving:

- The in-process test-agent scaffolding is extracted to startTestAgent
  (windows skip, socket lifecycle via t.Cleanup, SSH_AUTH_SOCK wiring in
  one place).
- The SSH_AUTH_SOCK isolation guard moves into the shared Mocks fixture,
  so every mock login flow is isolated from the developer's real agent by
  construction instead of by three pasted guards; a test that needs an
  agent points SSH_AUTH_SOCK at its own after calling Mocks.
- TestParseConfigWithAgentLifetime collapses its two verbatim copies into
  a loop over both accepted value shapes.
- Two comment blocks that restated nearby doc comments are trimmed.
- docs/cli/opkssh_login.md is regenerated to document the new --lifetime
  flag (only this file has content changes from the flag addition).
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