github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/environs/config" 19 "github.com/juju/juju/state" 20 "github.com/juju/juju/utils/ssh" 21 ) 22 23 var logger = loggo.GetLogger("juju.apiserver.keymanager") 24 25 func init() { 26 common.RegisterStandardFacade("KeyManager", 0, 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 // KeyUpdaterAPI 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.AuthEnvironManager() { 54 return nil, common.ErrPerm 55 } 56 env, err := st.Environment() 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.EnvironConfig() 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.UpdateEnvironConfig(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.EnvironConfig() 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) []importedSSHKey { 235 keyInfo := make([]importedSSHKey, len(keyIds)) 236 for i, keyId := range keyIds { 237 output, err := RunSSHImportId(keyId) 238 if err != nil { 239 keyInfo[i].err = err 240 continue 241 } 242 lines := strings.Split(output, "\n") 243 for _, line := range lines { 244 if !strings.HasPrefix(line, "ssh-") { 245 continue 246 } 247 keyInfo[i].fingerprint, _, keyInfo[i].err = ssh.KeyFingerprint(line) 248 if err == nil { 249 keyInfo[i].key = line 250 } 251 } 252 if keyInfo[i].key == "" { 253 keyInfo[i].err = fmt.Errorf("invalid ssh key id: %s", keyId) 254 } 255 } 256 return keyInfo 257 } 258 259 // ImportKeys imports new authorised ssh keys from the specified key ids for the specified user. 260 func (api *KeyManagerAPI) ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 261 if err := api.check.ChangeAllowed(); err != nil { 262 return params.ErrorResults{}, errors.Trace(err) 263 } 264 result := params.ErrorResults{ 265 Results: make([]params.ErrorResult, len(arg.Keys)), 266 } 267 if len(arg.Keys) == 0 { 268 return result, nil 269 } 270 271 if !api.canWrite(arg.User) { 272 return params.ErrorResults{}, common.ServerError(common.ErrPerm) 273 } 274 275 // For now, authorised keys are global, common to all users. 276 sshKeys, currentFingerprints, err := api.currentKeyDataForAdd() 277 if err != nil { 278 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 279 } 280 281 importedKeyInfo := runSSHKeyImport(arg.Keys) 282 // Ensure we are not going to add invalid or duplicate keys. 283 result.Results = make([]params.ErrorResult, len(importedKeyInfo)) 284 for i, keyInfo := range importedKeyInfo { 285 if keyInfo.err != nil { 286 result.Results[i].Error = common.ServerError(keyInfo.err) 287 continue 288 } 289 if currentFingerprints.Contains(keyInfo.fingerprint) { 290 result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", keyInfo.key)) 291 continue 292 } 293 sshKeys = append(sshKeys, keyInfo.key) 294 } 295 err = api.writeSSHKeys(sshKeys) 296 if err != nil { 297 return params.ErrorResults{}, common.ServerError(err) 298 } 299 return result, nil 300 } 301 302 // currentKeyDataForDelete gathers data used when deleting ssh keys. 303 func (api *KeyManagerAPI) currentKeyDataForDelete() ( 304 keys map[string]string, invalidKeys []string, comments map[string]string, err error) { 305 306 cfg, err := api.state.EnvironConfig() 307 if err != nil { 308 return nil, nil, nil, fmt.Errorf("reading current key data: %v", err) 309 } 310 // For now, authorised keys are global, common to all users. 311 existingSSHKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) 312 313 // Build up a map of keys indexed by fingerprint, and fingerprints indexed by comment 314 // so we can easily get the key represented by each keyId, which may be either a fingerprint 315 // or comment. 316 keys = make(map[string]string) 317 comments = make(map[string]string) 318 for _, key := range existingSSHKeys { 319 fingerprint, comment, err := ssh.KeyFingerprint(key) 320 if err != nil { 321 logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err) 322 invalidKeys = append(invalidKeys, key) 323 continue 324 } 325 keys[fingerprint] = key 326 if comment != "" { 327 comments[comment] = fingerprint 328 } 329 } 330 return keys, invalidKeys, comments, nil 331 } 332 333 // DeleteKeys deletes the authorised ssh keys for the specified user. 334 func (api *KeyManagerAPI) DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { 335 if err := api.check.ChangeAllowed(); err != nil { 336 return params.ErrorResults{}, errors.Trace(err) 337 } 338 result := params.ErrorResults{ 339 Results: make([]params.ErrorResult, len(arg.Keys)), 340 } 341 if len(arg.Keys) == 0 { 342 return result, nil 343 } 344 345 if !api.canWrite(arg.User) { 346 return params.ErrorResults{}, common.ServerError(common.ErrPerm) 347 } 348 349 sshKeys, invalidKeys, keyComments, err := api.currentKeyDataForDelete() 350 if err != nil { 351 return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) 352 } 353 354 // We keep all existing invalid keys. 355 keysToWrite := invalidKeys 356 357 // Find the keys corresponding to the specified key fingerprints or comments. 358 for i, keyId := range arg.Keys { 359 // assume keyId may be a fingerprint 360 fingerprint := keyId 361 _, ok := sshKeys[keyId] 362 if !ok { 363 // keyId is a comment 364 fingerprint, ok = keyComments[keyId] 365 } 366 if !ok { 367 result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", keyId)) 368 } 369 // We found the key to delete so remove it from those we wish to keep. 370 delete(sshKeys, fingerprint) 371 } 372 for _, key := range sshKeys { 373 keysToWrite = append(keysToWrite, key) 374 } 375 if len(keysToWrite) == 0 { 376 return params.ErrorResults{}, common.ServerError(fmt.Errorf("cannot delete all keys")) 377 } 378 379 err = api.writeSSHKeys(keysToWrite) 380 if err != nil { 381 return params.ErrorResults{}, common.ServerError(err) 382 } 383 return result, nil 384 }