github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/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/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/names" 13 "github.com/juju/utils" 14 "github.com/juju/utils/set" 15 "github.com/juju/utils/ssh" 16 17 "github.com/juju/juju/apiserver/common" 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/state" 21 ) 22 23 var logger = loggo.GetLogger("juju.apiserver.keymanager") 24 25 func init() { 26 common.RegisterStandardFacade("KeyManager", 1, NewKeyManagerAPI) 27 } 28 29 // KeyManager defines the methods on the keymanager API end point. 30 type KeyManager interface { 31 ListKeys(arg params.ListSSHKeys) (params.StringsResults, error) 32 AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) 33 DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) 34 ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) 35 } 36 37 // KeyManagerAPI implements the KeyUpdater interface and is the concrete 38 // implementation of the api end point. 39 type KeyManagerAPI struct { 40 state *state.State 41 resources *common.Resources 42 authorizer common.Authorizer 43 canRead func(string) bool 44 canWrite func(string) bool 45 check *common.BlockChecker 46 } 47 48 var _ KeyManager = (*KeyManagerAPI)(nil) 49 50 // NewKeyManagerAPI creates a new server-side keyupdater API end point. 51 func NewKeyManagerAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer) (*KeyManagerAPI, error) { 52 // Only clients and environment managers can access the key manager service. 53 if !authorizer.AuthClient() && !authorizer.AuthModelManager() { 54 return nil, common.ErrPerm 55 } 56 env, err := st.Model() 57 if err != nil { 58 return nil, errors.Trace(err) 59 } 60 // For gccgo interface comparisons, we need a Tag. 61 owner := names.Tag(env.Owner()) 62 // TODO(wallyworld) - replace stub with real canRead function 63 // For now, only admins can read authorised ssh keys. 64 canRead := func(user string) bool { 65 // Are we a machine agent operating as the system identity? 66 if user == config.JujuSystemKey { 67 _, ismachinetag := authorizer.GetAuthTag().(names.MachineTag) 68 return ismachinetag 69 } 70 return authorizer.GetAuthTag() == owner 71 } 72 // TODO(wallyworld) - replace stub with real canWrite function 73 // For now, only admins can write authorised ssh keys for users. 74 // Machine agents can write the juju-system-key. 75 canWrite := func(user string) bool { 76 // Are we a machine agent writing the Juju system key. 77 if user == config.JujuSystemKey { 78 _, ismachinetag := authorizer.GetAuthTag().(names.MachineTag) 79 return ismachinetag 80 } 81 // No point looking to see if the user exists as we are not 82 // yet storing keys on the user. 83 return authorizer.GetAuthTag() == owner 84 } 85 return &KeyManagerAPI{ 86 state: st, 87 resources: resources, 88 authorizer: authorizer, 89 canRead: canRead, 90 canWrite: canWrite, 91 check: common.NewBlockChecker(st), 92 }, nil 93 } 94 95 // ListKeys returns the authorised ssh keys for the specified users. 96 func (api *KeyManagerAPI) ListKeys(arg params.ListSSHKeys) (params.StringsResults, error) { 97 if len(arg.Entities.Entities) == 0 { 98 return params.StringsResults{}, nil 99 } 100 results := make([]params.StringsResult, len(arg.Entities.Entities)) 101 102 // For now, authorised keys are global, common to all users. 103 var keyInfo []string 104 cfg, configErr := api.state.ModelConfig() 105 if configErr == nil { 106 keys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) 107 keyInfo = parseKeys(keys, arg.Mode) 108 } 109 110 for i, entity := range arg.Entities.Entities { 111 // NOTE: entity.Tag isn't a tag, but a username. 112 if !api.canRead(entity.Tag) { 113 results[i].Error = common.ServerError(common.ErrPerm) 114 continue 115 } 116 // All keys are global, no need to look up the user. 117 if configErr == nil { 118 results[i].Result = keyInfo 119 } 120 results[i].Error = common.ServerError(configErr) 121 } 122 return params.StringsResults{Results: results}, nil 123 } 124 125 func parseKeys(keys []string, mode ssh.ListMode) (keyInfo []string) { 126 for _, key := range keys { 127 fingerprint, comment, err := ssh.KeyFingerprint(key) 128 if err != nil { 129 keyInfo = append(keyInfo, fmt.Sprintf("Invalid key: %v", key)) 130 } else { 131 if mode == ssh.FullKeys { 132 keyInfo = append(keyInfo, key) 133 } else { 134 shortKey := fingerprint 135 if comment != "" { 136 shortKey += fmt.Sprintf(" (%s)", comment) 137 } 138 keyInfo = append(keyInfo, shortKey) 139 } 140 } 141 } 142 return keyInfo 143 } 144 145 func (api *KeyManagerAPI) writeSSHKeys(sshKeys []string) error { 146 // Write out the new keys. 147 keyStr := strings.Join(sshKeys, "\n") 148 attrs := map[string]interface{}{config.AuthKeysConfig: keyStr} 149 // TODO(waigani) 2014-03-17 bug #1293324 150 // Pass in validation to ensure SSH keys 151 // have not changed underfoot 152 err := api.state.UpdateModelConfig(attrs, nil, nil) 153 if err != nil { 154 return fmt.Errorf("writing environ config: %v", err) 155 } 156 return nil 157 } 158 159 // currentKeyDataForAdd gathers data used when adding ssh keys. 160 func (api *KeyManagerAPI) currentKeyDataForAdd() (keys []string, fingerprints set.Strings, err error) { 161 fingerprints = make(set.Strings) 162 cfg, err := api.state.ModelConfig() 163 if err != nil { 164 return nil, nil, fmt.Errorf("reading current key data: %v", err) 165 } 166 keys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) 167 for _, key := range keys { 168 fingerprint, _, err := ssh.KeyFingerprint(key) 169 if err != nil { 170 logger.Warningf("ignoring invalid ssh key %q: %v", key, err) 171 } 172 fingerprints.Add(fingerprint) 173 } 174 return keys, fingerprints, nil 175 } 176 177 // AddKeys adds new authorised ssh keys for the specified user. 178 func (api *KeyManagerAPI) AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 179 if err := api.check.ChangeAllowed(); err != nil { 180 return params.ErrorResults{}, errors.Trace(err) 181 } 182 result := params.ErrorResults{ 183 Results: make([]params.ErrorResult, len(arg.Keys)), 184 } 185 if len(arg.Keys) == 0 { 186 return result, nil 187 } 188 189 if !api.canWrite(arg.User) { 190 return params.ErrorResults{}, common.ServerError(common.ErrPerm) 191 } 192 193 // For now, authorised keys are global, common to all users. 194 sshKeys, currentFingerprints, err := api.currentKeyDataForAdd() 195 if err != nil { 196 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 197 } 198 199 // Ensure we are not going to add invalid or duplicate keys. 200 result.Results = make([]params.ErrorResult, len(arg.Keys)) 201 for i, key := range arg.Keys { 202 fingerprint, _, err := ssh.KeyFingerprint(key) 203 if err != nil { 204 result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", key)) 205 continue 206 } 207 if currentFingerprints.Contains(fingerprint) { 208 result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", key)) 209 continue 210 } 211 sshKeys = append(sshKeys, key) 212 } 213 err = api.writeSSHKeys(sshKeys) 214 if err != nil { 215 return params.ErrorResults{}, common.ServerError(err) 216 } 217 return result, nil 218 } 219 220 type importedSSHKey struct { 221 key string 222 fingerprint string 223 err error 224 } 225 226 // Override for testing 227 var RunSSHImportId = runSSHImportId 228 229 func runSSHImportId(keyId string) (string, error) { 230 return utils.RunCommand("ssh-import-id", "-o", "-", keyId) 231 } 232 233 // runSSHKeyImport uses ssh-import-id to find the ssh keys for the specified key ids. 234 func runSSHKeyImport(keyIds []string) map[string][]importedSSHKey { 235 importResults := make(map[string][]importedSSHKey, len(keyIds)) 236 for _, keyId := range keyIds { 237 keyInfo := []importedSSHKey{} 238 output, err := RunSSHImportId(keyId) 239 if err != nil { 240 keyInfo = append(keyInfo, importedSSHKey{err: err}) 241 continue 242 } 243 lines := strings.Split(output, "\n") 244 hasKey := false 245 for _, line := range lines { 246 if !strings.HasPrefix(line, "ssh-") { 247 continue 248 } 249 hasKey = true 250 // ignore key comment (e.g., user@host) 251 fingerprint, _, err := ssh.KeyFingerprint(line) 252 keyInfo = append(keyInfo, importedSSHKey{ 253 key: line, 254 fingerprint: fingerprint, 255 err: errors.Annotatef(err, "invalid ssh key for %s", keyId), 256 }) 257 } 258 if !hasKey { 259 keyInfo = append(keyInfo, importedSSHKey{ 260 err: errors.Errorf("invalid ssh key id: %s", keyId), 261 }) 262 } 263 importResults[keyId] = keyInfo 264 } 265 return importResults 266 } 267 268 // ImportKeys imports new authorised ssh keys from the specified key ids for the specified user. 269 func (api *KeyManagerAPI) ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 270 if err := api.check.ChangeAllowed(); err != nil { 271 return params.ErrorResults{}, errors.Trace(err) 272 } 273 result := params.ErrorResults{ 274 Results: make([]params.ErrorResult, len(arg.Keys)), 275 } 276 if len(arg.Keys) == 0 { 277 return result, nil 278 } 279 280 if !api.canWrite(arg.User) { 281 return params.ErrorResults{}, common.ServerError(common.ErrPerm) 282 } 283 284 // For now, authorised keys are global, common to all users. 285 sshKeys, currentFingerprints, err := api.currentKeyDataForAdd() 286 if err != nil { 287 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 288 } 289 290 importedKeyInfo := runSSHKeyImport(arg.Keys) 291 // Ensure we are not going to add invalid or duplicate keys. 292 result.Results = make([]params.ErrorResult, len(importedKeyInfo)) 293 for i, key := range arg.Keys { 294 compoundErr := "" 295 for _, keyInfo := range importedKeyInfo[key] { 296 if keyInfo.err != nil { 297 compoundErr += fmt.Sprintf("%v\n", keyInfo.err) 298 continue 299 } 300 if currentFingerprints.Contains(keyInfo.fingerprint) { 301 compoundErr += fmt.Sprintf("%v\n", errors.Errorf("duplicate ssh key: %s", keyInfo.key)) 302 continue 303 } 304 sshKeys = append(sshKeys, keyInfo.key) 305 } 306 if compoundErr != "" { 307 result.Results[i].Error = common.ServerError(errors.Errorf(strings.TrimSuffix(compoundErr, "\n"))) 308 } 309 310 } 311 err = api.writeSSHKeys(sshKeys) 312 if err != nil { 313 return params.ErrorResults{}, common.ServerError(err) 314 } 315 return result, nil 316 } 317 318 // currentKeyDataForDelete gathers data used when deleting ssh keys. 319 func (api *KeyManagerAPI) currentKeyDataForDelete() ( 320 keys map[string]string, invalidKeys []string, comments map[string]string, err error) { 321 322 cfg, err := api.state.ModelConfig() 323 if err != nil { 324 return nil, nil, nil, fmt.Errorf("reading current key data: %v", err) 325 } 326 // For now, authorised keys are global, common to all users. 327 existingSSHKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) 328 329 // Build up a map of keys indexed by fingerprint, and fingerprints indexed by comment 330 // so we can easily get the key represented by each keyId, which may be either a fingerprint 331 // or comment. 332 keys = make(map[string]string) 333 comments = make(map[string]string) 334 for _, key := range existingSSHKeys { 335 fingerprint, comment, err := ssh.KeyFingerprint(key) 336 if err != nil { 337 logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err) 338 invalidKeys = append(invalidKeys, key) 339 continue 340 } 341 keys[fingerprint] = key 342 if comment != "" { 343 comments[comment] = fingerprint 344 } 345 } 346 return keys, invalidKeys, comments, nil 347 } 348 349 // DeleteKeys deletes the authorised ssh keys for the specified user. 350 func (api *KeyManagerAPI) DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 351 if err := api.check.ChangeAllowed(); err != nil { 352 return params.ErrorResults{}, errors.Trace(err) 353 } 354 result := params.ErrorResults{ 355 Results: make([]params.ErrorResult, len(arg.Keys)), 356 } 357 if len(arg.Keys) == 0 { 358 return result, nil 359 } 360 361 if !api.canWrite(arg.User) { 362 return params.ErrorResults{}, common.ServerError(common.ErrPerm) 363 } 364 365 sshKeys, invalidKeys, keyComments, err := api.currentKeyDataForDelete() 366 if err != nil { 367 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 368 } 369 370 // We keep all existing invalid keys. 371 keysToWrite := invalidKeys 372 373 // Find the keys corresponding to the specified key fingerprints or comments. 374 for i, keyId := range arg.Keys { 375 // assume keyId may be a fingerprint 376 fingerprint := keyId 377 _, ok := sshKeys[keyId] 378 if !ok { 379 // keyId is a comment 380 fingerprint, ok = keyComments[keyId] 381 } 382 if !ok { 383 result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", keyId)) 384 } 385 // We found the key to delete so remove it from those we wish to keep. 386 delete(sshKeys, fingerprint) 387 } 388 for _, key := range sshKeys { 389 keysToWrite = append(keysToWrite, key) 390 } 391 if len(keysToWrite) == 0 { 392 return params.ErrorResults{}, common.ServerError(fmt.Errorf("cannot delete all keys")) 393 } 394 395 err = api.writeSSHKeys(keysToWrite) 396 if err != nil { 397 return params.ErrorResults{}, common.ServerError(err) 398 } 399 return result, nil 400 }