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