Turn IdentitiesOnly off for ssh to (closes #74) #85

Merged
clawbot merged 1 commits from issue-74-ssh-to-identitiesonly into next 2026-10-14 21:12:59 +02:00
3 changed files with 35 additions and 6 deletions
Showing only changes of commit 8fe4a3c348 - Show all commits
+9 -5
View File
@@ -219,11 +219,15 @@ first, or pass `-o StrictHostKeyChecking=accept-new` after `--`.
Derives the key, serves it from an SSH agent that runs inside the tool on a unix
socket in a new private `0700` temporary directory, then runs the system `ssh`
with `-o IdentityAgent=<that socket>` followed by the host and all remaining
arguments unchanged. The tool exits with `ssh`'s exit status and removes the
socket and directory on the way out. The private key is never written to disk. A
SIGINT, SIGTERM or SIGHUP ends `ssh` and still removes the socket and directory,
and the tool then exits with status 1 unless `ssh` reported one of its own.
with `-o IdentityAgent=<that socket> -o IdentitiesOnly=no` followed by the host
and all remaining arguments unchanged. Without `IdentitiesOnly=no`, an
`IdentitiesOnly yes` in the user's `ssh` configuration keeps the derived key
from being offered. The agent holds no other key, so turning it off adds nothing
else to what `ssh` offers. The tool exits with `ssh`'s exit status and removes
the socket and directory on the way out. The private key is never written to
disk. A SIGINT, SIGTERM or SIGHUP ends `ssh` and still removes the socket and
directory, and the tool then exits with status 1 unless `ssh` reported one of
its own.
## age identities: `keyfunc age`
+5
View File
@@ -57,8 +57,13 @@ func to() *cobra.Command {
defer served.Stop()
// IdentitiesOnly yes in the user's ssh configuration
// would keep the agent's key from being offered. The
// agent holds only the derived key, so turning it off
// adds no other key.
argv := slices.Concat([]string{
"-o", "IdentityAgent=" + served.Socket(),
"-o", "IdentitiesOnly=no",
}, args)
return connect(ctx, argv)
+21 -1
View File
@@ -587,7 +587,7 @@ func TestSSHIsPointedAtTheAgentAndItsStatusIsHandedOn(t *testing.T) {
given := recorded(t, arguments)
require.Equal(t, "-o", given[0])
require.Equal(t, []string{host, remoteCommand}, given[2:])
require.Equal(t, []string{host, remoteCommand}, given[4:])
// The stand-in wrote the path down only because there really was
// a socket there while it ran.
@@ -596,6 +596,26 @@ func TestSSHIsPointedAtTheAgentAndItsStatusIsHandedOn(t *testing.T) {
require.NoDirExists(t, filepath.Dir(socket))
}
func TestIdentitiesOnlyIsTurnedOffBeforeTheHost(t *testing.T) {
t.Setenv(mnemonic.Variable, example())
arguments, _ := pretendCall(t)
_, err := execute(t, subcommand, "to", host, remoteCommand)
var passed ssh.StatusError
require.ErrorAs(t, err, &passed)
// IdentitiesOnly yes in the user's ssh configuration keeps ssh
// from offering the agent's key. An option given before the host
// wins over the configuration.
given := recorded(t, arguments)
require.Equal(t,
[]string{"-o", "IdentitiesOnly=no", host, remoteCommand}, given[2:],
)
}
func TestTheToolEndsWithTheStatusSSHEndedWith(t *testing.T) {
t.Setenv(mnemonic.Variable, example())