github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/certupdater/certupdater.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package certupdater 5 6 import ( 7 "reflect" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/utils/cert" 13 "gopkg.in/juju/worker.v1" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/controller" 17 "github.com/juju/juju/network" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/watcher/legacy" 20 ) 21 22 var logger = loggo.GetLogger("juju.worker.certupdater") 23 24 // CertificateUpdater is responsible for generating controller certificates. 25 // 26 // In practice, CertificateUpdater is used by a controller's machine agent to watch 27 // that server's machines addresses in state, and write a new certificate to the 28 // agent's config file. 29 type CertificateUpdater struct { 30 addressWatcher AddressWatcher 31 getter StateServingInfoGetter 32 setter StateServingInfoSetter 33 configGetter ControllerConfigGetter 34 hostPortsGetter APIHostPortsGetter 35 addresses []network.Address 36 } 37 38 // AddressWatcher is an interface that is provided to NewCertificateUpdater 39 // which can be used to watch for machine address changes. 40 type AddressWatcher interface { 41 WatchAddresses() state.NotifyWatcher 42 Addresses() (addresses []network.Address) 43 } 44 45 // ControllerConfigGetter is an interface that is provided to NewCertificateUpdater 46 // which can be used to get the controller config. 47 type ControllerConfigGetter interface { 48 ControllerConfig() (controller.Config, error) 49 } 50 51 // StateServingInfoGetter is an interface that is provided to NewCertificateUpdater 52 // whose StateServingInfo method will be invoked to get state serving info. 53 type StateServingInfoGetter interface { 54 StateServingInfo() (params.StateServingInfo, bool) 55 } 56 57 // StateServingInfoSetter defines a function that is called to set a 58 // StateServingInfo value with a newly generated certificate. 59 type StateServingInfoSetter func(info params.StateServingInfo) error 60 61 // APIHostPortsGetter is an interface that is provided to NewCertificateUpdater. 62 // It returns all known API addresses. 63 type APIHostPortsGetter interface { 64 APIHostPortsForClients() ([][]network.HostPort, error) 65 } 66 67 // Config holds the configuration for the certificate updater worker. 68 type Config struct { 69 AddressWatcher AddressWatcher 70 StateServingInfoGetter StateServingInfoGetter 71 StateServingInfoSetter StateServingInfoSetter 72 ControllerConfigGetter ControllerConfigGetter 73 APIHostPortsGetter APIHostPortsGetter 74 } 75 76 // NewCertificateUpdater returns a worker.Worker that watches for changes to 77 // machine addresses and then generates a new controller certificate with those 78 // addresses in the certificate's SAN value. 79 func NewCertificateUpdater(config Config) worker.Worker { 80 return legacy.NewNotifyWorker(&CertificateUpdater{ 81 addressWatcher: config.AddressWatcher, 82 configGetter: config.ControllerConfigGetter, 83 hostPortsGetter: config.APIHostPortsGetter, 84 getter: config.StateServingInfoGetter, 85 setter: config.StateServingInfoSetter, 86 }) 87 } 88 89 // SetUp is defined on the NotifyWatchHandler interface. 90 func (c *CertificateUpdater) SetUp() (state.NotifyWatcher, error) { 91 // Populate certificate SAN with any addresses we know about now. 92 apiHostPorts, err := c.hostPortsGetter.APIHostPortsForClients() 93 if err != nil { 94 return nil, errors.Annotate(err, "retrieving initial server addresses") 95 } 96 var initialSANAddresses []network.Address 97 for _, server := range apiHostPorts { 98 for _, nhp := range server { 99 if nhp.Scope != network.ScopeCloudLocal { 100 continue 101 } 102 initialSANAddresses = append(initialSANAddresses, nhp.Address) 103 } 104 } 105 if err := c.updateCertificate(initialSANAddresses); err != nil { 106 return nil, errors.Annotate(err, "setting initial certificate SAN list") 107 } 108 return c.addressWatcher.WatchAddresses(), nil 109 } 110 111 // Handle is defined on the NotifyWatchHandler interface. 112 func (c *CertificateUpdater) Handle(done <-chan struct{}) error { 113 addresses := c.addressWatcher.Addresses() 114 if reflect.DeepEqual(addresses, c.addresses) { 115 // Sometimes the watcher will tell us things have changed, when they 116 // haven't as far as we can tell. 117 logger.Debugf("addresses haven't really changed since last updated cert") 118 return nil 119 } 120 return c.updateCertificate(addresses) 121 } 122 123 func (c *CertificateUpdater) updateCertificate(addresses []network.Address) error { 124 logger.Debugf("new machine addresses: %#v", addresses) 125 c.addresses = addresses 126 127 // Older Juju deployments will not have the CA cert private key 128 // available. 129 stateInfo, ok := c.getter.StateServingInfo() 130 if !ok { 131 return errors.New("no state serving info, cannot regenerate server certificate") 132 } 133 caPrivateKey := stateInfo.CAPrivateKey 134 if caPrivateKey == "" { 135 logger.Errorf("no CA cert private key, cannot regenerate server certificate") 136 return nil 137 } 138 139 cfg, err := c.configGetter.ControllerConfig() 140 if err != nil { 141 return errors.Annotate(err, "cannot read controller config") 142 } 143 144 // For backwards compatibility, we must include "anything", "juju-apiserver" 145 // and "juju-mongodb" as hostnames as that is what clients specify 146 // as the hostname for verification (this certificate is used both 147 // for serving MongoDB and API server connections). We also 148 // explicitly include localhost. 149 serverAddrs := []string{"localhost", "juju-apiserver", "juju-mongodb", "anything"} 150 for _, addr := range addresses { 151 if addr.Value == "localhost" { 152 continue 153 } 154 serverAddrs = append(serverAddrs, addr.Value) 155 } 156 newServerAddrs, update, err := updateRequired(stateInfo.Cert, serverAddrs) 157 if err != nil { 158 return errors.Annotate(err, "cannot determine if cert update needed") 159 } 160 if !update { 161 logger.Debugf("no certificate update required") 162 return nil 163 } 164 165 // Generate a new controller certificate with the machine addresses in the SAN value. 166 caCert, hasCACert := cfg.CACert() 167 if !hasCACert { 168 return errors.New("configuration has no ca-cert") 169 } 170 newCert, newKey, err := controller.GenerateControllerCertAndKey(caCert, caPrivateKey, newServerAddrs) 171 if err != nil { 172 return errors.Annotate(err, "cannot generate controller certificate") 173 } 174 stateInfo.Cert = newCert 175 stateInfo.PrivateKey = newKey 176 err = c.setter(stateInfo) 177 if err != nil { 178 return errors.Annotate(err, "cannot write agent config") 179 } 180 logger.Infof("controller certificate addresses updated to %q", newServerAddrs) 181 return nil 182 } 183 184 // updateRequired returns true and a list of merged addresses if any of the 185 // new addresses are not yet contained in the server cert SAN list. 186 func updateRequired(serverCert string, newAddrs []string) ([]string, bool, error) { 187 x509Cert, err := cert.ParseCert(serverCert) 188 if err != nil { 189 return nil, false, errors.Annotate(err, "cannot parse existing TLS certificate") 190 } 191 existingAddr := set.NewStrings() 192 for _, ip := range x509Cert.IPAddresses { 193 existingAddr.Add(ip.String()) 194 } 195 logger.Debugf("existing cert addresses %v", existingAddr) 196 logger.Debugf("new addresses %v", newAddrs) 197 // Does newAddr contain any that are not already in existingAddr? 198 newAddrSet := set.NewStrings(newAddrs...) 199 update := newAddrSet.Difference(existingAddr).Size() > 0 200 newAddrSet = newAddrSet.Union(existingAddr) 201 return newAddrSet.SortedValues(), update, nil 202 } 203 204 // TearDown is defined on the NotifyWatchHandler interface. 205 func (c *CertificateUpdater) TearDown() error { 206 return nil 207 }