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