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