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