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