github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"github.com/juju/utils/ssh"
    16  
    17  	"github.com/juju/juju/apiserver/common"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/state"
    21  )
    22  
    23  var logger = loggo.GetLogger("juju.apiserver.keymanager")
    24  
    25  func init() {
    26  	common.RegisterStandardFacade("KeyManager", 1, 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  // KeyManagerAPI 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.AuthModelManager() {
    54  		return nil, common.ErrPerm
    55  	}
    56  	env, err := st.Model()
    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.ModelConfig()
   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.UpdateModelConfig(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.ModelConfig()
   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) map[string][]importedSSHKey {
   235  	importResults := make(map[string][]importedSSHKey, len(keyIds))
   236  	for _, keyId := range keyIds {
   237  		keyInfo := []importedSSHKey{}
   238  		output, err := RunSSHImportId(keyId)
   239  		if err != nil {
   240  			keyInfo = append(keyInfo, importedSSHKey{err: err})
   241  			continue
   242  		}
   243  		lines := strings.Split(output, "\n")
   244  		hasKey := false
   245  		for _, line := range lines {
   246  			if !strings.HasPrefix(line, "ssh-") {
   247  				continue
   248  			}
   249  			hasKey = true
   250  			// ignore key comment (e.g., user@host)
   251  			fingerprint, _, err := ssh.KeyFingerprint(line)
   252  			keyInfo = append(keyInfo, importedSSHKey{
   253  				key:         line,
   254  				fingerprint: fingerprint,
   255  				err:         errors.Annotatef(err, "invalid ssh key for %s", keyId),
   256  			})
   257  		}
   258  		if !hasKey {
   259  			keyInfo = append(keyInfo, importedSSHKey{
   260  				err: errors.Errorf("invalid ssh key id: %s", keyId),
   261  			})
   262  		}
   263  		importResults[keyId] = keyInfo
   264  	}
   265  	return importResults
   266  }
   267  
   268  // ImportKeys imports new authorised ssh keys from the specified key ids for the specified user.
   269  func (api *KeyManagerAPI) ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
   270  	if err := api.check.ChangeAllowed(); err != nil {
   271  		return params.ErrorResults{}, errors.Trace(err)
   272  	}
   273  	result := params.ErrorResults{
   274  		Results: make([]params.ErrorResult, len(arg.Keys)),
   275  	}
   276  	if len(arg.Keys) == 0 {
   277  		return result, nil
   278  	}
   279  
   280  	if !api.canWrite(arg.User) {
   281  		return params.ErrorResults{}, common.ServerError(common.ErrPerm)
   282  	}
   283  
   284  	// For now, authorised keys are global, common to all users.
   285  	sshKeys, currentFingerprints, err := api.currentKeyDataForAdd()
   286  	if err != nil {
   287  		return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
   288  	}
   289  
   290  	importedKeyInfo := runSSHKeyImport(arg.Keys)
   291  	// Ensure we are not going to add invalid or duplicate keys.
   292  	result.Results = make([]params.ErrorResult, len(importedKeyInfo))
   293  	for i, key := range arg.Keys {
   294  		compoundErr := ""
   295  		for _, keyInfo := range importedKeyInfo[key] {
   296  			if keyInfo.err != nil {
   297  				compoundErr += fmt.Sprintf("%v\n", keyInfo.err)
   298  				continue
   299  			}
   300  			if currentFingerprints.Contains(keyInfo.fingerprint) {
   301  				compoundErr += fmt.Sprintf("%v\n", errors.Errorf("duplicate ssh key: %s", keyInfo.key))
   302  				continue
   303  			}
   304  			sshKeys = append(sshKeys, keyInfo.key)
   305  		}
   306  		if compoundErr != "" {
   307  			result.Results[i].Error = common.ServerError(errors.Errorf(strings.TrimSuffix(compoundErr, "\n")))
   308  		}
   309  
   310  	}
   311  	err = api.writeSSHKeys(sshKeys)
   312  	if err != nil {
   313  		return params.ErrorResults{}, common.ServerError(err)
   314  	}
   315  	return result, nil
   316  }
   317  
   318  // currentKeyDataForDelete gathers data used when deleting ssh keys.
   319  func (api *KeyManagerAPI) currentKeyDataForDelete() (
   320  	keys map[string]string, invalidKeys []string, comments map[string]string, err error) {
   321  
   322  	cfg, err := api.state.ModelConfig()
   323  	if err != nil {
   324  		return nil, nil, nil, fmt.Errorf("reading current key data: %v", err)
   325  	}
   326  	// For now, authorised keys are global, common to all users.
   327  	existingSSHKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
   328  
   329  	// Build up a map of keys indexed by fingerprint, and fingerprints indexed by comment
   330  	// so we can easily get the key represented by each keyId, which may be either a fingerprint
   331  	// or comment.
   332  	keys = make(map[string]string)
   333  	comments = make(map[string]string)
   334  	for _, key := range existingSSHKeys {
   335  		fingerprint, comment, err := ssh.KeyFingerprint(key)
   336  		if err != nil {
   337  			logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err)
   338  			invalidKeys = append(invalidKeys, key)
   339  			continue
   340  		}
   341  		keys[fingerprint] = key
   342  		if comment != "" {
   343  			comments[comment] = fingerprint
   344  		}
   345  	}
   346  	return keys, invalidKeys, comments, nil
   347  }
   348  
   349  // DeleteKeys deletes the authorised ssh keys for the specified user.
   350  func (api *KeyManagerAPI) DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
   351  	if err := api.check.ChangeAllowed(); err != nil {
   352  		return params.ErrorResults{}, errors.Trace(err)
   353  	}
   354  	result := params.ErrorResults{
   355  		Results: make([]params.ErrorResult, len(arg.Keys)),
   356  	}
   357  	if len(arg.Keys) == 0 {
   358  		return result, nil
   359  	}
   360  
   361  	if !api.canWrite(arg.User) {
   362  		return params.ErrorResults{}, common.ServerError(common.ErrPerm)
   363  	}
   364  
   365  	sshKeys, invalidKeys, keyComments, err := api.currentKeyDataForDelete()
   366  	if err != nil {
   367  		return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
   368  	}
   369  
   370  	// We keep all existing invalid keys.
   371  	keysToWrite := invalidKeys
   372  
   373  	// Find the keys corresponding to the specified key fingerprints or comments.
   374  	for i, keyId := range arg.Keys {
   375  		// assume keyId may be a fingerprint
   376  		fingerprint := keyId
   377  		_, ok := sshKeys[keyId]
   378  		if !ok {
   379  			// keyId is a comment
   380  			fingerprint, ok = keyComments[keyId]
   381  		}
   382  		if !ok {
   383  			result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", keyId))
   384  		}
   385  		// We found the key to delete so remove it from those we wish to keep.
   386  		delete(sshKeys, fingerprint)
   387  	}
   388  	for _, key := range sshKeys {
   389  		keysToWrite = append(keysToWrite, key)
   390  	}
   391  	if len(keysToWrite) == 0 {
   392  		return params.ErrorResults{}, common.ServerError(fmt.Errorf("cannot delete all keys"))
   393  	}
   394  
   395  	err = api.writeSSHKeys(keysToWrite)
   396  	if err != nil {
   397  		return params.ErrorResults{}, common.ServerError(err)
   398  	}
   399  	return result, nil
   400  }