Skip to content

fix(ssh): wrap agent signers to continue to next identity on signing failure#3401

Open
Jason-Shen2 wants to merge 2 commits into
wavetermdev:mainfrom
Jason-Shen2:fix/ssh-agent-failover-signer
Open

fix(ssh): wrap agent signers to continue to next identity on signing failure#3401
Jason-Shen2 wants to merge 2 commits into
wavetermdev:mainfrom
Jason-Shen2:fix/ssh-agent-failover-signer

Conversation

@Jason-Shen2

Copy link
Copy Markdown

Fixes #3365

Problem

When an SSH agent-backed key fails to sign (e.g. user cancels a FIDO2 / security-key user-presence prompt), the crypto/ssh publickey callback returns an error. The SSH library treats any callback error as fatal and aborts the entire authentication flow — it does not try any remaining agent identities or keyfiles. This differs from OpenSSH, which moves on to the next key when a signing request is declined.

For users with multiple identities in their agent (e.g. a YubiKey/FIDO2 key first, then a regular ed25519 key), tapping "decline" on the hardware key prompt prevents any subsequent key from being attempted, even when the server would accept it.

Root cause

agentClient.Signers() returns []ssh.Signer that call through to the agent. If Sign() returns an error (e.g. agent.errNoMoreKeys/user cancellation), ssh.PublicKeysCallback propagates that error and RetryableAuthMethod stops retrying because the method itself failed, not because authentication was rejected.

Fix

Wrap each agent-backed signer in a new failoverSigner (sshsigners.go) that:

  • Implements both ssh.Signer and ssh.AlgorithmSigner.
  • On Sign / SignWithAlgorithm failure, logs a [conndebug] message and returns a deliberately invalid signature (a blob that cannot verify) instead of an error.
  • The server will reject the auth attempt (wrong signature for that key) which ssh treats as a normal per-key failure, allowing RetryableAuthMethod to continue with the next identity — exactly matching OpenSSH's behavior.

Also adds [conndebug] logging to aid troubleshooting:

  • Agent socket dial failures
  • Agent key listing errors
  • Number of identities provided by the agent
  • Each agent identity being tried (type + SHA256 fingerprint)
  • Per-key signing failures with the key fingerprint and error

Testing

  • go build ./... passes
  • go vet ./pkg/remote/ passes
  • go test ./pkg/remote/ passes (includes TestDialIdentityAgentUnix)

No behavioral change when signers succeed; only the error path changes so that a rejected hardware-key prompt no longer blocks fallback to other identities.

…failure

When an SSH agent signer fails to sign (e.g. user cancels a FIDO2/security-key
user-presence prompt), golang.org/x/crypto/ssh treats the error as fatal and
aborts the entire publickey authentication, preventing remaining identities
from being tried. This deviates from OpenSSH behavior where a declined
signature simply moves on to the next key.

This change wraps agent-backed signers in a failoverSigner that returns a
deliberately invalid signature on signing failure instead of propagating the
error. The server rejects the invalid signature as a normal auth failure,
allowing RetryableAuthMethod to continue with the next identity.

Also adds conndebug logging for:
- Agent socket dial failures
- Agent key listing errors
- Number of identities provided by the agent
- Each agent identity being attempted
- Per-key signing failures

Fixes wavetermdev#3365
@CLAassistant

CLAassistant commented Jun 28, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c4e6fdda-52d4-4351-9c4a-3c6eccf8674e

📥 Commits

Reviewing files that changed from the base of the PR and between a6b9fbc and 5c73f06.

📒 Files selected for processing (1)
  • pkg/remote/sshsigners.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/remote/sshsigners.go

Walkthrough

A new failoverSigner type is introduced in pkg/remote/sshsigners.go. It wraps an ssh.Signer and implements Sign and SignWithAlgorithm such that on signing failure it logs the error and returns a synthesized invalid signature instead of propagating the error. An invalidSignature helper constructs the placeholder ssh.Signature. In pkg/remote/sshclient.go, the public-key callback now logs each identity's key type and SHA-256 fingerprint. Agent socket failures and agentClient.Signers() errors are now explicitly logged, and each agent signer is wrapped in failoverSigner before being appended.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: wrapping agent signers so SSH auth can continue after a signing failure.
Description check ✅ Passed The description matches the implemented SSH agent failover and logging changes.
Linked Issues check ✅ Passed The changes satisfy #3365 by preserving fallback to the next identity, supporting AlgorithmSigner, and adding the requested conndebug logging.
Out of Scope Changes check ✅ Passed The diff stays focused on SSH agent failover and diagnostics, with no obvious unrelated changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/remote/sshsigners.go`:
- Around line 39-49: The failover path in failoverSigner.SignWithAlgorithm is
returning a signature whose Format is based on the key type instead of the
requested signing algorithm. Update invalidSignature (and its call site in
SignWithAlgorithm) so the fallback signature preserves the algorithm passed into
SignWithAlgorithm, using that value for Signature.Format rather than
signer.PublicKey().Type().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e89d98ce-5add-43a8-86ec-6f94637a33b3

📥 Commits

Reviewing files that changed from the base of the PR and between c99022c and a6b9fbc.

📒 Files selected for processing (2)
  • pkg/remote/sshclient.go
  • pkg/remote/sshsigners.go

Comment thread pkg/remote/sshsigners.go
- invalidSignature now accepts a format parameter
- SignWithAlgorithm passes the requested algorithm instead of key type
- Add docstrings for failoverSigner and all methods to meet coverage

Addresses CodeRabbit review comment on PR wavetermdev#3401
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.

[Bug]: Failed agent signature aborts SSH auth instead of trying the next key

2 participants