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  }