All checks were successful
check / check (push) Successful in 21s
install runs the system ssh and hands the host a short shell script to run, with the public key line on the connection's standard input rather than on a command line, where anyone else on the host could read it out of the process list. The script makes ~/.ssh and authorized_keys if they are missing, adds the line unless the same line is already there, and says which of the two it did. to serves the key from an agent inside the tool, on a unix socket in a temporary directory only its owner can enter, and points ssh at it with -o IdentityAgent. The socket and directory go when the command ends and the private key is never written to disk. Only this command hands back the status ssh ended with instead of ending with status 1. The tests put a stand-in ssh on the path: for install it runs the script the tool sends against a directory standing in for the host's home directory, so the file, the modes and the second run that changes nothing are all watched happening. Model: opus-5
96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
package ssh
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"slices"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// StatusError says the tool should end with the status ssh ended with.
|
|
// Only "ssh to" gives one back; every other error ends the tool with
|
|
// status 1.
|
|
type StatusError struct {
|
|
Status int
|
|
}
|
|
|
|
// Error says which status ssh ended with.
|
|
func (e StatusError) Error() string {
|
|
return fmt.Sprintf("ssh exited with status %d", e.Status)
|
|
}
|
|
|
|
// to returns the command that runs ssh with the derived key held by an
|
|
// agent of the tool's own.
|
|
func to() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "to <host> [ssh arguments...]",
|
|
Short: "run ssh with the derived key served from its own agent",
|
|
Long: "Serves the derived key from an SSH agent that runs " +
|
|
"inside the tool and points the system ssh at it. The host " +
|
|
"and everything after it are given to ssh unchanged, the " +
|
|
"tool ends with the status ssh ended with, and the key is " +
|
|
"never written to disk.",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
key, comment, err := derived(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
served, err := key.Serve(cmd.Context(), comment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer served.Stop()
|
|
|
|
argv := slices.Concat([]string{
|
|
"-o", "IdentityAgent=" + served.Socket(),
|
|
}, args)
|
|
|
|
return connect(cmd.Context(), argv)
|
|
},
|
|
}
|
|
|
|
// Everything from the host onwards belongs to ssh, so flag
|
|
// reading stops at the first argument that is not a flag.
|
|
cmd.Flags().SetInterspersed(false)
|
|
|
|
addComment(cmd)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// connect runs ssh on the terminal the tool was given and turns the
|
|
// status it ended with into the status the tool ends with.
|
|
func connect(ctx context.Context, argv []string) error {
|
|
//nolint:gosec // the arguments are the user's own, meant for ssh
|
|
command := exec.CommandContext(ctx, "ssh", argv...)
|
|
command.Stdin = os.Stdin
|
|
command.Stdout = os.Stdout
|
|
command.Stderr = os.Stderr
|
|
|
|
err := command.Run()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
var ended *exec.ExitError
|
|
if errors.As(err, &ended) {
|
|
status := ended.ExitCode()
|
|
if status < 0 {
|
|
// A signal ended ssh, and a signal has no status of its
|
|
// own to pass on.
|
|
status = 1
|
|
}
|
|
|
|
return StatusError{Status: status}
|
|
}
|
|
|
|
return fmt.Errorf("running ssh: %w", err)
|
|
}
|