Merge pull request #11514 from hashicorp/dnephin/ca-fix-secondary-init
ca: properly handle the case where the secondary initializes after the primary
This commit is contained in:
commit
0ad360fadf
|
@ -0,0 +1,6 @@
|
||||||
|
```release-note:improvement
|
||||||
|
connect/ca: Return an error when querying roots from uninitialized CA.
|
||||||
|
```
|
||||||
|
```release-note:bug
|
||||||
|
connect/ca: Allow secondary initialization to resume after being deferred due to unreachable or incompatible primary DC servers.
|
||||||
|
```
|
|
@ -44,7 +44,7 @@ type caServerDelegate interface {
|
||||||
forwardDC(method, dc string, args interface{}, reply interface{}) error
|
forwardDC(method, dc string, args interface{}, reply interface{}) error
|
||||||
generateCASignRequest(csr string) *structs.CASignRequest
|
generateCASignRequest(csr string) *structs.CASignRequest
|
||||||
|
|
||||||
checkServersProvider
|
ServersSupportMultiDCConnectCA() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// CAManager is a wrapper around CA operations such as updating roots, an intermediate
|
// CAManager is a wrapper around CA operations such as updating roots, an intermediate
|
||||||
|
@ -127,6 +127,17 @@ func (c *caDelegateWithState) generateCASignRequest(csr string) *structs.CASignR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *caDelegateWithState) ServersSupportMultiDCConnectCA() error {
|
||||||
|
versionOk, primaryFound := ServersInDCMeetMinimumVersion(c.Server, c.Server.config.PrimaryDatacenter, minMultiDCConnectVersion)
|
||||||
|
if !primaryFound {
|
||||||
|
return fmt.Errorf("primary datacenter is unreachable")
|
||||||
|
}
|
||||||
|
if !versionOk {
|
||||||
|
return fmt.Errorf("all servers in the primary datacenter are not at the minimum version %v", minMultiDCConnectVersion)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewCAManager(delegate caServerDelegate, leaderRoutineManager *routine.Manager, logger hclog.Logger, config *Config) *CAManager {
|
func NewCAManager(delegate caServerDelegate, leaderRoutineManager *routine.Manager, logger hclog.Logger, config *Config) *CAManager {
|
||||||
return &CAManager{
|
return &CAManager{
|
||||||
delegate: delegate,
|
delegate: delegate,
|
||||||
|
@ -202,7 +213,8 @@ func (c *CAManager) initializeCAConfig() (*structs.CAConfiguration, error) {
|
||||||
}
|
}
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = c.serverConf.CAConfig
|
config = c.serverConf.CAConfig
|
||||||
if config.ClusterID == "" {
|
|
||||||
|
if c.serverConf.Datacenter == c.serverConf.PrimaryDatacenter && config.ClusterID == "" {
|
||||||
id, err := uuid.GenerateUUID()
|
id, err := uuid.GenerateUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -408,18 +420,8 @@ func (c *CAManager) InitializeCA() (reterr error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CAManager) secondaryInitialize(provider ca.Provider, conf *structs.CAConfiguration) error {
|
func (c *CAManager) secondaryInitialize(provider ca.Provider, conf *structs.CAConfiguration) error {
|
||||||
// If this isn't the primary DC, run the secondary DC routine if the primary has already been upgraded to at least 1.6.0
|
if err := c.delegate.ServersSupportMultiDCConnectCA(); err != nil {
|
||||||
versionOk, foundPrimary := ServersInDCMeetMinimumVersion(c.delegate, c.serverConf.PrimaryDatacenter, minMultiDCConnectVersion)
|
return fmt.Errorf("initialization will be deferred: %w", err)
|
||||||
if !foundPrimary {
|
|
||||||
c.logger.Warn("primary datacenter is configured but unreachable - deferring initialization of the secondary datacenter CA")
|
|
||||||
// return nil because we will initialize the secondary CA later
|
|
||||||
return nil
|
|
||||||
} else if !versionOk {
|
|
||||||
// return nil because we will initialize the secondary CA later
|
|
||||||
c.logger.Warn("servers in the primary datacenter are not at least at the minimum version - deferring initialization of the secondary datacenter CA",
|
|
||||||
"min_version", minMultiDCConnectVersion.String(),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the root CA to see if we need to refresh our intermediate.
|
// Get the root CA to see if we need to refresh our intermediate.
|
||||||
|
@ -1266,15 +1268,11 @@ func (c *CAManager) secondaryUpdateRoots(roots structs.IndexedCARoots) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !c.secondaryIsCAConfigured() {
|
if !c.secondaryIsCAConfigured() {
|
||||||
versionOk, primaryFound := ServersInDCMeetMinimumVersion(c.delegate, c.serverConf.PrimaryDatacenter, minMultiDCConnectVersion)
|
if err := c.delegate.ServersSupportMultiDCConnectCA(); err != nil {
|
||||||
if !primaryFound {
|
return fmt.Errorf("failed to initialize while updating primary roots: %w", err)
|
||||||
return fmt.Errorf("Primary datacenter is unreachable - deferring secondary CA initialization")
|
|
||||||
}
|
}
|
||||||
|
if err := c.secondaryInitializeProvider(provider, roots); err != nil {
|
||||||
if versionOk {
|
return fmt.Errorf("Failed to initialize secondary CA provider: %v", err)
|
||||||
if err := c.secondaryInitializeProvider(provider, roots); err != nil {
|
|
||||||
return fmt.Errorf("Failed to initialize secondary CA provider: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,15 +14,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
|
||||||
"github.com/hashicorp/serf/serf"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
ca "github.com/hashicorp/consul/agent/connect/ca"
|
ca "github.com/hashicorp/consul/agent/connect/ca"
|
||||||
"github.com/hashicorp/consul/agent/consul/state"
|
"github.com/hashicorp/consul/agent/consul/state"
|
||||||
"github.com/hashicorp/consul/agent/metadata"
|
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/agent/token"
|
"github.com/hashicorp/consul/agent/token"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
|
@ -60,12 +57,8 @@ func (m *mockCAServerDelegate) IsLeader() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockCAServerDelegate) CheckServers(datacenter string, fn func(*metadata.Server) bool) {
|
func (m *mockCAServerDelegate) ServersSupportMultiDCConnectCA() error {
|
||||||
ver, _ := version.NewVersion("1.6.0")
|
return nil
|
||||||
fn(&metadata.Server{
|
|
||||||
Status: serf.StatusAlive,
|
|
||||||
Build: *ver,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockCAServerDelegate) ApplyCALeafRequest() (uint64, error) {
|
func (m *mockCAServerDelegate) ApplyCALeafRequest() (uint64, error) {
|
||||||
|
|
|
@ -1094,7 +1094,6 @@ func TestLeader_SecondaryCA_UpgradeBeforePrimary(t *testing.T) {
|
||||||
|
|
||||||
// Wait for the secondary transition to happen and then verify the secondary DC
|
// Wait for the secondary transition to happen and then verify the secondary DC
|
||||||
// has both roots present.
|
// has both roots present.
|
||||||
secondaryProvider, _ := getCAProviderWithLock(s2)
|
|
||||||
retry.Run(t, func(r *retry.R) {
|
retry.Run(t, func(r *retry.R) {
|
||||||
state1 := s1.fsm.State()
|
state1 := s1.fsm.State()
|
||||||
_, roots1, err := state1.CARoots(nil)
|
_, roots1, err := state1.CARoots(nil)
|
||||||
|
@ -1110,15 +1109,18 @@ func TestLeader_SecondaryCA_UpgradeBeforePrimary(t *testing.T) {
|
||||||
require.Equal(r, roots1[0].ID, roots2[0].ID)
|
require.Equal(r, roots1[0].ID, roots2[0].ID)
|
||||||
require.Equal(r, roots1[0].RootCert, roots2[0].RootCert)
|
require.Equal(r, roots1[0].RootCert, roots2[0].RootCert)
|
||||||
|
|
||||||
|
secondaryProvider, _ := getCAProviderWithLock(s2)
|
||||||
inter, err := secondaryProvider.ActiveIntermediate()
|
inter, err := secondaryProvider.ActiveIntermediate()
|
||||||
require.NoError(r, err)
|
require.NoError(r, err)
|
||||||
require.NotEmpty(r, inter, "should have valid intermediate")
|
require.NotEmpty(r, inter, "should have valid intermediate")
|
||||||
})
|
})
|
||||||
|
|
||||||
_, caRoot := getCAProviderWithLock(s1)
|
secondaryProvider, _ := getCAProviderWithLock(s2)
|
||||||
intermediatePEM, err := secondaryProvider.ActiveIntermediate()
|
intermediatePEM, err := secondaryProvider.ActiveIntermediate()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, caRoot := getCAProviderWithLock(s1)
|
||||||
|
|
||||||
// Have dc2 sign a leaf cert and make sure the chain is correct.
|
// Have dc2 sign a leaf cert and make sure the chain is correct.
|
||||||
spiffeService := &connect.SpiffeIDService{
|
spiffeService := &connect.SpiffeIDService{
|
||||||
Host: "node1",
|
Host: "node1",
|
||||||
|
|
|
@ -16,19 +16,23 @@ func (s *Server) getCARoots(ws memdb.WatchSet, state *state.Store) (*structs.Ind
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("CA has not finished initializing")
|
||||||
|
}
|
||||||
|
|
||||||
indexedRoots := &structs.IndexedCARoots{}
|
indexedRoots := &structs.IndexedCARoots{}
|
||||||
|
|
||||||
if config != nil {
|
// Build TrustDomain based on the ClusterID stored.
|
||||||
// Build TrustDomain based on the ClusterID stored.
|
signingID := connect.SpiffeIDSigningForCluster(config)
|
||||||
signingID := connect.SpiffeIDSigningForCluster(config)
|
if signingID == nil {
|
||||||
if signingID == nil {
|
// If CA is bootstrapped at all then this should never happen but be
|
||||||
// If CA is bootstrapped at all then this should never happen but be
|
// defensive.
|
||||||
// defensive.
|
return nil, fmt.Errorf("no cluster trust domain setup")
|
||||||
return nil, fmt.Errorf("no cluster trust domain setup")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
indexedRoots.TrustDomain = signingID.Host()
|
indexedRoots.TrustDomain = signingID.Host()
|
||||||
|
if indexedRoots.TrustDomain == "" {
|
||||||
|
return nil, fmt.Errorf("CA has not finished initializing")
|
||||||
}
|
}
|
||||||
|
|
||||||
indexedRoots.Index, indexedRoots.Roots = index, roots
|
indexedRoots.Index, indexedRoots.Roots = index, roots
|
||||||
|
|
|
@ -180,8 +180,6 @@ func (s *Store) caSetConfigTxn(idx uint64, tx WriteTxn, config *structs.CAConfig
|
||||||
if prev != nil {
|
if prev != nil {
|
||||||
existing := prev.(*structs.CAConfiguration)
|
existing := prev.(*structs.CAConfiguration)
|
||||||
config.CreateIndex = existing.CreateIndex
|
config.CreateIndex = existing.CreateIndex
|
||||||
// Allow the ClusterID to change if it's provided by an internal operation, such
|
|
||||||
// as a primary datacenter being switched to secondary mode.
|
|
||||||
if config.ClusterID == "" {
|
if config.ClusterID == "" {
|
||||||
config.ClusterID = existing.ClusterID
|
config.ClusterID = existing.ClusterID
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue