github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/controller"
    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    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, 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 ControllerConfigGetter, 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  		return errors.New("no state serving info, cannot regenerate server certificate")
   126  	}
   127  	caPrivateKey := stateInfo.CAPrivateKey
   128  	if caPrivateKey == "" {
   129  		logger.Errorf("no CA cert private key, cannot regenerate server certificate")
   130  		return nil
   131  	}
   132  
   133  	cfg, err := c.configGetter.ControllerConfig()
   134  	if err != nil {
   135  		return errors.Annotate(err, "cannot read controller config")
   136  	}
   137  
   138  	// For backwards compatibility, we must include "anything", "juju-apiserver"
   139  	// and "juju-mongodb" as hostnames as that is what clients specify
   140  	// as the hostname for verification (this certicate is used both
   141  	// for serving MongoDB and API server connections).  We also
   142  	// explicitly include localhost.
   143  	serverAddrs := []string{"localhost", "juju-apiserver", "juju-mongodb", "anything"}
   144  	for _, addr := range addresses {
   145  		if addr.Value == "localhost" {
   146  			continue
   147  		}
   148  		serverAddrs = append(serverAddrs, addr.Value)
   149  	}
   150  	newServerAddrs, update, err := updateRequired(stateInfo.Cert, serverAddrs)
   151  	if err != nil {
   152  		return errors.Annotate(err, "cannot determine if cert update needed")
   153  	}
   154  	if !update {
   155  		logger.Debugf("no certificate update required")
   156  		return nil
   157  	}
   158  
   159  	// Generate a new controller certificate with the machine addresses in the SAN value.
   160  	caCert, hasCACert := cfg.CACert()
   161  	if !hasCACert {
   162  		return errors.New("configuration has no ca-cert")
   163  	}
   164  	newCert, newKey, err := controller.GenerateControllerCertAndKey(caCert, caPrivateKey, newServerAddrs)
   165  	if err != nil {
   166  		return errors.Annotate(err, "cannot generate controller certificate")
   167  	}
   168  	stateInfo.Cert = string(newCert)
   169  	stateInfo.PrivateKey = string(newKey)
   170  	err = c.setter(stateInfo, done)
   171  	if err != nil {
   172  		return errors.Annotate(err, "cannot write agent config")
   173  	}
   174  	logger.Infof("controller cerificate addresses updated to %q", newServerAddrs)
   175  	return nil
   176  }
   177  
   178  // updateRequired returns true and a list of merged addresses if any of the
   179  // new addresses are not yet contained in the server cert SAN list.
   180  func updateRequired(serverCert string, newAddrs []string) ([]string, bool, error) {
   181  	x509Cert, err := cert.ParseCert(serverCert)
   182  	if err != nil {
   183  		return nil, false, errors.Annotate(err, "cannot parse existing TLS certificate")
   184  	}
   185  	existingAddr := set.NewStrings()
   186  	for _, ip := range x509Cert.IPAddresses {
   187  		existingAddr.Add(ip.String())
   188  	}
   189  	logger.Debugf("existing cert addresses %v", existingAddr)
   190  	logger.Debugf("new addresses %v", newAddrs)
   191  	// Does newAddr contain any that are not already in existingAddr?
   192  	newAddrSet := set.NewStrings(newAddrs...)
   193  	update := newAddrSet.Difference(existingAddr).Size() > 0
   194  	newAddrSet = newAddrSet.Union(existingAddr)
   195  	return newAddrSet.SortedValues(), update, nil
   196  }
   197  
   198  // TearDown is defined on the NotifyWatchHandler interface.
   199  func (c *CertificateUpdater) TearDown() error {
   200  	return nil
   201  }