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