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