Add the root rotation mechanism to the CA config endpoint

This commit is contained in:
Kyle Havlovitz 2018-04-20 18:46:02 -07:00 committed by Mitchell Hashimoto
parent a585a0ba10
commit bbfcb278e1
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
8 changed files with 396 additions and 135 deletions

View File

@ -38,6 +38,18 @@ func ParseSigner(pemValue string) (crypto.Signer, error) {
case "EC PRIVATE KEY": case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(block.Bytes) return x509.ParseECPrivateKey(block.Bytes)
case "PRIVATE KEY":
signer, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
pk, ok := signer.(crypto.Signer)
if !ok {
return nil, fmt.Errorf("private key is not a valid format")
}
return pk, nil
default: default:
return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type) return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type)
} }

View File

@ -10,9 +10,10 @@ import (
// an external CA that provides leaf certificate signing for // an external CA that provides leaf certificate signing for
// given SpiffeIDServices. // given SpiffeIDServices.
type CAProvider interface { type CAProvider interface {
SetConfiguration(raw map[string]interface{}) error
ActiveRoot() (*structs.CARoot, error) ActiveRoot() (*structs.CARoot, error)
ActiveIntermediate() (*structs.CARoot, error) ActiveIntermediate() (*structs.CARoot, error)
GenerateIntermediate() (*structs.CARoot, error) GenerateIntermediate() (*structs.CARoot, error)
Sign(*SpiffeIDService, *x509.CertificateRequest) (*structs.IssuedCert, error) Sign(*SpiffeIDService, *x509.CertificateRequest) (*structs.IssuedCert, error)
//SignCA(*x509.CertificateRequest) (*structs.IssuedCert, error)
Teardown() error
} }

View File

@ -2,6 +2,7 @@ package consul
import ( import (
"fmt" "fmt"
"reflect"
"github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/connect"
@ -60,9 +61,95 @@ func (s *ConnectCA) ConfigurationSet(
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
// Commit // Exit early if it's a no-op change
// todo(kyhavlov): trigger a bootstrap here when the provider changes state := s.srv.fsm.State()
args.Op = structs.CAOpSetConfig _, config, err := state.CAConfig()
if err != nil {
return err
}
if args.Config.Provider == config.Provider && reflect.DeepEqual(args.Config.Config, config.Config) {
return nil
}
// Create a new instance of the provider described by the config
// and get the current active root CA. This acts as a good validation
// of the config and makes sure the provider is functioning correctly
// before we commit any changes to Raft.
newProvider, err := s.srv.createCAProvider(args.Config)
if err != nil {
return fmt.Errorf("could not initialize provider: %v", err)
}
newActiveRoot, err := newProvider.ActiveRoot()
if err != nil {
return err
}
// Compare the new provider's root CA ID to the current one. If they
// match, just update the existing provider with the new config.
// If they don't match, begin the root rotation process.
_, root, err := state.CARootActive(nil)
if err != nil {
return err
}
if root != nil && root.ID == newActiveRoot.ID {
args.Op = structs.CAOpSetConfig
resp, err := s.srv.raftApply(structs.ConnectCARequestType, args)
if err != nil {
return err
}
if respErr, ok := resp.(error); ok {
return respErr
}
// If the config has been committed, update the local provider instance
s.srv.setCAProvider(newProvider)
s.srv.logger.Printf("[INFO] connect: provider config updated")
return nil
}
// At this point, we know the config change has trigged a root rotation,
// either by swapping the provider type or changing the provider's config
// to use a different root certificate.
// If it's a config change that would trigger a rotation (different provider/root):
// -1. Create an instance of the provider described by the new config
// 2. Get the intermediate from the new provider
// 3. Generate a CSR for the new intermediate, call SignCA on the old/current provider
// to get the cross-signed intermediate
// ~4. Get the active root for the new provider, append the intermediate from step 3
// to its list of intermediates
// -5. Update the roots and CA config in the state store at the same time, finally switching
// to the new provider
// -6. Call teardown on the old provider, so it can clean up whatever it needs to
/*_, err := newProvider.ActiveIntermediate()
if err != nil {
return err
}*/
// Update the roots and CA config in the state store at the same time
idx, roots, err := state.CARoots(nil)
if err != nil {
return err
}
var newRoots structs.CARoots
for _, r := range roots {
newRoot := *r
if newRoot.Active {
newRoot.Active = false
}
newRoots = append(newRoots, &newRoot)
}
newRoots = append(newRoots, newActiveRoot)
args.Op = structs.CAOpSetRootsAndConfig
args.Index = idx
args.Roots = newRoots
resp, err := s.srv.raftApply(structs.ConnectCARequestType, args) resp, err := s.srv.raftApply(structs.ConnectCARequestType, args)
if err != nil { if err != nil {
return err return err
@ -71,6 +158,17 @@ func (s *ConnectCA) ConfigurationSet(
return respErr return respErr
} }
// If the config has been committed, update the local provider instance
// and call teardown on the old provider
oldProvider := s.srv.getCAProvider()
s.srv.setCAProvider(newProvider)
if err := oldProvider.Teardown(); err != nil {
return err
}
s.srv.logger.Printf("[INFO] connect: CA rotated to the new root under %q provider", args.Config.Provider)
return nil return nil
} }

View File

@ -29,28 +29,94 @@ type ConsulCAProviderConfig struct {
type ConsulCAProvider struct { type ConsulCAProvider struct {
config *ConsulCAProviderConfig config *ConsulCAProviderConfig
// todo(kyhavlov): store these directly in the state store id string
// and pass a reference to the state to this provider instead of
// having these values here
srv *Server srv *Server
sync.RWMutex sync.RWMutex
} }
// NewConsulCAProvider returns a new instance of the Consul CA provider,
// bootstrapping its state in the state store necessary
func NewConsulCAProvider(rawConfig map[string]interface{}, srv *Server) (*ConsulCAProvider, error) { func NewConsulCAProvider(rawConfig map[string]interface{}, srv *Server) (*ConsulCAProvider, error) {
provider := &ConsulCAProvider{srv: srv} conf, err := decodeConfig(rawConfig)
provider.SetConfiguration(rawConfig)
return provider, nil
}
func (c *ConsulCAProvider) SetConfiguration(raw map[string]interface{}) error {
conf, err := decodeConfig(raw)
if err != nil { if err != nil {
return err return nil, err
}
provider := &ConsulCAProvider{
config: conf,
srv: srv,
id: fmt.Sprintf("%s,%s", conf.PrivateKey, conf.RootCert),
} }
c.config = conf // Check if this configuration of the provider has already been
return nil // initialized in the state store.
state := srv.fsm.State()
_, providerState, err := state.CAProviderState(provider.id)
if err != nil {
return nil, err
}
// Exit early if the state store has already been populated for this config.
if providerState != nil {
return provider, nil
}
newState := structs.CAConsulProviderState{
ID: provider.id,
}
// Write the initial provider state to get the index to use for the
// CA serial number.
{
args := &structs.CARequest{
Op: structs.CAOpSetProviderState,
ProviderState: &newState,
}
resp, err := srv.raftApply(structs.ConnectCARequestType, args)
if err != nil {
return nil, err
}
if respErr, ok := resp.(error); ok {
return nil, respErr
}
}
idx, _, err := state.CAProviderState(provider.id)
if err != nil {
return nil, err
}
// Generate a private key if needed
if conf.PrivateKey == "" {
pk, err := generatePrivateKey()
if err != nil {
return nil, err
}
newState.PrivateKey = pk
} else {
newState.PrivateKey = conf.PrivateKey
}
// Generate the root CA
ca, err := provider.generateCA(newState.PrivateKey, conf.RootCert, idx+1)
if err != nil {
return nil, fmt.Errorf("error generating CA: %v", err)
}
newState.CARoot = ca
// Write the provider state
args := &structs.CARequest{
Op: structs.CAOpSetProviderState,
ProviderState: &newState,
}
resp, err := srv.raftApply(structs.ConnectCARequestType, args)
if err != nil {
return nil, err
}
if respErr, ok := resp.(error); ok {
return nil, respErr
}
return provider, nil
} }
func decodeConfig(raw map[string]interface{}) (*ConsulCAProviderConfig, error) { func decodeConfig(raw map[string]interface{}) (*ConsulCAProviderConfig, error) {
@ -59,59 +125,22 @@ func decodeConfig(raw map[string]interface{}) (*ConsulCAProviderConfig, error) {
return nil, fmt.Errorf("error decoding config: %s", err) return nil, fmt.Errorf("error decoding config: %s", err)
} }
if config.PrivateKey == "" && config.RootCert != "" {
return nil, fmt.Errorf("must provide a private key when providing a root cert")
}
return config, nil return config, nil
} }
// Return the active root CA and generate a new one if needed // Return the active root CA and generate a new one if needed
func (c *ConsulCAProvider) ActiveRoot() (*structs.CARoot, error) { func (c *ConsulCAProvider) ActiveRoot() (*structs.CARoot, error) {
state := c.srv.fsm.State() state := c.srv.fsm.State()
_, providerState, err := state.CAProviderState() _, providerState, err := state.CAProviderState(c.id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var update bool return providerState.CARoot, nil
var newState structs.CAConsulProviderState
if providerState != nil {
newState = *providerState
}
// Generate a private key if needed
if providerState == nil || providerState.PrivateKey == "" {
pk, err := generatePrivateKey()
if err != nil {
return nil, err
}
newState.PrivateKey = pk
update = true
}
// Generate a root CA if needed
if providerState == nil || providerState.CARoot == nil {
ca, err := c.generateCA(newState.PrivateKey, newState.RootIndex+1)
if err != nil {
return nil, err
}
newState.CARoot = ca
newState.RootIndex += 1
update = true
}
// Update the provider state if we generated a new private key/cert
if update {
args := &structs.CARequest{
Op: structs.CAOpSetProviderState,
ProviderState: &newState,
}
resp, err := c.srv.raftApply(structs.ConnectCARequestType, args)
if err != nil {
return nil, err
}
if respErr, ok := resp.(error); ok {
return nil, respErr
}
}
return newState.CARoot, nil
} }
func (c *ConsulCAProvider) ActiveIntermediate() (*structs.CARoot, error) { func (c *ConsulCAProvider) ActiveIntermediate() (*structs.CARoot, error) {
@ -120,15 +149,12 @@ func (c *ConsulCAProvider) ActiveIntermediate() (*structs.CARoot, error) {
func (c *ConsulCAProvider) GenerateIntermediate() (*structs.CARoot, error) { func (c *ConsulCAProvider) GenerateIntermediate() (*structs.CARoot, error) {
state := c.srv.fsm.State() state := c.srv.fsm.State()
_, providerState, err := state.CAProviderState() idx, providerState, err := state.CAProviderState(c.id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if providerState == nil {
return nil, fmt.Errorf("CA provider not yet initialized")
}
ca, err := c.generateCA(providerState.PrivateKey, providerState.RootIndex+1) ca, err := c.generateCA(providerState.PrivateKey, "", idx+1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -136,12 +162,34 @@ func (c *ConsulCAProvider) GenerateIntermediate() (*structs.CARoot, error) {
return ca, nil return ca, nil
} }