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  }