Skip to content

feat: Add an option to let the user provide the entire key PEM data as the answer #1078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 28, 2023
88 changes: 74 additions & 14 deletions common/sshkeys/sshkeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ import (
"golang.org/x/crypto/ssh"
)

const (
userProvidedKey = "user_provided_key"
)

var (
// DomainToPublicKeys maps domains to public keys gathered with known-hosts/get-public-keys.sh
DomainToPublicKeys = map[string][]string{
Expand Down Expand Up @@ -101,37 +105,67 @@ func loadSSHKeysOfCurrentUser() {
privateKeyDir = filepath.Join(home, ".ssh")
logrus.Debugf("Looking in ssh directory at path %q for keys.", privateKeyDir)

// Ask if we should look at the private keys
// Ask whether to load private keys or provide own key
options := []string{
"Load private ssh keys from " + privateKeyDir,
"Provide your own key",
"No, I will add them later if necessary.",
}
message := `The CI/CD pipeline needs access to the git repos in order to clone, build and push.
If any of the repos require ssh keys you will need to provide them.
Do you want to load the private ssh keys from [%s]?:`
ans := qaengine.FetchBoolAnswer(common.ConfigRepoLoadPrivKey, fmt.Sprintf(message, privateKeyDir), []string{"No, I will add them later if necessary."}, false, nil)
if !ans {
Select an option:`
selectedOption := qaengine.FetchSelectAnswer(common.ConfigRepoLoadPrivKey, message, nil, "", options, nil)

switch selectedOption {
case options[0]:
if err := loadKeysFromDirectory(privateKeyDir); err != nil {
logrus.Warn("Can't load keys from directory. Error:", err)
return
}

case options[1]:
privateKeysToConsider = []string{userProvidedKey}

default:
logrus.Debug("Don't read private keys. They will be added later if necessary.")
return
}

// Ask which keys we should consider
finfos, err := os.ReadDir(privateKeyDir)
}

func loadKeysFromDirectory(directory string) error {
finfos, err := os.ReadDir(directory)
if err != nil {
logrus.Errorf("Failed to read the ssh directory at path %q Error: %q", privateKeyDir, err)
return
return fmt.Errorf("failed to read the SSH directory at path %q: %w", directory, err)
}

if len(finfos) == 0 {
logrus.Warn("No key files where found in", privateKeyDir)
return
logrus.Warn("No key files were found in", directory)
return nil
}

filenames := []string{}
for _, finfo := range finfos {
filenames = append(filenames, finfo.Name())
}
filenames = qaengine.FetchMultiSelectAnswer(common.ConfigRepoKeyPathsKey, fmt.Sprintf("These are the files we found in %q . Which keys should we consider?", privateKeyDir), []string{"Select all the keys that give access to git repos."}, filenames, filenames, nil)
if len(filenames) == 0 {

selectedFilenames := qaengine.FetchMultiSelectAnswer(
common.ConfigRepoKeyPathsKey,
fmt.Sprintf("These are the files found in %q. Select the keys to consider:", directory),
[]string{"Select all the keys that give access to git repos."},
filenames,
filenames,
nil,
)

if len(selectedFilenames) == 0 {
logrus.Info("All key files ignored.")
return
return nil
}

// Save the filenames for now. We will decrypt them if and when we need them.
privateKeysToConsider = filenames
privateKeysToConsider = selectedFilenames
return nil
}

func marshalRSAIntoPEM(key *rsa.PrivateKey) string {
Expand Down Expand Up @@ -195,6 +229,18 @@ func GetSSHKey(domain string) (string, bool) {
if len(privateKeysToConsider) == 0 {
return "", false
}
if privateKeysToConsider[0] == userProvidedKey {
key := qaengine.FetchStringAnswer(common.ConfigRepoPrivKey, "Provide your own PEM-formatted private key:", []string{"Should not be empty"}, "", nil)
if key == "" {
logrus.Error("User-provided private key is empty.")
return "", false
}
if err := validatePEMPrivateKey(key); err != nil {
logrus.Error("Can't validate the PEM-formatted private key. Error:", err)
return "", false
}
return key, true
}

filenames := privateKeysToConsider
noAnswer := "none of the above"
Expand All @@ -216,3 +262,17 @@ func GetSSHKey(domain string) (string, bool) {
}
return key, true
}

func validatePEMPrivateKey(key string) error {
block, _ := pem.Decode([]byte(key))
if block == nil || block.Type != "RSA PRIVATE KEY" {
return fmt.Errorf("invalid PEM private key format")
}

_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}

return nil
}