github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/common/authkeys.go (about)

     1  // Copyright 2012-2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"bytes"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/juju/cmd"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/juju/environs/config"
    15  	"github.com/juju/schema"
    16  	"github.com/juju/utils"
    17  	"github.com/juju/utils/ssh"
    18  )
    19  
    20  var ErrNoAuthorizedKeys = errors.New("no public ssh keys found")
    21  
    22  // FinalizeAuthorizedKeys takes a set of configuration attributes and
    23  // ensures that it has an authorized-keys setting, or returns
    24  // ErrNoAuthorizedKeys if it cannot.
    25  //
    26  // If the attributes contains a non-empty value for "authorized-keys",
    27  // then it is left alone. If there is an "authorized-keys-path" setting,
    28  // its contents will be loaded into "authorized-keys". Otherwise, the
    29  // contents of standard public keys will be used: ~/.ssh/id_dsa.pub,
    30  // ~/.ssh/id_rsa.pub, and ~/.ssh/identity.pub.
    31  func FinalizeAuthorizedKeys(ctx *cmd.Context, attrs map[string]interface{}) error {
    32  	const authorizedKeysPathKey = "authorized-keys-path"
    33  	checker := schema.FieldMap(schema.Fields{
    34  		config.AuthorizedKeysKey: schema.String(),
    35  		authorizedKeysPathKey:    schema.String(),
    36  	}, schema.Defaults{
    37  		config.AuthorizedKeysKey: schema.Omit,
    38  		authorizedKeysPathKey:    schema.Omit,
    39  	})
    40  	coerced, err := checker.Coerce(attrs, nil)
    41  	if err != nil {
    42  		return errors.Trace(err)
    43  	}
    44  	coercedAttrs := coerced.(map[string]interface{})
    45  
    46  	authorizedKeys, haveAuthorizedKeys := coercedAttrs[config.AuthorizedKeysKey].(string)
    47  	authorizedKeysPath, haveAuthorizedKeysPath := coercedAttrs[authorizedKeysPathKey].(string)
    48  	if haveAuthorizedKeys && haveAuthorizedKeysPath {
    49  		return errors.Errorf(
    50  			"%q and %q may not both be specified",
    51  			config.AuthorizedKeysKey, authorizedKeysPathKey,
    52  		)
    53  	}
    54  	if haveAuthorizedKeys {
    55  		// We have authorized-keys already; nothing to do.
    56  		return nil
    57  	}
    58  
    59  	authorizedKeys, err = ReadAuthorizedKeys(ctx, authorizedKeysPath)
    60  	if err != nil {
    61  		return errors.Annotate(err, "reading authorized-keys")
    62  	}
    63  	if haveAuthorizedKeysPath {
    64  		delete(attrs, authorizedKeysPathKey)
    65  	}
    66  	attrs[config.AuthorizedKeysKey] = authorizedKeys
    67  	return nil
    68  }
    69  
    70  // ReadAuthorizedKeys implements the standard juju behaviour for finding
    71  // authorized_keys. It returns a set of keys in in authorized_keys format
    72  // (see sshd(8) for a description).  If path is non-empty, it names the
    73  // file to use; otherwise the user's .ssh directory will be searched.
    74  // Home directory expansion will be performed on the path if it starts with
    75  // a ~; if the expanded path is relative, it will be interpreted relative
    76  // to $HOME/.ssh.
    77  //
    78  // The result of utils/ssh.PublicKeyFiles will always be prepended to the
    79  // result. In practice, this means ReadAuthorizedKeys never returns an
    80  // error when the call originates in the CLI.
    81  //
    82  // If no SSH keys are found, ReadAuthorizedKeys returns
    83  // ErrNoAuthorizedKeys.
    84  func ReadAuthorizedKeys(ctx *cmd.Context, path string) (string, error) {
    85  	files := ssh.PublicKeyFiles()
    86  	if path == "" {
    87  		files = append(files, "id_dsa.pub", "id_rsa.pub", "identity.pub")
    88  	} else {
    89  		files = append(files, path)
    90  	}
    91  	var firstError error
    92  	var keyData []byte
    93  	for _, f := range files {
    94  		f, err := utils.NormalizePath(f)
    95  		if err != nil {
    96  			if firstError == nil {
    97  				firstError = err
    98  			}
    99  			continue
   100  		}
   101  		if !filepath.IsAbs(f) {
   102  			f = filepath.Join(utils.Home(), ".ssh", f)
   103  		}
   104  		data, err := ioutil.ReadFile(f)
   105  		if err != nil {
   106  			if firstError == nil && !os.IsNotExist(err) {
   107  				firstError = err
   108  			}
   109  			continue
   110  		}
   111  		keyData = append(keyData, bytes.Trim(data, "\n")...)
   112  		keyData = append(keyData, '\n')
   113  		ctx.Verbosef("Adding contents of %q to authorized-keys", f)
   114  	}
   115  	if len(keyData) == 0 {
   116  		if firstError == nil {
   117  			firstError = ErrNoAuthorizedKeys
   118  		}
   119  		return "", firstError
   120  	}
   121  	return string(keyData), nil
   122  
   123  }