github.com/secman-team/gh-api@v1.8.2/pkg/cmd/auth/shared/ssh_keys.go (about)

     1  package shared
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  
    11  	"github.com/AlecAivazis/survey/v2"
    12  	"github.com/secman-team/gh-api/core/config"
    13  	"github.com/secman-team/gh-api/core/run"
    14  	"github.com/secman-team/gh-api/pkg/cmd/ssh-key/add"
    15  	"github.com/secman-team/gh-api/pkg/prompt"
    16  	"github.com/cli/safeexec"
    17  )
    18  
    19  type sshContext struct {
    20  	configDir string
    21  	keygenExe string
    22  }
    23  
    24  func (c *sshContext) sshDir() (string, error) {
    25  	if c.configDir != "" {
    26  		return c.configDir, nil
    27  	}
    28  	dir, err := config.HomeDirPath(".ssh")
    29  	if err == nil {
    30  		c.configDir = dir
    31  	}
    32  	return dir, err
    33  }
    34  
    35  func (c *sshContext) localPublicKeys() ([]string, error) {
    36  	sshDir, err := c.sshDir()
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	return filepath.Glob(filepath.Join(sshDir, "*.pub"))
    42  }
    43  
    44  func (c *sshContext) findKeygen() (string, error) {
    45  	if c.keygenExe != "" {
    46  		return c.keygenExe, nil
    47  	}
    48  
    49  	keygenExe, err := safeexec.LookPath("ssh-keygen")
    50  	if err != nil && runtime.GOOS == "windows" {
    51  		// We can try and find ssh-keygen in a Git for Windows install
    52  		if gitPath, err := safeexec.LookPath("git"); err == nil {
    53  			gitKeygen := filepath.Join(filepath.Dir(gitPath), "..", "usr", "bin", "ssh-keygen.exe")
    54  			if _, err = os.Stat(gitKeygen); err == nil {
    55  				return gitKeygen, nil
    56  			}
    57  		}
    58  	}
    59  
    60  	if err == nil {
    61  		c.keygenExe = keygenExe
    62  	}
    63  	return keygenExe, err
    64  }
    65  
    66  func (c *sshContext) generateSSHKey() (string, error) {
    67  	keygenExe, err := c.findKeygen()
    68  	if err != nil {
    69  		// give up silently if `ssh-keygen` is not available
    70  		return "", nil
    71  	}
    72  
    73  	var sshChoice bool
    74  	err = prompt.SurveyAskOne(&survey.Confirm{
    75  		Message: "Generate a new SSH key to add to your GitHub account?",
    76  		Default: true,
    77  	}, &sshChoice)
    78  	if err != nil {
    79  		return "", fmt.Errorf("could not prompt: %w", err)
    80  	}
    81  	if !sshChoice {
    82  		return "", nil
    83  	}
    84  
    85  	sshDir, err := c.sshDir()
    86  	if err != nil {
    87  		return "", err
    88  	}
    89  	keyFile := filepath.Join(sshDir, "id_ed25519")
    90  	if _, err := os.Stat(keyFile); err == nil {
    91  		return "", fmt.Errorf("refusing to overwrite file %s", keyFile)
    92  	}
    93  
    94  	if err := os.MkdirAll(filepath.Dir(keyFile), 0711); err != nil {
    95  		return "", err
    96  	}
    97  
    98  	var sshLabel string
    99  	var sshPassphrase string
   100  	err = prompt.SurveyAskOne(&survey.Password{
   101  		Message: "Enter a passphrase for your new SSH key (Optional)",
   102  	}, &sshPassphrase)
   103  	if err != nil {
   104  		return "", fmt.Errorf("could not prompt: %w", err)
   105  	}
   106  
   107  	keygenCmd := exec.Command(keygenExe, "-t", "ed25519", "-C", sshLabel, "-N", sshPassphrase, "-f", keyFile)
   108  	return keyFile + ".pub", run.PrepareCmd(keygenCmd).Run()
   109  }
   110  
   111  func sshKeyUpload(httpClient *http.Client, hostname, keyFile string) error {
   112  	f, err := os.Open(keyFile)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	defer f.Close()
   117  
   118  	return add.SSHKeyUpload(httpClient, hostname, f, "GitHub CLI")
   119  }