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 [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) }