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