github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/authenticationworker/worker.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package authenticationworker
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/names"
    12  	"github.com/juju/utils/os"
    13  	"github.com/juju/utils/set"
    14  	"launchpad.net/tomb"
    15  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/api/keyupdater"
    18  	"github.com/juju/juju/api/watcher"
    19  	"github.com/juju/juju/utils/ssh"
    20  	"github.com/juju/juju/worker"
    21  )
    22  
    23  // The user name used to ssh into Juju nodes.
    24  // Override for testing.
    25  var SSHUser = "ubuntu"
    26  
    27  var logger = loggo.GetLogger("juju.worker.authenticationworker")
    28  
    29  type keyupdaterWorker struct {
    30  	st   *keyupdater.State
    31  	tomb tomb.Tomb
    32  	tag  names.MachineTag
    33  	// jujuKeys are the most recently retrieved keys from state.
    34  	jujuKeys set.Strings
    35  	// nonJujuKeys are those added externally to auth keys file
    36  	// such keys do not have comments with the Juju: prefix.
    37  	nonJujuKeys []string
    38  }
    39  
    40  var _ worker.NotifyWatchHandler = (*keyupdaterWorker)(nil)
    41  
    42  // NewWorker returns a worker that keeps track of
    43  // the machine's authorised ssh keys and ensures the
    44  // ~/.ssh/authorized_keys file is up to date.
    45  func NewWorker(st *keyupdater.State, agentConfig agent.Config) worker.Worker {
    46  	if os.HostOS() == os.Windows {
    47  		return worker.NewNoOpWorker()
    48  	}
    49  	kw := &keyupdaterWorker{st: st, tag: agentConfig.Tag().(names.MachineTag)}
    50  	return worker.NewNotifyWorker(kw)
    51  }
    52  
    53  // SetUp is defined on the worker.NotifyWatchHandler interface.
    54  func (kw *keyupdaterWorker) SetUp() (watcher.NotifyWatcher, error) {
    55  	// Record the keys Juju knows about.
    56  	jujuKeys, err := kw.st.AuthorisedKeys(kw.tag)
    57  	if err != nil {
    58  		err = errors.Annotatef(err, "reading Juju ssh keys for %q", kw.tag)
    59  		logger.Infof(err.Error())
    60  		return nil, err
    61  	}
    62  	kw.jujuKeys = set.NewStrings(jujuKeys...)
    63  
    64  	// Read the keys currently in ~/.ssh/authorised_keys.
    65  	sshKeys, err := ssh.ListKeys(SSHUser, ssh.FullKeys)
    66  	if err != nil {
    67  		err = errors.Annotatef(err, "reading ssh authorized keys for %q", kw.tag)
    68  		logger.Infof(err.Error())
    69  		return nil, err
    70  	}
    71  	// Record any keys not added by Juju.
    72  	for _, key := range sshKeys {
    73  		_, comment, err := ssh.KeyFingerprint(key)
    74  		// Also record keys which we cannot parse.
    75  		if err != nil || !strings.HasPrefix(comment, ssh.JujuCommentPrefix) {
    76  			kw.nonJujuKeys = append(kw.nonJujuKeys, key)
    77  		}
    78  	}
    79  	// Write out the ssh authorised keys file to match the current state of the world.
    80  	if err := kw.writeSSHKeys(jujuKeys); err != nil {
    81  		err = errors.Annotate(err, "adding current Juju keys to ssh authorised keys")
    82  		logger.Infof(err.Error())
    83  		return nil, err
    84  	}
    85  
    86  	w, err := kw.st.WatchAuthorisedKeys(kw.tag)
    87  	if err != nil {
    88  		err = errors.Annotate(err, "starting key updater worker")
    89  		logger.Infof(err.Error())
    90  		return nil, err
    91  	}
    92  	logger.Infof("%q key updater worker started", kw.tag)
    93  	return w, nil
    94  }
    95  
    96  // writeSSHKeys writes out a new ~/.ssh/authorised_keys file, retaining any non Juju keys
    97  // and adding the specified set of Juju keys.
    98  func (kw *keyupdaterWorker) writeSSHKeys(jujuKeys []string) error {
    99  	allKeys := kw.nonJujuKeys
   100  	// Ensure any Juju keys have the required prefix in their comment.
   101  	for i, key := range jujuKeys {
   102  		jujuKeys[i] = ssh.EnsureJujuComment(key)
   103  	}
   104  	allKeys = append(allKeys, jujuKeys...)
   105  	return ssh.ReplaceKeys(SSHUser, allKeys...)
   106  }
   107  
   108  // Handle is defined on the worker.NotifyWatchHandler interface.
   109  func (kw *keyupdaterWorker) Handle(_ <-chan struct{}) error {
   110  	// Read the keys that Juju has.
   111  	newKeys, err := kw.st.AuthorisedKeys(kw.tag)
   112  	if err != nil {
   113  		err = errors.Annotatef(err, "reading Juju ssh keys for %q", kw.tag)
   114  		logger.Infof(err.Error())
   115  		return err
   116  	}
   117  	// Figure out if any keys have been added or deleted.
   118  	newJujuKeys := set.NewStrings(newKeys...)
   119  	deleted := kw.jujuKeys.Difference(newJujuKeys)
   120  	added := newJujuKeys.Difference(kw.jujuKeys)
   121  	if added.Size() > 0 || deleted.Size() > 0 {
   122  		logger.Debugf("adding ssh keys to authorised keys: %v", added)
   123  		logger.Debugf("deleting ssh keys from authorised keys: %v", deleted)
   124  		if err = kw.writeSSHKeys(newKeys); err != nil {
   125  			err = errors.Annotate(err, "updating ssh keys")
   126  			logger.Infof(err.Error())
   127  			return err
   128  		}
   129  	}
   130  	kw.jujuKeys = newJujuKeys
   131  	return nil
   132  }
   133  
   134  // TearDown is defined on the worker.NotifyWatchHandler interface.
   135  func (kw *keyupdaterWorker) TearDown() error {
   136  	// Nothing to do here.
   137  	return nil
   138  }