1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
23
var logger = loggo.GetLogger("juju.state.apiserver.keymanager")
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)
33
// KeyUpdaterAPI implements the KeyUpdater interface and is the concrete
34
// implementation of the api end point.
35
type KeyManagerAPI struct {
37
resources *common.Resources
38
authorizer common.Authorizer
39
getCanRead common.GetAuthFunc
40
getCanWrite common.GetAuthFunc
43
var _ KeyManager = (*KeyManagerAPI)(nil)
45
// NewKeyUpdaterAPI creates a new server-side keyupdater API end point.
46
func NewKeyManagerAPI(
48
resources *common.Resources,
49
authorizer common.Authorizer,
50
) (*KeyManagerAPI, error) {
51
// Only clients can access the key manager service.
52
if !authorizer.AuthClient() {
53
return nil, common.ErrPerm
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"
62
// TODO(wallyworld) - replace stub with real canRead function
63
// For now, only admins can write authorised ssh keys.
64
getCanWrite := func() (common.AuthFunc, error) {
65
return func(tag string) bool {
66
if _, err := st.User(tag); err != nil {
69
return authorizer.GetAuthTag() == "user-admin"
72
return &KeyManagerAPI{
73
state: st, resources: resources, authorizer: authorizer, getCanRead: getCanRead, getCanWrite: getCanWrite}, nil
76
// ListKeys returns the authorised ssh keys for the specified users.
77
func (api *KeyManagerAPI) ListKeys(arg params.ListSSHKeys) (params.StringsResults, error) {
78
if len(arg.Entities.Entities) == 0 {
79
return params.StringsResults{}, nil
81
results := make([]params.StringsResult, len(arg.Entities.Entities))
83
// For now, authorised keys are global, common to all users.
85
cfg, configErr := api.state.EnvironConfig()
87
keys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
88
keyInfo = parseKeys(keys, arg.Mode)
91
canRead, err := api.getCanRead()
93
return params.StringsResults{}, err
95
for i, entity := range arg.Entities.Entities {
96
if !canRead(entity.Tag) {
97
results[i].Error = common.ServerError(common.ErrPerm)
100
if _, err := api.state.User(entity.Tag); err != nil {
101
if errors.IsNotFoundError(err) {
102
results[i].Error = common.ServerError(common.ErrPerm)
104
results[i].Error = common.ServerError(err)
109
if configErr == nil {
110
results[i].Result = keyInfo
114
results[i].Error = common.ServerError(err)
116
return params.StringsResults{results}, nil
119
func parseKeys(keys []string, mode ssh.ListMode) (keyInfo []string) {
120
for _, key := range keys {
121
fingerprint, comment, err := ssh.KeyFingerprint(key)
123
keyInfo = append(keyInfo, fmt.Sprintf("Invalid key: %v", key))
125
if mode == ssh.FullKeys {
126
keyInfo = append(keyInfo, key)
128
shortKey := fingerprint
130
shortKey += fmt.Sprintf(" (%s)", comment)
132
keyInfo = append(keyInfo, shortKey)
139
func (api *KeyManagerAPI) writeSSHKeys(currentConfig *config.Config, sshKeys []string) error {
140
// Write out the new keys.
141
keyStr := strings.Join(sshKeys, "\n")
142
newConfig, err := currentConfig.Apply(map[string]interface{}{"authorized-keys": keyStr})
144
return fmt.Errorf("creating new environ config: %v", err)
146
err = api.state.SetEnvironConfig(newConfig, currentConfig)
148
return fmt.Errorf("writing environ config: %v", err)
153
// currentKeyDataForAdd gathers data used when adding ssh keys.
154
func (api *KeyManagerAPI) currentKeyDataForAdd() (keys []string, fingerprints *set.Strings, cfg *config.Config, err error) {
155
fp := set.NewStrings()
157
cfg, err = api.state.EnvironConfig()
159
return nil, nil, nil, err
161
keys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
162
for _, key := range keys {
163
fingerprint, _, err := ssh.KeyFingerprint(key)
165
logger.Warningf("ignoring invalid ssh key %q: %v", key, err)
167
fingerprints.Add(fingerprint)
169
return keys, fingerprints, cfg, nil
172
// AddKeys adds new authorised ssh keys for the specified user.
173
func (api *KeyManagerAPI) AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
174
result := params.ErrorResults{
175
Results: make([]params.ErrorResult, len(arg.Keys)),
177
if len(arg.Keys) == 0 {
181
canWrite, err := api.getCanWrite()
183
return params.ErrorResults{}, common.ServerError(err)
185
if !canWrite(arg.User) {
186
return params.ErrorResults{}, common.ServerError(common.ErrPerm)
189
// For now, authorised keys are global, common to all users.
190
sshKeys, currentFingerprints, currentConfig, err := api.currentKeyDataForAdd()
192
return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
195
// Ensure we are not going to add invalid or duplicate keys.
196
result.Results = make([]params.ErrorResult, len(arg.Keys))
197
for i, key := range arg.Keys {
198
fingerprint, _, err := ssh.KeyFingerprint(key)
200
result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", key))
203
if currentFingerprints.Contains(fingerprint) {
204
result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", key))
207
sshKeys = append(sshKeys, key)
209
err = api.writeSSHKeys(currentConfig, sshKeys)
211
return params.ErrorResults{}, common.ServerError(err)
216
type importedSSHKey struct {
222
// Override for testing
223
var RunSSHImportId = runSSHImportId
225
func runSSHImportId(keyId string) (string, error) {
226
return utils.RunCommand("ssh-import-id", "-o", "-", keyId)
229
// runSSHKeyImport uses ssh-import-id to find the ssh keys for the specified key ids.
230
func runSSHKeyImport(keyIds []string) []importedSSHKey {
231
keyInfo := make([]importedSSHKey, len(keyIds))
232
for i, keyId := range keyIds {
233
output, err := RunSSHImportId(keyId)
238
lines := strings.Split(output, "\n")
239
for _, line := range lines {
240
if !strings.HasPrefix(line, "ssh-") {
243
keyInfo[i].fingerprint, _, keyInfo[i].err = ssh.KeyFingerprint(line)
245
keyInfo[i].key = line
248
if keyInfo[i].key == "" {
249
keyInfo[i].err = fmt.Errorf("invalid ssh key id: %s", keyId)
255
// ImportKeys imports new authorised ssh keys from the specified key ids for the specified user.
256
func (api *KeyManagerAPI) ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
257
result := params.ErrorResults{
258
Results: make([]params.ErrorResult, len(arg.Keys)),
260
if len(arg.Keys) == 0 {
264
canWrite, err := api.getCanWrite()
266
return params.ErrorResults{}, common.ServerError(err)
268
if !canWrite(arg.User) {
269
return params.ErrorResults{}, common.ServerError(common.ErrPerm)
272
// For now, authorised keys are global, common to all users.
273
sshKeys, currentFingerprints, currentConfig, err := api.currentKeyDataForAdd()
275
return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
278
importedKeyInfo := runSSHKeyImport(arg.Keys)
279
// Ensure we are not going to add invalid or duplicate keys.
280
result.Results = make([]params.ErrorResult, len(importedKeyInfo))
281
for i, keyInfo := range importedKeyInfo {
282
if keyInfo.err != nil {
283
result.Results[i].Error = common.ServerError(keyInfo.err)
286
if currentFingerprints.Contains(keyInfo.fingerprint) {
287
result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", keyInfo.key))
290
sshKeys = append(sshKeys, keyInfo.key)
292
err = api.writeSSHKeys(currentConfig, sshKeys)
294
return params.ErrorResults{}, common.ServerError(err)
299
// currentKeyDataForAdd gathers data used when deleting ssh keys.
300
func (api *KeyManagerAPI) currentKeyDataForDelete() (
301
keys map[string]string, invalidKeys []string, comments map[string]string, cfg *config.Config, err error) {
303
// For now, authorised keys are global, common to all users.
304
cfg, err = api.state.EnvironConfig()
306
return nil, nil, nil, nil, fmt.Errorf("reading current key data: %v", err)
308
existingSSHKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
310
// Build up a map of keys indexed by fingerprint, and fingerprints indexed by comment
311
// so we can easily get the key represented by each keyId, which may be either a fingerprint
313
keys = make(map[string]string)
314
comments = make(map[string]string)
315
for _, key := range existingSSHKeys {
316
fingerprint, comment, err := ssh.KeyFingerprint(key)
318
logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err)
319
invalidKeys = append(invalidKeys, key)
322
keys[fingerprint] = key
324
comments[comment] = fingerprint
327
return keys, invalidKeys, comments, cfg, nil
330
// DeleteKeys deletes the authorised ssh keys for the specified user.
331
func (api *KeyManagerAPI) DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
332
result := params.ErrorResults{
333
Results: make([]params.ErrorResult, len(arg.Keys)),
335
if len(arg.Keys) == 0 {
339
canWrite, err := api.getCanWrite()
341
return params.ErrorResults{}, common.ServerError(err)
343
if !canWrite(arg.User) {
344
return params.ErrorResults{}, common.ServerError(common.ErrPerm)
347
sshKeys, invalidKeys, keyComments, currentConfig, err := api.currentKeyDataForDelete()
349
return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
352
// We keep all existing invalid keys.
353
keysToWrite := invalidKeys
355
// Find the keys corresponding to the specified key fingerprints or comments.
356
for i, keyId := range arg.Keys {
357
// assume keyId may be a fingerprint
359
_, ok := sshKeys[keyId]
361
// keyId is a comment
362
fingerprint, ok = keyComments[keyId]
365
result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", keyId))
367
// We found the key to delete so remove it from those we wish to keep.
368
delete(sshKeys, fingerprint)
370
for _, key := range sshKeys {
371
keysToWrite = append(keysToWrite, key)
373
if len(keysToWrite) == 0 {
374
return params.ErrorResults{}, common.ServerError(stderrors.New("cannot delete all keys"))
377
err = api.writeSSHKeys(currentConfig, keysToWrite)
379
return params.ErrorResults{}, common.ServerError(err)