~dstroppa/juju-core/joyent-provider-storage

« back to all changes in this revision

Viewing changes to state/apiserver/keymanager/keymanager.go

  • Committer: Daniele Stroppa
  • Date: 2014-01-08 15:58:10 UTC
  • mfrom: (1953.1.231 juju-core)
  • Revision ID: daniele.stroppa@joyent.com-20140108155810-xecbwrqkb5i0fyoe
Merging trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        "launchpad.net/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
// 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
// NewKeyUpdaterAPI 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 can access the key manager service.
 
52
        if !authorizer.AuthClient() {
 
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 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 {
 
67
                                return false
 
68
                        }
 
69
                        return authorizer.GetAuthTag() == "user-admin"
 
70
                }, nil
 
71
        }
 
72
        return &KeyManagerAPI{
 
73
                state: st, resources: resources, authorizer: authorizer, getCanRead: getCanRead, getCanWrite: getCanWrite}, nil
 
74
}
 
75
 
 
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
 
80
        }
 
81
        results := make([]params.StringsResult, len(arg.Entities.Entities))
 
82
 
 
83
        // For now, authorised keys are global, common to all users.
 
84
        var keyInfo []string
 
85
        cfg, configErr := api.state.EnvironConfig()
 
86
        if configErr == nil {
 
87
                keys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
 
88
                keyInfo = parseKeys(keys, arg.Mode)
 
89
        }
 
90
 
 
91
        canRead, err := api.getCanRead()
 
92
        if err != nil {
 
93
                return params.StringsResults{}, err
 
94
        }
 
95
        for i, entity := range arg.Entities.Entities {
 
96
                if !canRead(entity.Tag) {
 
97
                        results[i].Error = common.ServerError(common.ErrPerm)
 
98
                        continue
 
99
                }
 
100
                if _, err := api.state.User(entity.Tag); err != nil {
 
101
                        if errors.IsNotFoundError(err) {
 
102
                                results[i].Error = common.ServerError(common.ErrPerm)
 
103
                        } else {
 
104
                                results[i].Error = common.ServerError(err)
 
105
                        }
 
106
                        continue
 
107
                }
 
108
                var err error
 
109
                if configErr == nil {
 
110
                        results[i].Result = keyInfo
 
111
                } else {
 
112
                        err = configErr
 
113
                }
 
114
                results[i].Error = common.ServerError(err)
 
115
        }
 
116
        return params.StringsResults{results}, nil
 
117
}
 
118
 
 
119
func parseKeys(keys []string, mode ssh.ListMode) (keyInfo []string) {
 
120
        for _, key := range keys {
 
121
                fingerprint, comment, err := ssh.KeyFingerprint(key)
 
122
                if err != nil {
 
123
                        keyInfo = append(keyInfo, fmt.Sprintf("Invalid key: %v", key))
 
124
                } else {
 
125
                        if mode == ssh.FullKeys {
 
126
                                keyInfo = append(keyInfo, key)
 
127
                        } else {
 
128
                                shortKey := fingerprint
 
129
                                if comment != "" {
 
130
                                        shortKey += fmt.Sprintf(" (%s)", comment)
 
131
                                }
 
132
                                keyInfo = append(keyInfo, shortKey)
 
133
                        }
 
134
                }
 
135
        }
 
136
        return keyInfo
 
137
}
 
138
 
 
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})
 
143
        if err != nil {
 
144
                return fmt.Errorf("creating new environ config: %v", err)
 
145
        }
 
146
        err = api.state.SetEnvironConfig(newConfig, currentConfig)
 
147
        if err != nil {
 
148
                return fmt.Errorf("writing environ config: %v", err)
 
149
        }
 
150
        return nil
 
151
}
 
152
 
 
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()
 
156
        fingerprints = &fp
 
157
        cfg, err = api.state.EnvironConfig()
 
158
        if err != nil {
 
159
                return nil, nil, nil, err
 
160
        }
 
161
        keys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
 
162
        for _, key := range keys {
 
163
                fingerprint, _, err := ssh.KeyFingerprint(key)
 
164
                if err != nil {
 
165
                        logger.Warningf("ignoring invalid ssh key %q: %v", key, err)
 
166
                }
 
167
                fingerprints.Add(fingerprint)
 
168
        }
 
169
        return keys, fingerprints, cfg, nil
 
170
}
 
171
 
 
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)),
 
176
        }
 
177
        if len(arg.Keys) == 0 {
 
178
                return result, nil
 
179
        }
 
180
 
 
181
        canWrite, err := api.getCanWrite()
 
182
        if err != nil {
 
183
                return params.ErrorResults{}, common.ServerError(err)
 
184
        }
 
185
        if !canWrite(arg.User) {
 
186
                return params.ErrorResults{}, common.ServerError(common.ErrPerm)
 
187
        }
 
188
 
 
189
        // For now, authorised keys are global, common to all users.
 
190
        sshKeys, currentFingerprints, currentConfig, err := api.currentKeyDataForAdd()
 
191
        if err != nil {
 
192
                return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
 
193
        }
 
194
 
 
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)
 
199
                if err != nil {
 
200
                        result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", key))
 
201
                        continue
 
202
                }
 
203
                if currentFingerprints.Contains(fingerprint) {
 
204
                        result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", key))
 
205
                        continue
 
206
                }
 
207
                sshKeys = append(sshKeys, key)
 
208
        }
 
209
        err = api.writeSSHKeys(currentConfig, sshKeys)
 
210
        if err != nil {
 
211
                return params.ErrorResults{}, common.ServerError(err)
 
212
        }
 
213
        return result, nil
 
214
}
 
215
 
 
216
type importedSSHKey struct {
 
217
        key         string
 
218
        fingerprint string
 
219
        err         error
 
220
}
 
221
 
 
222
//  Override for testing
 
223
var RunSSHImportId = runSSHImportId
 
224
 
 
225
func runSSHImportId(keyId string) (string, error) {
 
226
        return utils.RunCommand("ssh-import-id", "-o", "-", keyId)
 
227
}
 
228
 
 
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)
 
234
                if err != nil {
 
235
                        keyInfo[i].err = err
 
236
                        continue
 
237
                }
 
238
                lines := strings.Split(output, "\n")
 
239
                for _, line := range lines {
 
240
                        if !strings.HasPrefix(line, "ssh-") {
 
241
                                continue
 
242
                        }
 
243
                        keyInfo[i].fingerprint, _, keyInfo[i].err = ssh.KeyFingerprint(line)
 
244
                        if err == nil {
 
245
                                keyInfo[i].key = line
 
246
                        }
 
247
                }
 
248
                if keyInfo[i].key == "" {
 
249
                        keyInfo[i].err = fmt.Errorf("invalid ssh key id: %s", keyId)
 
250
                }
 
251
        }
 
252
        return keyInfo
 
253
}
 
254
 
 
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)),
 
259
        }
 
260
        if len(arg.Keys) == 0 {
 
261
                return result, nil
 
262
        }
 
263
 
 
264
        canWrite, err := api.getCanWrite()
 
265
        if err != nil {
 
266
                return params.ErrorResults{}, common.ServerError(err)
 
267
        }
 
268
        if !canWrite(arg.User) {
 
269
                return params.ErrorResults{}, common.ServerError(common.ErrPerm)
 
270
        }
 
271
 
 
272
        // For now, authorised keys are global, common to all users.
 
273
        sshKeys, currentFingerprints, currentConfig, err := api.currentKeyDataForAdd()
 
274
        if err != nil {
 
275
                return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
 
276
        }
 
277
 
 
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)
 
284
                        continue
 
285
                }
 
286
                if currentFingerprints.Contains(keyInfo.fingerprint) {
 
287
                        result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", keyInfo.key))
 
288
                        continue
 
289
                }
 
290
                sshKeys = append(sshKeys, keyInfo.key)
 
291
        }
 
292
        err = api.writeSSHKeys(currentConfig, sshKeys)
 
293
        if err != nil {
 
294
                return params.ErrorResults{}, common.ServerError(err)
 
295
        }
 
296
        return result, nil
 
297
}
 
298
 
 
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) {
 
302
 
 
303
        // For now, authorised keys are global, common to all users.
 
304
        cfg, err = api.state.EnvironConfig()
 
305
        if err != nil {
 
306
                return nil, nil, nil, nil, fmt.Errorf("reading current key data: %v", err)
 
307
        }
 
308
        existingSSHKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
 
309
 
 
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
 
312
        // or comment.
 
313
        keys = make(map[string]string)
 
314
        comments = make(map[string]string)
 
315
        for _, key := range existingSSHKeys {
 
316
                fingerprint, comment, err := ssh.KeyFingerprint(key)
 
317
                if err != nil {
 
318
                        logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err)
 
319
                        invalidKeys = append(invalidKeys, key)
 
320
                        continue
 
321
                }
 
322
                keys[fingerprint] = key
 
323
                if comment != "" {
 
324
                        comments[comment] = fingerprint
 
325
                }
 
326
        }
 
327
        return keys, invalidKeys, comments, cfg, nil
 
328
}
 
329
 
 
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)),
 
334
        }
 
335
        if len(arg.Keys) == 0 {
 
336
                return result, nil
 
337
        }
 
338
 
 
339
        canWrite, err := api.getCanWrite()
 
340
        if err != nil {
 
341
                return params.ErrorResults{}, common.ServerError(err)
 
342
        }
 
343
        if !canWrite(arg.User) {
 
344
                return params.ErrorResults{}, common.ServerError(common.ErrPerm)
 
345
        }
 
346
 
 
347
        sshKeys, invalidKeys, keyComments, currentConfig, err := api.currentKeyDataForDelete()
 
348
        if err != nil {
 
349
                return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
 
350
        }
 
351
 
 
352
        // We keep all existing invalid keys.
 
353
        keysToWrite := invalidKeys
 
354
 
 
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
 
358
                fingerprint := keyId
 
359
                _, ok := sshKeys[keyId]
 
360
                if !ok {
 
361
                        // keyId is a comment
 
362
                        fingerprint, ok = keyComments[keyId]
 
363
                }
 
364
                if !ok {
 
365
                        result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", keyId))
 
366
                }
 
367
                // We found the key to delete so remove it from those we wish to keep.
 
368
                delete(sshKeys, fingerprint)
 
369
        }
 
370
        for _, key := range sshKeys {
 
371
                keysToWrite = append(keysToWrite, key)
 
372
        }
 
373
        if len(keysToWrite) == 0 {
 
374
                return params.ErrorResults{}, common.ServerError(stderrors.New("cannot delete all keys"))
 
375
        }
 
376
 
 
377
        err = api.writeSSHKeys(currentConfig, keysToWrite)
 
378
        if err != nil {
 
379
                return params.ErrorResults{}, common.ServerError(err)
 
380
        }
 
381
        return result, nil
 
382
}