github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/config/authkeys.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package config
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/tls"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/utils"
    15  	"github.com/juju/utils/ssh"
    16  
    17  	"github.com/juju/juju/cert"
    18  )
    19  
    20  const (
    21  	// AuthKeysConfig is the configuration key for authorised keys.
    22  	AuthKeysConfig = "authorized-keys"
    23  	// JujuSystemKey is the SSH key comment for Juju system keys.
    24  	JujuSystemKey = "juju-system-key"
    25  )
    26  
    27  var ErrNoAuthorizedKeys = errors.New("no public ssh keys found")
    28  
    29  // ReadAuthorizedKeys implements the standard juju behaviour for finding
    30  // authorized_keys. It returns a set of keys in in authorized_keys format
    31  // (see sshd(8) for a description).  If path is non-empty, it names the
    32  // file to use; otherwise the user's .ssh directory will be searched.
    33  // Home directory expansion will be performed on the path if it starts with
    34  // a ~; if the expanded path is relative, it will be interpreted relative
    35  // to $HOME/.ssh.
    36  //
    37  // The result of utils/ssh.PublicKeyFiles will always be prepended to the
    38  // result. In practice, this means ReadAuthorizedKeys never returns an
    39  // error when the call originates in the CLI.
    40  //
    41  // If no SSH keys are found, ReadAuthorizedKeys returns
    42  // ErrNoAuthorizedKeys.
    43  func ReadAuthorizedKeys(path string) (string, error) {
    44  	files := ssh.PublicKeyFiles()
    45  	if path == "" {
    46  		files = append(files, "id_dsa.pub", "id_rsa.pub", "identity.pub")
    47  	} else {
    48  		files = append(files, path)
    49  	}
    50  	var firstError error
    51  	var keyData []byte
    52  	for _, f := range files {
    53  		f, err := utils.NormalizePath(f)
    54  		if err != nil {
    55  			if firstError == nil {
    56  				firstError = err
    57  			}
    58  			continue
    59  		}
    60  		if !filepath.IsAbs(f) {
    61  			f = filepath.Join(utils.Home(), ".ssh", f)
    62  		}
    63  		data, err := ioutil.ReadFile(f)
    64  		if err != nil {
    65  			if firstError == nil && !os.IsNotExist(err) {
    66  				firstError = err
    67  			}
    68  			continue
    69  		}
    70  		keyData = append(keyData, bytes.Trim(data, "\n")...)
    71  		keyData = append(keyData, '\n')
    72  	}
    73  	if len(keyData) == 0 {
    74  		if firstError == nil {
    75  			firstError = ErrNoAuthorizedKeys
    76  		}
    77  		return "", firstError
    78  	}
    79  	return string(keyData), nil
    80  }
    81  
    82  // verifyKeyPair verifies that the certificate and key parse correctly.
    83  // The key is optional - if it is provided, we also check that the key
    84  // matches the certificate.
    85  func verifyKeyPair(certb, key string) error {
    86  	if key != "" {
    87  		_, err := tls.X509KeyPair([]byte(certb), []byte(key))
    88  		return err
    89  	}
    90  	_, err := cert.ParseCert(certb)
    91  	return err
    92  }
    93  
    94  // ConcatAuthKeys concatenates the two sets of authorised keys, interposing
    95  // a newline if necessary, because authorised keys are newline-separated.
    96  func ConcatAuthKeys(a, b string) string {
    97  	if a == "" {
    98  		return b
    99  	}
   100  	if b == "" {
   101  		return a
   102  	}
   103  	if a[len(a)-1] != '\n' {
   104  		return a + "\n" + b
   105  	}
   106  	return a + b
   107  }