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  }