github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/keymanager/keymanager.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package keymanager 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/utils" 14 "github.com/juju/utils/ssh" 15 "gopkg.in/juju/names.v2" 16 17 "github.com/juju/juju/apiserver/common" 18 "github.com/juju/juju/apiserver/facade" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/environs/config" 21 "github.com/juju/juju/permission" 22 "github.com/juju/juju/state" 23 ) 24 25 var logger = loggo.GetLogger("juju.apiserver.keymanager") 26 27 // The comment values used by juju internal ssh keys. 28 var internalComments = set.NewStrings([]string{"juju-client-key", "juju-system-key"}...) 29 30 // KeyManager defines the methods on the keymanager API end point. 31 type KeyManager interface { 32 ListKeys(arg params.ListSSHKeys) (params.StringsResults, error) 33 AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) 34 DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) 35 ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) 36 } 37 38 // KeyManagerAPI implements the KeyUpdater interface and is the concrete 39 // implementation of the api end point. 40 type KeyManagerAPI struct { 41 state *state.State 42 model *state.Model 43 resources facade.Resources 44 authorizer facade.Authorizer 45 apiUser names.UserTag 46 check *common.BlockChecker 47 } 48 49 var _ KeyManager = (*KeyManagerAPI)(nil) 50 51 // NewKeyManagerAPI creates a new server-side keyupdater API end point. 52 func NewKeyManagerAPI(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*KeyManagerAPI, error) { 53 // Only clients can access the key manager service. 54 if !authorizer.AuthClient() { 55 return nil, common.ErrPerm 56 } 57 m, err := st.Model() 58 if err != nil { 59 return nil, errors.Trace(err) 60 } 61 return &KeyManagerAPI{ 62 state: st, 63 model: m, 64 resources: resources, 65 authorizer: authorizer, 66 apiUser: authorizer.GetAuthTag().(names.UserTag), 67 check: common.NewBlockChecker(st), 68 }, nil 69 } 70 71 func (api *KeyManagerAPI) checkCanRead(sshUser string) error { 72 if err := api.checkCanWrite(sshUser); err == nil { 73 return nil 74 } else if err != common.ErrPerm { 75 return errors.Trace(err) 76 } 77 if sshUser == config.JujuSystemKey { 78 // users cannot read the system key. 79 return common.ErrPerm 80 } 81 ok, err := common.HasPermission( 82 api.state.UserPermission, 83 api.apiUser, 84 permission.ReadAccess, 85 api.model.ModelTag(), 86 ) 87 if err != nil { 88 return errors.Trace(err) 89 } 90 if !ok { 91 return common.ErrPerm 92 } 93 return nil 94 } 95 96 func (api *KeyManagerAPI) checkCanWrite(sshUser string) error { 97 if sshUser == config.JujuSystemKey { 98 // users cannot modify the system key. 99 return common.ErrPerm 100 } 101 model, err := api.state.Model() 102 if err != nil { 103 return errors.Trace(err) 104 } 105 ok, err := common.HasModelAdmin(api.authorizer, api.apiUser, api.state.ControllerTag(), model) 106 if err != nil { 107 return errors.Trace(err) 108 } 109 if !ok { 110 return common.ErrPerm 111 } 112 return nil 113 } 114 115 // ListKeys returns the authorised ssh keys for the specified users. 116 func (api *KeyManagerAPI) ListKeys(arg params.ListSSHKeys) (params.StringsResults, error) { 117 if len(arg.Entities.Entities) == 0 { 118 return params.StringsResults{}, nil 119 } 120 results := make([]params.StringsResult, len(arg.Entities.Entities)) 121 122 // For now, authorised keys are global, common to all users. 123 var keyInfo []string 124 cfg, configErr := api.model.ModelConfig() 125 if configErr == nil { 126 keys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) 127 keyInfo = parseKeys(keys, arg.Mode) 128 } 129 130 for i, entity := range arg.Entities.Entities { 131 // NOTE: entity.Tag isn't a tag, but a username. 132 if err := api.checkCanRead(entity.Tag); err != nil { 133 results[i].Error = common.ServerError(err) 134 continue 135 } 136 // All keys are global, no need to look up the user. 137 if configErr == nil { 138 results[i].Result = keyInfo 139 } 140 results[i].Error = common.ServerError(configErr) 141 } 142 return params.StringsResults{Results: results}, nil 143 } 144 145 func parseKeys(keys []string, mode ssh.ListMode) (keyInfo []string) { 146 for _, key := range keys { 147 fingerprint, comment, err := ssh.KeyFingerprint(key) 148 if err != nil { 149 keyInfo = append(keyInfo, fmt.Sprintf("Invalid key: %v", key)) 150 continue 151 } 152 // Only including user added keys not internal ones. 153 if internalComments.Contains(comment) { 154 continue 155 } 156 if mode == ssh.FullKeys { 157 keyInfo = append(keyInfo, key) 158 } else { 159 shortKey := fingerprint 160 if comment != "" { 161 shortKey += fmt.Sprintf(" (%s)", comment) 162 } 163 keyInfo = append(keyInfo, shortKey) 164 } 165 } 166 return keyInfo 167 } 168 169 func (api *KeyManagerAPI) writeSSHKeys(sshKeys []string) error { 170 // Write out the new keys. 171 keyStr := strings.Join(sshKeys, "\n") 172 attrs := map[string]interface{}{config.AuthorizedKeysKey: keyStr} 173 // TODO(waigani) 2014-03-17 bug #1293324 174 // Pass in validation to ensure SSH keys 175 // have not changed underfoot 176 err := api.model.UpdateModelConfig(attrs, nil) 177 if err != nil { 178 return fmt.Errorf("writing environ config: %v", err) 179 } 180 return nil 181 } 182 183 // currentKeyDataForAdd gathers data used when adding ssh keys. 184 func (api *KeyManagerAPI) currentKeyDataForAdd() (keys []string, fingerprints set.Strings, err error) { 185 fingerprints = make(set.Strings) 186 cfg, err := api.model.ModelConfig() 187 if err != nil { 188 return nil, nil, fmt.Errorf("reading current key data: %v", err) 189 } 190 keys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) 191 for _, key := range keys { 192 fingerprint, _, err := ssh.KeyFingerprint(key) 193 if err != nil { 194 logger.Warningf("ignoring invalid ssh key %q: %v", key, err) 195 } 196 fingerprints.Add(fingerprint) 197 } 198 return keys, fingerprints, nil 199 } 200 201 // AddKeys adds new authorised ssh keys for the specified user. 202 func (api *KeyManagerAPI) AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 203 if err := api.check.ChangeAllowed(); err != nil { 204 return params.ErrorResults{}, errors.Trace(err) 205 } 206 result := params.ErrorResults{ 207 Results: make([]params.ErrorResult, len(arg.Keys)), 208 } 209 if len(arg.Keys) == 0 { 210 return result, nil 211 } 212 213 if err := api.checkCanWrite(arg.User); err != nil { 214 return params.ErrorResults{}, common.ServerError(err) 215 } 216 217 // For now, authorised keys are global, common to all users. 218 sshKeys, currentFingerprints, err := api.currentKeyDataForAdd() 219 if err != nil { 220 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 221 } 222 223 // Ensure we are not going to add invalid or duplicate keys. 224 result.Results = make([]params.ErrorResult, len(arg.Keys)) 225 for i, key := range arg.Keys { 226 fingerprint, _, err := ssh.KeyFingerprint(key) 227 if err != nil { 228 result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", key)) 229 continue 230 } 231 if currentFingerprints.Contains(fingerprint) { 232 result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", key)) 233 continue 234 } 235 sshKeys = append(sshKeys, key) 236 } 237 err = api.writeSSHKeys(sshKeys) 238 if err != nil { 239 return params.ErrorResults{}, common.ServerError(err) 240 } 241 return result, nil 242 } 243 244 type importedSSHKey struct { 245 key string 246 fingerprint string 247 err error 248 } 249 250 // Override for testing 251 var RunSSHImportId = runSSHImportId 252 253 func runSSHImportId(keyId string) (string, error) { 254 return utils.RunCommand("ssh-import-id", "-o", "-", keyId) 255 } 256 257 // runSSHKeyImport uses ssh-import-id to find the ssh keys for the specified key ids. 258 func runSSHKeyImport(keyIds []string) map[string][]importedSSHKey { 259 importResults := make(map[string][]importedSSHKey, len(keyIds)) 260 for _, keyId := range keyIds { 261 keyInfo := []importedSSHKey{} 262 output, err := RunSSHImportId(keyId) 263 if err != nil { 264 keyInfo = append(keyInfo, importedSSHKey{err: err}) 265 continue 266 } 267 lines := strings.Split(output, "\n") 268 hasKey := false 269 for _, line := range lines { 270 if !strings.HasPrefix(line, "ssh-") { 271 continue 272 } 273 hasKey = true 274 // ignore key comment (e.g., user@host) 275 fingerprint, _, err := ssh.KeyFingerprint(line) 276 keyInfo = append(keyInfo, importedSSHKey{ 277 key: line, 278 fingerprint: fingerprint, 279 err: errors.Annotatef(err, "invalid ssh key for %s", keyId), 280 }) 281 } 282 if !hasKey { 283 keyInfo = append(keyInfo, importedSSHKey{ 284 err: errors.Errorf("invalid ssh key id: %s", keyId), 285 }) 286 } 287 importResults[keyId] = keyInfo 288 } 289 return importResults 290 } 291 292 // ImportKeys imports new authorised ssh keys from the specified key ids for the specified user. 293 func (api *KeyManagerAPI) ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 294 if err := api.check.ChangeAllowed(); err != nil { 295 return params.ErrorResults{}, errors.Trace(err) 296 } 297 result := params.ErrorResults{ 298 Results: make([]params.ErrorResult, len(arg.Keys)), 299 } 300 if len(arg.Keys) == 0 { 301 return result, nil 302 } 303 304 if err := api.checkCanWrite(arg.User); err != nil { 305 return params.ErrorResults{}, common.ServerError(err) 306 } 307 308 // For now, authorised keys are global, common to all users. 309 sshKeys, currentFingerprints, err := api.currentKeyDataForAdd() 310 if err != nil { 311 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 312 } 313 314 importedKeyInfo := runSSHKeyImport(arg.Keys) 315 // Ensure we are not going to add invalid or duplicate keys. 316 result.Results = make([]params.ErrorResult, len(importedKeyInfo)) 317 for i, key := range arg.Keys { 318 compoundErr := "" 319 for _, keyInfo := range importedKeyInfo[key] { 320 if keyInfo.err != nil { 321 compoundErr += fmt.Sprintf("%v\n", keyInfo.err) 322 continue 323 } 324 if currentFingerprints.Contains(keyInfo.fingerprint) { 325 compoundErr += fmt.Sprintf("%v\n", errors.Errorf("duplicate ssh key: %s", keyInfo.key)) 326 continue 327 } 328 sshKeys = append(sshKeys, keyInfo.key) 329 } 330 if compoundErr != "" { 331 result.Results[i].Error = common.ServerError(errors.Errorf(strings.TrimSuffix(compoundErr, "\n"))) 332 } 333 334 } 335 err = api.writeSSHKeys(sshKeys) 336 if err != nil { 337 return params.ErrorResults{}, common.ServerError(err) 338 } 339 return result, nil 340 } 341 342 // currentKeyDataForDelete gathers data used when deleting ssh keys. 343 func (api *KeyManagerAPI) currentKeyDataForDelete() ( 344 currentKeys []string, byFingerprint map[string]string, byComment map[string]string, err error) { 345 346 cfg, err := api.model.ModelConfig() 347 if err != nil { 348 return nil, nil, nil, fmt.Errorf("reading current key data: %v", err) 349 } 350 // For now, authorised keys are global, common to all users. 351 currentKeys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) 352 353 // Make two maps that index keys by fingerprint and by comment for fast 354 // lookup of keys to delete which may be given as either. 355 byFingerprint = make(map[string]string) 356 byComment = make(map[string]string) 357 for _, key := range currentKeys { 358 fingerprint, comment, err := ssh.KeyFingerprint(key) 359 if err != nil { 360 logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err) 361 continue 362 } 363 byFingerprint[fingerprint] = key 364 if comment != "" { 365 byComment[comment] = key 366 } 367 } 368 return currentKeys, byFingerprint, byComment, nil 369 } 370 371 // DeleteKeys deletes the authorised ssh keys for the specified user. 372 func (api *KeyManagerAPI) DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 373 if err := api.check.ChangeAllowed(); err != nil { 374 return params.ErrorResults{}, errors.Trace(err) 375 } 376 result := params.ErrorResults{ 377 Results: make([]params.ErrorResult, len(arg.Keys)), 378 } 379 if len(arg.Keys) == 0 { 380 return result, nil 381 } 382 383 if err := api.checkCanWrite(arg.User); err != nil { 384 return params.ErrorResults{}, common.ServerError(err) 385 } 386 387 allKeys, byFingerprint, byComment, err := api.currentKeyDataForDelete() 388 if err != nil { 389 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 390 } 391 392 // Record the keys to be deleted in the second pass. 393 keysToDelete := make(set.Strings) 394 395 // Find the keys corresponding to the specified key fingerprints or comments. 396 for i, keyId := range arg.Keys { 397 // Is given keyId a fingerprint? 398 key, ok := byFingerprint[keyId] 399 if ok { 400 keysToDelete.Add(key) 401 continue 402 } 403 // Not a fingerprint, is it a comment? 404 key, ok = byComment[keyId] 405 if ok { 406 if internalComments.Contains(keyId) { 407 result.Results[i].Error = common.ServerError(fmt.Errorf("may not delete internal key: %s", keyId)) 408 continue 409 } 410 keysToDelete.Add(key) 411 continue 412 } 413 result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", keyId)) 414 } 415 416 var keysToWrite []string 417 418 // Add back only the keys that are not deleted, preserving the order. 419 for _, key := range allKeys { 420 if !keysToDelete.Contains(key) { 421 keysToWrite = append(keysToWrite, key) 422 } 423 } 424 425 if len(keysToWrite) == 0 { 426 return params.ErrorResults{}, common.ServerError(fmt.Errorf("cannot delete all keys")) 427 } 428 429 err = api.writeSSHKeys(keysToWrite) 430 if err != nil { 431 return params.ErrorResults{}, common.ServerError(err) 432 } 433 return result, nil 434 }