From 1f6501895feb08ca182f439315261c0c25835c8a Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Sun, 8 Apr 2018 21:57:32 -0700 Subject: [PATCH] Add CA bootstrapping on establishing leadership --- agent/consul/leader.go | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/agent/consul/leader.go b/agent/consul/leader.go index d950d71bac..5162012623 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -1,12 +1,15 @@ package consul import ( + "crypto/x509" "fmt" "net" "strconv" "sync" "time" + "github.com/hashicorp/consul/agent/connect" + "github.com/armon/go-metrics" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/consul/autopilot" @@ -210,6 +213,10 @@ func (s *Server) establishLeadership() error { s.getOrCreateAutopilotConfig() s.autopilot.Start() + + // todo(kyhavlov): start a goroutine here for handling periodic CA rotation + s.bootstrapCA() + s.setConsistentReadReady() return nil } @@ -359,6 +366,106 @@ func (s *Server) getOrCreateAutopilotConfig() *autopilot.Config { return config } +// getOrCreateCAConfig is used to get the CA config, initializing it if necessary +func (s *Server) getOrCreateCAConfig() (*structs.CAConfiguration, error) { + state := s.fsm.State() + _, config, err := state.CAConfig() + if err != nil { + return nil, err + } + if config != nil { + return config, nil + } + + config = s.config.CAConfig + req := structs.CARequest{ + Op: structs.CAOpSetConfig, + Config: config, + } + if _, err = s.raftApply(structs.ConnectCARequestType, req); err != nil { + return nil, err + } + + return config, nil +} + +// bootstrapCA handles the initialization of a new CA provider +func (s *Server) bootstrapCA() error { + conf, err := s.getOrCreateCAConfig() + if err != nil { + return err + } + + // Initialize the right provider based on the config + var provider connect.CAProvider + switch conf.Provider { + case structs.ConsulCAProvider: + provider, err = connect.NewConsulCAProvider(conf.Config) + if err != nil { + return err + } + default: + return fmt.Errorf("unknown CA provider %q", conf.Provider) + } + + s.caProviderLock.Lock() + s.caProvider = provider + s.caProviderLock.Unlock() + + // Get the intermediate cert from the CA + trustedCA, err := provider.ActiveIntermediate() + if err != nil { + return fmt.Errorf("error getting intermediate cert: %v", err) + } + + // Check if this CA is already initialized + state := s.fsm.State() + _, root, err := state.CARootActive(nil) + if err != nil { + return err + } + // Exit early if the root is already in the state store. + if root != nil && root.ID == trustedCA.ID { + return nil + } + + // Get the highest index + idx, _, err := state.CARoots(nil) + if err != nil { + return err + } + + // Store the intermediate in raft + resp, err := s.raftApply(structs.ConnectCARequestType, &structs.CARequest{ + Op: structs.CAOpSetRoots, + Index: idx, + Roots: []*structs.CARoot{trustedCA}, + }) + if err != nil { + s.logger.Printf("[ERR] connect: Apply failed %v", err) + return err + } + if respErr, ok := resp.(error); ok { + return respErr + } + + s.logger.Printf("[INFO] connect: initialized CA with provider %q", conf.Provider) + + return nil +} + +// signConnectCert signs a cert for a service using the currently configured CA provider +func (s *Server) signConnectCert(service *connect.SpiffeIDService, csr *x509.CertificateRequest) (*structs.IssuedCert, error) { + s.caProviderLock.RLock() + defer s.caProviderLock.RUnlock() + + cert, err := s.caProvider.Sign(service, csr) + if err != nil { + return nil, err + } + return cert, nil +} + // reconcileReaped is used to reconcile nodes that have failed and been reaped // from Serf but remain in the catalog. This is done by looking for unknown nodes with serfHealth checks registered. // We generate a "reap" event to cause the node to be cleaned up.