@@ -34,7 +34,7 @@ import (
34
34
)
35
35
36
36
const (
37
- userProvidedKey = "user_provided_key "
37
+ shoudlAskUserForSSHKey = "____m2k_ask_user_to_provide_the_ssh_private_key "
38
38
)
39
39
40
40
var (
@@ -107,65 +107,56 @@ func loadSSHKeysOfCurrentUser() {
107
107
108
108
// Ask whether to load private keys or provide own key
109
109
options := []string {
110
- "Load private ssh keys from " + privateKeyDir ,
110
+ fmt . Sprintf ( "Load the private SSH keys from the directory '%s' " + privateKeyDir ) ,
111
111
"Provide your own key" ,
112
112
"No, I will add them later if necessary." ,
113
113
}
114
114
message := `The CI/CD pipeline needs access to the git repos in order to clone, build and push.
115
115
If any of the repos require ssh keys you will need to provide them.
116
116
Select an option:`
117
117
selectedOption := qaengine .FetchSelectAnswer (common .ConfigRepoLoadPrivKey , message , nil , "" , options , nil )
118
-
119
118
switch selectedOption {
120
119
case options [0 ]:
121
- if err := loadKeysFromDirectory (privateKeyDir ); err != nil {
122
- logrus .Warn ("Can't load keys from directory. Error:" , err )
120
+ selectedKeyFilenames , err := loadKeysFromDirectory (privateKeyDir )
121
+ if err != nil {
122
+ logrus .Warnf ("Failed to load the keys from the SSH directory '%s'. Error: %q" , privateKeyDir , err )
123
123
return
124
124
}
125
-
125
+ // Save the filenames for now. We will decrypt them if and when we need them.
126
+ privateKeysToConsider = selectedKeyFilenames
126
127
case options [1 ]:
127
- privateKeysToConsider = []string {userProvidedKey }
128
-
128
+ privateKeysToConsider = []string {shoudlAskUserForSSHKey }
129
129
default :
130
130
logrus .Debug ("Don't read private keys. They will be added later if necessary." )
131
131
return
132
132
}
133
-
134
133
}
135
134
136
- func loadKeysFromDirectory (directory string ) error {
135
+ func loadKeysFromDirectory (directory string ) ([] string , error ) {
137
136
finfos , err := os .ReadDir (directory )
138
137
if err != nil {
139
- return fmt .Errorf ("failed to read the SSH directory at path %q : %w" , directory , err )
138
+ return nil , fmt .Errorf ("failed to read the directory '%s'. Error : %w" , directory , err )
140
139
}
141
-
142
140
if len (finfos ) == 0 {
143
- logrus .Warn ("No key files were found in" , directory )
144
- return nil
141
+ return nil , fmt .Errorf ("no key files were found in the directory '%s'" , directory )
145
142
}
146
-
147
143
filenames := []string {}
148
144
for _ , finfo := range finfos {
149
145
filenames = append (filenames , finfo .Name ())
150
146
}
151
-
152
147
selectedFilenames := qaengine .FetchMultiSelectAnswer (
153
148
common .ConfigRepoKeyPathsKey ,
154
- fmt .Sprintf ("These are the files found in %q . Select the keys to consider:" , directory ),
155
- []string {"Select all the keys that give access to git repos." },
149
+ fmt .Sprintf ("These are the files we found in the SSH directory '%s' . Select the keys to consider:" , directory ),
150
+ []string {"Select all the keys that give access to the git repos." },
156
151
filenames ,
157
152
filenames ,
158
153
nil ,
159
154
)
160
-
161
155
if len (selectedFilenames ) == 0 {
162
156
logrus .Info ("All key files ignored." )
163
- return nil
157
+ return nil , nil
164
158
}
165
-
166
- // Save the filenames for now. We will decrypt them if and when we need them.
167
- privateKeysToConsider = selectedFilenames
168
- return nil
159
+ return selectedFilenames , nil
169
160
}
170
161
171
162
func marshalRSAIntoPEM (key * rsa.PrivateKey ) string {
@@ -186,29 +177,22 @@ func marshalECDSAIntoPEM(key *ecdsa.PrivateKey) string {
186
177
return string (PEMBytes )
187
178
}
188
179
189
- func loadSSHKey (filename string ) (string , error ) {
190
- path := filepath .Join (privateKeyDir , filename )
191
- fileBytes , err := os .ReadFile (path )
192
- if err != nil {
193
- logrus .Errorf ("Failed to read the private key file at path %q Error: %q" , path , err )
194
- return "" , err
195
- }
196
- key , err := ssh .ParseRawPrivateKey (fileBytes )
180
+ // loadSSHPrivateKeyFromBytes tries to parse the bytes as an SSH private key.
181
+ // The keyName is optional (used to ask the user for the password if necessary).
182
+ func loadSSHPrivateKeyFromBytes (keyBytes []byte , keyName string ) (string , error ) {
183
+ key , err := ssh .ParseRawPrivateKey (keyBytes )
197
184
if err != nil {
198
185
// Could be an encrypted private key.
199
186
if _ , ok := err .(* ssh.PassphraseMissingError ); ! ok {
200
- logrus .Errorf ("Failed to parse the private key file at path %q Error %q" , path , err )
201
- return "" , err
187
+ return "" , fmt .Errorf ("failed to parse as a SSH private key. Error %w" , err )
202
188
}
203
-
204
- qaKey := common .JoinQASubKeys (common .ConfigRepoPrivKey , `"` + filename + `"` , "password" )
205
- desc := fmt .Sprintf ("Enter the password to decrypt the private key %q : " , filename )
189
+ qaKey := common .JoinQASubKeys (common .ConfigRepoPrivKey , `"` + keyName + `"` , "password" )
190
+ desc := fmt .Sprintf ("Enter the password to decrypt the SSH private key '%s' : " , keyName )
206
191
hints := []string {"Password:" }
207
192
password := qaengine .FetchPasswordAnswer (qaKey , desc , hints , nil )
208
- key , err = ssh .ParseRawPrivateKeyWithPassphrase (fileBytes , []byte (password ))
193
+ key , err = ssh .ParseRawPrivateKeyWithPassphrase (keyBytes , []byte (password ))
209
194
if err != nil {
210
- logrus .Errorf ("Failed to parse the encrypted private key file at path %q Error %q" , path , err )
211
- return "" , err
195
+ return "" , fmt .Errorf ("failed to decrypt and parse the encrypted private SSH key '%s' . Error %w" , keyName , err )
212
196
}
213
197
}
214
198
// *ecdsa.PrivateKey
@@ -218,61 +202,70 @@ func loadSSHKey(filename string) (string, error) {
218
202
case * ecdsa.PrivateKey :
219
203
return marshalECDSAIntoPEM (actualKey ), nil
220
204
default :
221
- logrus .Errorf ("Unknown key type [%T]" , key )
222
205
return "" , fmt .Errorf ("unknown key type [%T]" , key )
223
206
}
224
207
}
225
208
209
+ func loadSSHPrivateKey (filename string ) (string , error ) {
210
+ path := filepath .Join (privateKeyDir , filename )
211
+ fileBytes , err := os .ReadFile (path )
212
+ if err != nil {
213
+ return "" , fmt .Errorf ("failed to read the SSH private key file '%s' . Error: %w" , path , err )
214
+ }
215
+ return loadSSHPrivateKeyFromBytes (fileBytes , filename )
216
+ }
217
+
226
218
// GetSSHKey returns the private key for the given domain.
227
219
func GetSSHKey (domain string ) (string , bool ) {
228
220
loadSSHKeysOfCurrentUser ()
229
221
if len (privateKeysToConsider ) == 0 {
230
222
return "" , false
231
223
}
232
- if privateKeysToConsider [0 ] == userProvidedKey {
233
- key := qaengine .FetchStringAnswer (common .ConfigRepoPrivKey , "Provide your own PEM-formatted private key:" , []string {"Should not be empty" }, "" , nil )
224
+
225
+ if len (privateKeysToConsider ) == 1 && privateKeysToConsider [0 ] == shoudlAskUserForSSHKey {
226
+ qaKey := common .JoinQASubKeys (common .ConfigRepoKeysKey , `"` + domain + `"` , "keyData" )
227
+ validatedKey := ""
228
+ key := qaengine .FetchStringAnswer (
229
+ qaKey ,
230
+ fmt .Sprintf ("Provide a PEM-formatted SSH private key for the domain '%s':" , domain ),
231
+ []string {"To skip this question, just leave the answer empty" },
232
+ "" ,
233
+ func (ansI interface {}) error {
234
+ ans := ansI .(string )
235
+ if ans == "" {
236
+ return nil
237
+ }
238
+ t1 , err := loadSSHPrivateKeyFromBytes ([]byte (ans ), domain )
239
+ if err == nil {
240
+ validatedKey = t1
241
+ }
242
+ return err
243
+ },
244
+ )
234
245
if key == "" {
235
- logrus .Error ("User-provided private key is empty." )
236
- return "" , false
237
- }
238
- if err := validatePEMPrivateKey (key ); err != nil {
239
- logrus .Error ("Can't validate the PEM-formatted private key. Error:" , err )
246
+ logrus .Debugf ("No key was provided for the domain '%s'" , domain )
240
247
return "" , false
241
248
}
242
- return key , true
249
+ return validatedKey , true
243
250
}
244
251
245
252
filenames := privateKeysToConsider
246
253
noAnswer := "none of the above"
247
254
filenames = append (filenames , noAnswer )
248
255
qaKey := common .JoinQASubKeys (common .ConfigRepoKeysKey , `"` + domain + `"` , "key" )
249
- desc := fmt .Sprintf ("Select the key to use for the git domain %s :" , domain )
250
- hints := []string {fmt .Sprintf ("If none of the keys are correct, select %s " , noAnswer )}
256
+ desc := fmt .Sprintf ("Select the key to use for the git domain '%s' :" , domain )
257
+ hints := []string {fmt .Sprintf ("If none of the keys are correct, select '%s' " , noAnswer )}
251
258
filename := qaengine .FetchSelectAnswer (qaKey , desc , hints , noAnswer , filenames , nil )
252
259
if filename == noAnswer {
253
- logrus .Debugf ("No key selected for domain %s " , domain )
260
+ logrus .Debugf ("No key was selected for domain '%s' " , domain )
254
261
return "" , false
255
262
}
256
263
257
264
logrus .Debug ("Loading the key" , filename )
258
- key , err := loadSSHKey (filename )
265
+ key , err := loadSSHPrivateKey (filename )
259
266
if err != nil {
260
- logrus .Warnf ("Failed to load the key %q Error %q" , filename , err )
267
+ logrus .Warnf ("Failed to load the SSH private key file '%s' . Error %q" , filename , err )
261
268
return "" , false
262
269
}
263
270
return key , true
264
271
}
265
-
266
- func validatePEMPrivateKey (key string ) error {
267
- block , _ := pem .Decode ([]byte (key ))
268
- if block == nil || block .Type != "RSA PRIVATE KEY" {
269
- return fmt .Errorf ("invalid PEM private key format" )
270
- }
271
-
272
- _ , err := x509 .ParsePKCS1PrivateKey (block .Bytes )
273
- if err != nil {
274
- return err
275
- }
276
-
277
- return nil
278
- }
0 commit comments