Add documentation comments to exported symbols across all remaining smaller packages including cmd/gen-docs, context, internal/browser, internal/gh, internal/ghcmd, internal/ghinstance, internal/ghrepo, internal/keyring, internal/run, internal/safepaths, internal/tableprinter, internal/text, internal/update, pkg/cmd/accessibility, pkg/cmd/actions, pkg/cmd/alias, pkg/cmd/api, pkg/cmd/browse, pkg/cmd/cache, pkg/cmd/completion, pkg/cmd/copilot, pkg/cmd/factory, pkg/cmd/gpg-key, pkg/cmd/label, pkg/cmd/licenses, pkg/cmd/org, pkg/cmd/preview, pkg/cmd/ssh-key, pkg/cmd/version, pkg/extensions, pkg/jsoncolor, pkg/markdown, pkg/option, pkg/set, pkg/ssh, pkg/surveyext, test, internal/authflow, and utils. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
129 lines
3 KiB
Go
129 lines
3 KiB
Go
package ssh
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"github.com/cli/cli/v2/internal/config"
|
|
"github.com/cli/cli/v2/internal/run"
|
|
"github.com/cli/safeexec"
|
|
)
|
|
|
|
// Context holds configuration for SSH key operations.
|
|
type Context struct {
|
|
configDir string
|
|
keygenExe string
|
|
}
|
|
|
|
// NewContextForTests creates a new `ssh.Context` with internal properties set to the
|
|
// specified values. It should only be used to inject test-specific setup.
|
|
func NewContextForTests(configDir, keygenExe string) Context {
|
|
return Context{
|
|
configDir,
|
|
keygenExe,
|
|
}
|
|
}
|
|
|
|
// KeyPair holds a public and private SSH key pair.
|
|
type KeyPair struct {
|
|
PublicKeyPath string
|
|
PrivateKeyPath string
|
|
}
|
|
|
|
// ErrKeyAlreadyExists is returned when an SSH key already exists.
|
|
var ErrKeyAlreadyExists = errors.New("SSH key already exists")
|
|
|
|
// LocalPublicKeys returns the list of public SSH keys found locally.
|
|
func (c *Context) LocalPublicKeys() ([]string, error) {
|
|
sshDir, err := c.SshDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return filepath.Glob(filepath.Join(sshDir, "*.pub"))
|
|
}
|
|
|
|
// HasKeygen reports whether ssh-keygen is available.
|
|
func (c *Context) HasKeygen() bool {
|
|
_, err := c.findKeygen()
|
|
return err == nil
|
|
}
|
|
|
|
// GenerateSSHKey generates a new SSH key pair.
|
|
func (c *Context) GenerateSSHKey(keyName string, passphrase string) (*KeyPair, error) {
|
|
keygenExe, err := c.findKeygen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sshDir, err := c.SshDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = os.MkdirAll(sshDir, 0700)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not create .ssh directory: %w", err)
|
|
}
|
|
|
|
keyFile := filepath.Join(sshDir, keyName)
|
|
keyPair := KeyPair{
|
|
PublicKeyPath: keyFile + ".pub",
|
|
PrivateKeyPath: keyFile,
|
|
}
|
|
|
|
if _, err := os.Stat(keyFile); err == nil {
|
|
// Still return keyPair because the caller might be OK with this - they can check the error with errors.Is(err, ErrKeyAlreadyExists)
|
|
return &keyPair, ErrKeyAlreadyExists
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(keyFile), 0711); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keygenCmd := exec.Command(keygenExe, "-t", "ed25519", "-C", "", "-N", passphrase, "-f", keyFile)
|
|
err = run.PrepareCmd(keygenCmd).Run()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &keyPair, nil
|
|
}
|
|
|
|
// SshDir returns the path to the SSH directory.
|
|
func (c *Context) SshDir() (string, error) {
|
|
if c.configDir != "" {
|
|
return c.configDir, nil
|
|
}
|
|
dir, err := config.HomeDirPath(".ssh")
|
|
if err == nil {
|
|
c.configDir = dir
|
|
}
|
|
return dir, err
|
|
}
|
|
|
|
func (c *Context) findKeygen() (string, error) {
|
|
if c.keygenExe != "" {
|
|
return c.keygenExe, nil
|
|
}
|
|
|
|
keygenExe, err := safeexec.LookPath("ssh-keygen")
|
|
if err != nil && runtime.GOOS == "windows" {
|
|
// We can try and find ssh-keygen in a Git for Windows install
|
|
if gitPath, err := safeexec.LookPath("git"); err == nil {
|
|
gitKeygen := filepath.Join(filepath.Dir(gitPath), "..", "usr", "bin", "ssh-keygen.exe")
|
|
if _, err = os.Stat(gitKeygen); err == nil {
|
|
return gitKeygen, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if err == nil {
|
|
c.keygenExe = keygenExe
|
|
}
|
|
return keygenExe, err
|
|
}
|