Skip to content

Commit

Permalink
Merge pull request #11514 (#11539)
Browse files Browse the repository at this point in the history
Backport of #11514

---------

Fixes #11367

Previously `secondaryInitialize` would return nil in this case, which prevented the
deferred initialize from happening, and left the CA in an uninitialized state until a config
update or root rotation.

To fix this I extracted the common parts into the delegate implementation. However looking at this
again, it seems like the handling in secondaryUpdateRoots is impossible, because that function
should never be called before the secondary is initialized. I believe we can remove some of that
logic in a follow up.
  • Loading branch information
freddygv authored Nov 9, 2021
1 parent 8f16625 commit cbaa9ce
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 44 deletions.
6 changes: 6 additions & 0 deletions .changelog/11514.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 21 deletions agent/consul/leader_connect_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type caServerDelegate interface {
generateCASignRequest(csr string) *structs.CASignRequest
raftApply(t structs.MessageType, msg interface{}) (interface{}, error)

checkServersProvider
ServersSupportMultiDCConnectCA() error
}

// CAManager is a wrapper around CA operations such as updating roots, an intermediate
Expand Down Expand Up @@ -77,6 +77,17 @@ func (c *caDelegateWithState) State() *state.Store {
return c.fsm.State()
}

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 {
return &CAManager{
delegate: delegate,
Expand Down Expand Up @@ -158,7 +169,8 @@ func (c *CAManager) initializeCAConfig() (*structs.CAConfiguration, error) {
}
if config == nil {
config = c.serverConf.CAConfig
if config.ClusterID == "" {

if c.serverConf.Datacenter == c.serverConf.PrimaryDatacenter && config.ClusterID == "" {
id, err := uuid.GenerateUUID()
if err != nil {
return nil, err
Expand Down Expand Up @@ -354,19 +366,12 @@ func (c *CAManager) InitializeCA() (reterr error) {
if c.serverConf.PrimaryDatacenter == c.serverConf.Datacenter {
return c.initializeRootCA(provider, conf)
}
return c.secondaryInitialize(provider, conf)
}

// 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
versionOk, foundPrimary := ServersInDCMeetMinimumVersion(c.delegate, c.serverConf.PrimaryDatacenter, minMultiDCConnectVersion)
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
func (c *CAManager) secondaryInitialize(provider ca.Provider, conf *structs.CAConfiguration) error {
if err := c.delegate.ServersSupportMultiDCConnectCA(); err != nil {
return fmt.Errorf("initialization will be deferred: %w", err)
}

// Get the root CA to see if we need to refresh our intermediate.
Expand Down Expand Up @@ -1200,15 +1205,12 @@ func (c *CAManager) UpdateRoots(roots structs.IndexedCARoots) error {
return nil
}
if !c.configuredSecondaryCA() {
versionOk, primaryFound := ServersInDCMeetMinimumVersion(c.delegate, c.serverConf.PrimaryDatacenter, minMultiDCConnectVersion)
if !primaryFound {
return fmt.Errorf("Primary datacenter is unreachable - deferring secondary CA initialization")
if err := c.delegate.ServersSupportMultiDCConnectCA(); err != nil {
return fmt.Errorf("failed to initialize while updating primary roots: %w", err)
}

if versionOk {
if err := c.initializeSecondaryProvider(provider, roots); err != nil {
return fmt.Errorf("Failed to initialize secondary CA provider: %v", err)
}
if err := c.initializeSecondaryProvider(provider, roots); err != nil {
return fmt.Errorf("Failed to initialize secondary CA provider: %v", err)
}
}

Expand Down
14 changes: 4 additions & 10 deletions agent/consul/leader_connect_ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ import (
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/hashicorp/consul/agent/connect"
ca "github.com/hashicorp/consul/agent/connect/ca"
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/metadata"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/go-version"
"github.com/hashicorp/serf/serf"
"github.com/stretchr/testify/require"
)

// TODO(kyhavlov): replace with t.Deadline()
Expand Down Expand Up @@ -51,12 +49,8 @@ func (m *mockCAServerDelegate) IsLeader() bool {
return true
}

func (m *mockCAServerDelegate) CheckServers(datacenter string, fn func(*metadata.Server) bool) {
ver, _ := version.NewVersion("1.6.0")
fn(&metadata.Server{
Status: serf.StatusAlive,
Build: *ver,
})
func (m *mockCAServerDelegate) ServersSupportMultiDCConnectCA() error {
return nil
}

func (m *mockCAServerDelegate) ApplyCARequest(req *structs.CARequest) (interface{}, error) {
Expand Down
6 changes: 4 additions & 2 deletions agent/consul/leader_connect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,6 @@ func TestLeader_SecondaryCA_UpgradeBeforePrimary(t *testing.T) {

// Wait for the secondary transition to happen and then verify the secondary DC
// has both roots present.
secondaryProvider, _ := getCAProviderWithLock(s2)
retry.Run(t, func(r *retry.R) {
state1 := s1.fsm.State()
_, roots1, err := state1.CARoots(nil)
Expand All @@ -948,15 +947,18 @@ func TestLeader_SecondaryCA_UpgradeBeforePrimary(t *testing.T) {
require.Equal(r, roots1[0].ID, roots2[0].ID)
require.Equal(r, roots1[0].RootCert, roots2[0].RootCert)

secondaryProvider, _ := getCAProviderWithLock(s2)
inter, err := secondaryProvider.ActiveIntermediate()
require.NoError(r, err)
require.NotEmpty(r, inter, "should have valid intermediate")
})

_, caRoot := getCAProviderWithLock(s1)
secondaryProvider, _ := getCAProviderWithLock(s2)
intermediatePEM, err := secondaryProvider.ActiveIntermediate()
require.NoError(t, err)

_, caRoot := getCAProviderWithLock(s1)

// Have dc2 sign a leaf cert and make sure the chain is correct.
spiffeService := &connect.SpiffeIDService{
Host: "node1",
Expand Down
22 changes: 13 additions & 9 deletions agent/consul/server_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,23 @@ func (s *Server) getCARoots(ws memdb.WatchSet, state *state.Store) (*structs.Ind
if err != nil {
return nil, err
}
if config == nil {
return nil, fmt.Errorf("CA has not finished initializing")
}

indexedRoots := &structs.IndexedCARoots{}

if config != nil {
// Build TrustDomain based on the ClusterID stored.
signingID := connect.SpiffeIDSigningForCluster(config)
if signingID == nil {
// If CA is bootstrapped at all then this should never happen but be
// defensive.
return nil, fmt.Errorf("no cluster trust domain setup")
}
// Build TrustDomain based on the ClusterID stored.
signingID := connect.SpiffeIDSigningForCluster(config)
if signingID == nil {
// If CA is bootstrapped at all then this should never happen but be
// defensive.
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
Expand Down
2 changes: 0 additions & 2 deletions agent/consul/state/connect_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,6 @@ func (s *Store) caSetConfigTxn(idx uint64, tx WriteTxn, config *structs.CAConfig
if prev != nil {
existing := prev.(*structs.CAConfiguration)
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 == "" {
config.ClusterID = existing.ClusterID
}
Expand Down

0 comments on commit cbaa9ce

Please sign in to comment.