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  }