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