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 }