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 }