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