Skip to content

feat(ssh-agent): nodkey ssh-agent forwards SSH logins to the broker - #62

Merged
groupsky merged 1 commit into
mainfrom
feat/ssh-agent
Oct 15, 2026
Merged

groupsky merged 1 commit into
mainfrom
feat/ssh-agent

Conversation

@groupsky

Copy link
Copy Markdown
Contributor

What

nodkey ssh-agent [--socket PATH] serves an SSH agent socket on the agent's machine. Plain ssh, git and Ansible use it. The SSH keys stay on the broker; each signature request goes to the broker, which checks the real target host and asks the person.

  • Socket: in a 0700 directory of the user (default $XDG_RUNTIME_DIR/nodkey/agent.sock), mode 0600. Other users, and root, are refused with SO_PEERCRED. A lock file keeps a second agent from taking the socket. A stale socket is removed only if it is the user's and nothing answers. At most 128 connections.
  • Protocol: nodkey serves the SSH agent protocol itself (not x/crypto's agent.ServeAgent, which writes every error to Go's global log). Strict parsing; adding, removing and locking keys, and unknown extensions, are refused.
  • Per connection: the session-bind@openssh.com messages are kept (at most 2) and sent with each sign request, byte for byte with the key, the data and the flags. A waiting request is cancelled when ssh hangs up.
  • Claimed context: the command lines of ssh and up to 3 parents, and NODKEY_REASON from the environment of ssh (no other variable), read through one open /proc/PID directory with a 2 s deadline.
  • Answers: the same rules as nodkey run: only a final 200 that passes api.ReadAnswer; the format and the signature are checked against the key and the data before ssh gets them; no retries; a deadline below sshd's LoginGraceTime.
  • Logs: fixed texts only, rate-limited (failures and waiting lines separately).

Tests

The real OpenSSH 9.6 ssh against an in-process server and against a real sshd, through the real broker service with the fake approver: approve, deny, expiry, broker down, a wrong signature or format from the broker, a third bind, key changes, other users (a root-only test with a real second user runs in the e2e job, #15), the socket modes, the lock, stale sockets, hang-ups with extra bytes, the connection cap, idle memory, stuck /proc reads, signals. Four fuzz targets for the parsers. Stress runs with -count=10 -cpu 1,2,4 and CI=true pass on Go 1.27 and 1.26.

Docs

DESIGN.md sections 6 and 15; THREAT-MODEL.md T2, T12, and the new T30 and T31; AGENTS.md layout.

Review

Three rounds with the security-reviewer agent; the last said "ready to commit".

Fixed on the way:

  • one extra byte turned off the cancel when ssh hangs up;
  • the waiting lines had no limit; memory was reserved per connection before any data;
  • /proc reads had no deadline; two agents starting at once could remove each other's socket.

Left for later issues: #61 (pin the peer process with a pidfd before reading /proc).

Closes #13

An SSH agent socket for plain ssh, git and Ansible; each signature request goes to the broker with its session-binds and the claimed context, and the answer is checked before ssh gets it.

Closes #13
@groupsky groupsky added this to the v1 milestone Oct 15, 2026
@groupsky
groupsky merged commit e1c3f31 into main Oct 15, 2026
6 checks passed
@groupsky
groupsky deleted the feat/ssh-agent branch October 15, 2026 04:10
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.

Client: nodkey ssh-agent

1 participant