github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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/utils"
    13  	"github.com/juju/utils/set"
    14  	"github.com/juju/utils/ssh"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/apiserver/common"
    18  	"github.com/juju/juju/apiserver/facade"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/environs/config"
    21  	"github.com/juju/juju/state"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.apiserver.keymanager")
    25  
    26  func init() {
    27  	common.RegisterStandardFacade("KeyManager", 1, NewKeyManagerAPI)
    28  }
    29  
    30  // The comment values used by juju internal ssh keys.
    31  var internalComments = set.NewStrings([]string{"juju-client-key", "juju-system-key"}...)
    32  
    33  // KeyManager defines the methods on the keymanager API end point.
    34  type KeyManager interface {
    35  	ListKeys(arg params.ListSSHKeys) (params.StringsResults, error)
    36  	AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error)
    37  	DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error)
    38  	ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error)
    39  }
    40  
    41  // KeyManagerAPI implements the KeyUpdater interface and is the concrete
    42  // implementation of the api end point.
    43  type KeyManagerAPI struct {
    44  	state      *state.State
    45  	resources  facade.Resources
    46  	authorizer facade.Authorizer
    47  	canRead    func(string) bool
    48  	canWrite   func(string) bool
    49  	check      *common.BlockChecker
    50  }
    51  
    52  var _ KeyManager = (*KeyManagerAPI)(nil)
    53  
    54  // NewKeyManagerAPI creates a new server-side keyupdater API end point.
    55  func NewKeyManagerAPI(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*KeyManagerAPI, error) {
    56  	// Only clients and environment managers can access the key manager service.
    57  	if !authorizer.AuthClient() && !authorizer.AuthModelManager() {
    58  		return nil, common.ErrPerm
    59  	}
    60  	env, err := st.Model()
    61  	if err != nil {
    62  		return nil, errors.Trace(err)
    63  	}
    64  	// For gccgo interface comparisons, we need a Tag.
    65  	owner := names.Tag(env.Owner())
    66  	// TODO(wallyworld) - replace stub with real canRead function
    67  	// For now, only admins can read authorised ssh keys.
    68  	canRead := func(user string) bool {
    69  		// Are we a machine agent operating as the system identity?
    70  		if user == config.JujuSystemKey {
    71  			_, ismachinetag := authorizer.GetAuthTag().(names.MachineTag)
    72  			return ismachinetag
    73  		}
    74  		return authorizer.GetAuthTag() == owner
    75  	}
    76  	// TODO(wallyworld) - replace stub with real canWrite function
    77  	// For now, only admins can write authorised ssh keys for users.
    78  	// Machine agents can write the juju-system-key.
    79  	canWrite := func(user string) bool {
    80  		// Are we a machine agent writing the Juju system key.
    81  		if user == config.JujuSystemKey {
    82  			_, ismachinetag := authorizer.GetAuthTag().(names.MachineTag)
    83  			return ismachinetag
    84  		}
    85  		// No point looking to see if the user exists as we are not
    86  		// yet storing keys on the user.
    87  		return authorizer.GetAuthTag() == owner
    88  	}
    89  	return &KeyManagerAPI{
    90  		state:      st,
    91  		resources:  resources,
    92  		authorizer: authorizer,
    93  		canRead:    canRead,
    94  		canWrite:   canWrite,
    95  		check:      common.NewBlockChecker(st),
    96  	}, nil
    97  }
    98  
    99  // ListKeys returns the authorised ssh keys for the specified users.
   100  func (api *KeyManagerAPI) ListKeys(arg params.ListSSHKeys) (params.StringsResults, error) {
   101  	if len(arg.Entities.Entities) == 0 {
   102  		return params.StringsResults{}, nil
   103  	}
   104  	results := make([]params.StringsResult, len(arg.Entities.Entities))
   105  
   106  	// For now, authorised keys are global, common to all users.
   107  	var keyInfo []string
   108  	cfg, configErr := api.state.ModelConfig()
   109  	if configErr == nil {
   110  		keys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
   111  		keyInfo = parseKeys(keys, arg.Mode)
   112  	}
   113  
   114  	for i, entity := range arg.Entities.Entities {
   115  		// NOTE: entity.Tag isn't a tag, but a username.
   116  		if !api.canRead(entity.Tag) {
   117  			results[i].Error = common.ServerError(common.ErrPerm)
   118  			continue
   119  		}
   120  		// All keys are global, no need to look up the user.
   121  		if configErr == nil {
   122  			results[i].Result = keyInfo
   123  		}
   124  		results[i].Error = common.ServerError(configErr)
   125  	}
   126  	return params.StringsResults{Results: results}, nil
   127  }
   128  
   129  func parseKeys(keys []string, mode ssh.ListMode) (keyInfo []string) {
   130  	for _, key := range keys {
   131  		fingerprint, comment, err := ssh.KeyFingerprint(key)
   132  		if err != nil {
   133  			keyInfo = append(keyInfo, fmt.Sprintf("Invalid key: %v", key))
   134  			continue
   135  		}
   136  		// Only including user added keys not internal ones.
   137  		if internalComments.Contains(comment) {
   138  			continue
   139  		}
   140  		if mode == ssh.FullKeys {
   141  			keyInfo = append(keyInfo, key)
   142  		} else {
   143  			shortKey := fingerprint
   144  			if comment != "" {
   145  				shortKey += fmt.Sprintf(" (%s)", comment)
   146  			}
   147  			keyInfo = append(keyInfo, shortKey)
   148  		}
   149  	}
   150  	return keyInfo
   151  }
   152  
   153  func (api *KeyManagerAPI) writeSSHKeys(sshKeys []string) error {
   154  	// Write out the new keys.
   155  	keyStr := strings.Join(sshKeys, "\n")
   156  	attrs := map[string]interface{}{config.AuthorizedKeysKey: keyStr}
   157  	// TODO(waigani) 2014-03-17 bug #1293324
   158  	// Pass in validation to ensure SSH keys
   159  	// have not changed underfoot
   160  	err := api.state.UpdateModelConfig(attrs, nil, nil)
   161  	if err != nil {
   162  		return fmt.Errorf("writing environ config: %v", err)
   163  	}
   164  	return nil
   165  }
   166  
   167  // currentKeyDataForAdd gathers data used when adding ssh keys.
   168  func (api *KeyManagerAPI) currentKeyDataForAdd() (keys []string, fingerprints set.Strings, err error) {
   169  	fingerprints = make(set.Strings)
   170  	cfg, err := api.state.ModelConfig()
   171  	if err != nil {
   172  		return nil, nil, fmt.Errorf("reading current key data: %v", err)
   173  	}
   174  	keys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
   175  	for _, key := range keys {
   176  		fingerprint, _, err := ssh.KeyFingerprint(key)
   177  		if err != nil {
   178  			logger.Warningf("ignoring invalid ssh key %q: %v", key, err)
   179  		}
   180  		fingerprints.Add(fingerprint)
   181  	}
   182  	return keys, fingerprints, nil
   183  }
   184  
   185  // AddKeys adds new authorised ssh keys for the specified user.
   186  func (api *KeyManagerAPI) AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
   187  	if err := api.check.ChangeAllowed(); err != nil {
   188  		return params.ErrorResults{}, errors.Trace(err)
   189  	}
   190  	result := params.ErrorResults{
   191  		Results: make([]params.ErrorResult, len(arg.Keys)),
   192  	}
   193  	if len(arg.Keys) == 0 {
   194  		return result, nil
   195  	}
   196  
   197  	if !api.canWrite(arg.User) {
   198  		return params.ErrorResults{}, common.ServerError(common.ErrPerm)
   199  	}
   200  
   201  	// For now, authorised keys are global, common to all users.
   202  	sshKeys, currentFingerprints, err := api.currentKeyDataForAdd()
   203  	if err != nil {
   204  		return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
   205  	}
   206  
   207  	// Ensure we are not going to add invalid or duplicate keys.
   208  	result.Results = make([]params.ErrorResult, len(arg.Keys))
   209  	for i, key := range arg.Keys {
   210  		fingerprint, _, err := ssh.KeyFingerprint(key)
   211  		if err != nil {
   212  			result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", key))
   213  			continue
   214  		}
   215  		if currentFingerprints.Contains(fingerprint) {
   216  			result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", key))
   217  			continue
   218  		}
   219  		sshKeys = append(sshKeys, key)
   220  	}
   221  	err = api.writeSSHKeys(sshKeys)
   222  	if err != nil {
   223  		return params.ErrorResults{}, common.ServerError(err)
   224  	}
   225  	return result, nil
   226  }
   227  
   228  type importedSSHKey struct {
   229  	key         string
   230  	fingerprint string
   231  	err         error
   232  }
   233  
   234  //  Override for testing
   235  var RunSSHImportId = runSSHImportId
   236  
   237  func runSSHImportId(keyId string) (string, error) {
   238  	return utils.RunCommand("ssh-import-id", "-o", "-", keyId)
   239  }
   240  
   241  // runSSHKeyImport uses ssh-import-id to find the ssh keys for the specified key ids.
   242  func runSSHKeyImport(keyIds []string) map[string][]importedSSHKey {
   243  	importResults := make(map[string][]importedSSHKey, len(keyIds))
   244  	for _, keyId := range keyIds {
   245  		keyInfo := []importedSSHKey{}
   246  		output, err := RunSSHImportId(keyId)
   247  		if err != nil {
   248  			keyInfo = append(keyInfo, importedSSHKey{err: err})
   249  			continue
   250  		}
   251  		lines := strings.Split(output, "\n")
   252  		hasKey := false
   253  		for _, line := range lines {
   254  			if !strings.HasPrefix(line, "ssh-") {
   255  				continue
   256  			}
   257  			hasKey = true
   258  			// ignore key comment (e.g., user@host)
   259  			fingerprint, _, err := ssh.KeyFingerprint(line)
   260  			keyInfo = append(keyInfo, importedSSHKey{
   261  				key:         line,
   262  				fingerprint: fingerprint,
   263  				err:         errors.Annotatef(err, "invalid ssh key for %s", keyId),
   264  			})
   265  		}
   266  		if !hasKey {
   267  			keyInfo = append(keyInfo, importedSSHKey{
   268  				err: errors.Errorf("invalid ssh key id: %s", keyId),
   269  			})
   270  		}
   271  		importResults[keyId] = keyInfo
   272  	}
   273  	return importResults
   274  }
   275  
   276  // ImportKeys imports new authorised ssh keys from the specified key ids for the specified user.
   277  func (api *KeyManagerAPI) ImportKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
   278  	if err := api.check.ChangeAllowed(); err != nil {
   279  		return params.ErrorResults{}, errors.Trace(err)
   280  	}
   281  	result := params.ErrorResults{
   282  		Results: make([]params.ErrorResult, len(arg.Keys)),
   283  	}
   284  	if len(arg.Keys) == 0 {
   285  		return result, nil
   286  	}
   287  
   288  	if !api.canWrite(arg.User) {
   289  		return params.ErrorResults{}, common.ServerError(common.ErrPerm)
   290  	}
   291  
   292  	// For now, authorised keys are global, common to all users.
   293  	sshKeys, currentFingerprints, err := api.currentKeyDataForAdd()
   294  	if err != nil {
   295  		return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
   296  	}
   297  
   298  	importedKeyInfo := runSSHKeyImport(arg.Keys)
   299  	// Ensure we are not going to add invalid or duplicate keys.
   300  	result.Results = make([]params.ErrorResult, len(importedKeyInfo))
   301  	for i, key := range arg.Keys {
   302  		compoundErr := ""
   303  		for _, keyInfo := range importedKeyInfo[key] {
   304  			if keyInfo.err != nil {
   305  				compoundErr += fmt.Sprintf("%v\n", keyInfo.err)
   306  				continue
   307  			}
   308  			if currentFingerprints.Contains(keyInfo.fingerprint) {
   309  				compoundErr += fmt.Sprintf("%v\n", errors.Errorf("duplicate ssh key: %s", keyInfo.key))
   310  				continue
   311  			}
   312  			sshKeys = append(sshKeys, keyInfo.key)
   313  		}
   314  		if compoundErr != "" {
   315  			result.Results[i].Error = common.ServerError(errors.Errorf(strings.TrimSuffix(compoundErr, "\n")))
   316  		}
   317  
   318  	}
   319  	err = api.writeSSHKeys(sshKeys)
   320  	if err != nil {
   321  		return params.ErrorResults{}, common.ServerError(err)
   322  	}
   323  	return result, nil
   324  }
   325  
   326  // currentKeyDataForDelete gathers data used when deleting ssh keys.
   327  func (api *KeyManagerAPI) currentKeyDataForDelete() (
   328  	currentKeys []string, byFingerprint map[string]string, byComment map[string]string, err error) {
   329  
   330  	cfg, err := api.state.ModelConfig()
   331  	if err != nil {
   332  		return nil, nil, nil, fmt.Errorf("reading current key data: %v", err)
   333  	}
   334  	// For now, authorised keys are global, common to all users.
   335  	currentKeys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())
   336  
   337  	// Make two maps that index keys by fingerprint and by comment for fast
   338  	// lookup of keys to delete which may be given as either.
   339  	byFingerprint = make(map[string]string)
   340  	byComment = make(map[string]string)
   341  	for _, key := range currentKeys {
   342  		fingerprint, comment, err := ssh.KeyFingerprint(key)
   343  		if err != nil {
   344  			logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err)
   345  			continue
   346  		}
   347  		byFingerprint[fingerprint] = key
   348  		if comment != "" {
   349  			byComment[comment] = key
   350  		}
   351  	}
   352  	return currentKeys, byFingerprint, byComment, nil
   353  }
   354  
   355  // DeleteKeys deletes the authorised ssh keys for the specified user.
   356  func (api *KeyManagerAPI) DeleteKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) {
   357  	if err := api.check.ChangeAllowed(); err != nil {
   358  		return params.ErrorResults{}, errors.Trace(err)
   359  	}
   360  	result := params.ErrorResults{
   361  		Results: make([]params.ErrorResult, len(arg.Keys)),
   362  	}
   363  	if len(arg.Keys) == 0 {
   364  		return result, nil
   365  	}
   366  
   367  	if !api.canWrite(arg.User) {
   368  		return params.ErrorResults{}, common.ServerError(common.ErrPerm)
   369  	}
   370  
   371  	allKeys, byFingerprint, byComment, err := api.currentKeyDataForDelete()
   372  	if err != nil {
   373  		return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err))
   374  	}
   375  
   376  	// Record the keys to be deleted in the second pass.
   377  	keysToDelete := make(set.Strings)
   378  
   379  	// Find the keys corresponding to the specified key fingerprints or comments.
   380  	for i, keyId := range arg.Keys {
   381  		// Is given keyId a fingerprint?
   382  		key, ok := byFingerprint[keyId]
   383  		if ok {
   384  			keysToDelete.Add(key)
   385  			continue
   386  		}
   387  		// Not a fingerprint, is it a comment?
   388  		key, ok = byComment[keyId]
   389  		if ok {
   390  			if internalComments.Contains(keyId) {
   391  				result.Results[i].Error = common.ServerError(fmt.Errorf("may not delete internal key: %s", keyId))
   392  				continue
   393  			}
   394  			keysToDelete.Add(key)
   395  			continue
   396  		}
   397  		result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", keyId))
   398  	}
   399  
   400  	var keysToWrite []string
   401  
   402  	// Add back only the keys that are not deleted, preserving the order.
   403  	for _, key := range allKeys {
   404  		if !keysToDelete.Contains(key) {
   405  			keysToWrite = append(keysToWrite, key)
   406  		}
   407  	}
   408  
   409  	if len(keysToWrite) == 0 {
   410  		return params.ErrorResults{}, common.ServerError(fmt.Errorf("cannot delete all keys"))
   411  	}
   412  
   413  	err = api.writeSSHKeys(keysToWrite)
   414  	if err != nil {
   415  		return params.ErrorResults{}, common.ServerError(err)
   416  	}
   417  	return result, nil
   418  }