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