cli/pkg/cmd/auth/shared/ssh_keys.go
Mislav Marohnić 9550ad0159
Tweak prompt for SSH passphrase
Co-authored-by: Amanda Pinsker <ampinsk@github.com>
2021-02-05 10:16:42 +01:00

153 lines
3.5 KiB
Go

package shared
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/config"
"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/internal/run"
"github.com/cli/cli/pkg/prompt"
"github.com/cli/safeexec"
)
func localPublicKeys() ([]string, error) {
sshDir, err := config.HomeDirPath(".ssh")
if err != nil {
return nil, err
}
return filepath.Glob(filepath.Join(sshDir, "*.pub"))
}
func findKeygen() (string, error) {
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
}
}
}
return keygenExe, err
}
func generateSSHKey() (string, error) {
keygenExe, err := findKeygen()
if err != nil {
// give up silently if `ssh-keygen` is not available
return "", nil
}
var sshChoice bool
err = prompt.SurveyAskOne(&survey.Confirm{
Message: "Generate a new SSH key to add to your GitHub account?",
Default: true,
}, &sshChoice)
if err != nil {
return "", fmt.Errorf("could not prompt: %w", err)
}
if !sshChoice {
return "", nil
}
sshDir, err := config.HomeDirPath(".ssh")
if err != nil {
return "", err
}
keyFile := filepath.Join(sshDir, "id_ed25519")
if _, err := os.Stat(keyFile); err == nil {
return "", fmt.Errorf("refusing to overwrite file %s", keyFile)
}
if err := os.MkdirAll(filepath.Dir(keyFile), 0711); err != nil {
return "", err
}
var sshLabel string
// err = prompt.SurveyAskOne(&survey.Input{
// Message: "Enter the label for your new SSH key",
// }, &sshLabel)
// if err != nil {
// return "", fmt.Errorf("could not prompt: %w", err)
// }
var sshPassphrase string
err = prompt.SurveyAskOne(&survey.Password{
Message: "Enter a passphrase for your new SSH key (Optional)",
}, &sshPassphrase)
if err != nil {
return "", fmt.Errorf("could not prompt: %w", err)
}
keygenCmd := exec.Command(keygenExe, "-t", "ed25519", "-C", sshLabel, "-N", sshPassphrase, "-f", keyFile)
return keyFile + ".pub", run.PrepareCmd(keygenCmd).Run()
}
func sshKeyUpload(httpClient *http.Client, keyFile string) error {
url := ghinstance.RESTPrefix(ghinstance.OverridableDefault()) + "user/keys"
f, err := os.Open(keyFile)
if err != nil {
return err
}
keyBytes, err := ioutil.ReadAll(f)
if err != nil {
return err
}
payload := map[string]string{
"title": "GitHub CLI",
"key": string(keyBytes),
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes))
if err != nil {
return err
}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode > 299 {
var httpError api.HTTPError
err := api.HandleHTTPError(resp)
if errors.As(err, &httpError) && isDuplicateError(&httpError) {
return nil
}
return err
}
_, err = io.Copy(ioutil.Discard, resp.Body)
if err != nil {
return err
}
return nil
}
func isDuplicateError(err *api.HTTPError) bool {
return err.StatusCode == 422 && len(err.Errors) == 1 &&
err.Errors[0].Field == "key" && err.Errors[0].Message == "key is already in use"
}