diff --git a/agent/connect/ca/provider_consul.go b/agent/connect/ca/provider_consul.go index 7d7244fd9c..803ce2ac18 100644 --- a/agent/connect/ca/provider_consul.go +++ b/agent/connect/ca/provider_consul.go @@ -3,6 +3,7 @@ package ca import ( "bytes" "crypto/rand" + "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -10,6 +11,7 @@ import ( "fmt" "math/big" "net/url" + "strings" "sync" "time" @@ -35,7 +37,7 @@ type ConsulProviderStateDelegate interface { } // Configure sets up the provider using the given configuration. -func (c *ConsulProvider) Configure(clusterId string, isRoot bool, rawConfig map[string]interface{}) error { +func (c *ConsulProvider) Configure(clusterID string, isRoot bool, rawConfig map[string]interface{}) error { // Parse the raw config and update our ID. config, err := ParseConsulCAConfig(rawConfig) if err != nil { @@ -43,7 +45,8 @@ func (c *ConsulProvider) Configure(clusterId string, isRoot bool, rawConfig map[ } c.config = config c.isRoot = isRoot - c.id = fmt.Sprintf("%s,%s", config.PrivateKey, config.RootCert) + hash := sha256.Sum256([]byte(fmt.Sprintf("%s,%s,%v", config.PrivateKey, config.RootCert, isRoot))) + c.id = strings.Replace(fmt.Sprintf("% x", hash), " ", ":", -1) // Exit early if the state store has an entry for this provider's config. _, providerState, err := c.Delegate.State().CAProviderState(c.id) @@ -55,6 +58,37 @@ func (c *ConsulProvider) Configure(clusterId string, isRoot bool, rawConfig map[ return nil } + // Check if there's an entry with the old ID scheme. + oldID := fmt.Sprintf("%s,%s", config.PrivateKey, config.RootCert) + _, providerState, err = c.Delegate.State().CAProviderState(oldID) + if err != nil { + return err + } + + // Found an entry with the old ID, so update it to the new ID and + // delete the old entry. + if providerState != nil { + newState := *providerState + newState.ID = c.id + createReq := &structs.CARequest{ + Op: structs.CAOpSetProviderState, + ProviderState: &newState, + } + if err := c.Delegate.ApplyCARequest(createReq); err != nil { + return err + } + + deleteReq := &structs.CARequest{ + Op: structs.CAOpDeleteProviderState, + ProviderState: providerState, + } + if err := c.Delegate.ApplyCARequest(deleteReq); err != nil { + return err + } + + return nil + } + // Write the provider state to the state store. newState := structs.CAConsulProviderState{ ID: c.id, @@ -363,9 +397,9 @@ func (c *ConsulProvider) generateCA(privateKey string, sn uint64) (string, error serialNum := &big.Int{} serialNum.SetUint64(sn) template := x509.Certificate{ - SerialNumber: serialNum, - Subject: pkix.Name{CommonName: name}, - URIs: []*url.URL{id.URI()}, + SerialNumber: serialNum, + Subject: pkix.Name{CommonName: name}, + URIs: []*url.URL{id.URI()}, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | diff --git a/agent/connect/ca/provider_consul_test.go b/agent/connect/ca/provider_consul_test.go index 3bce52a0b8..857823458b 100644 --- a/agent/connect/ca/provider_consul_test.go +++ b/agent/connect/ca/provider_consul_test.go @@ -271,3 +271,32 @@ func testCrossSignProviders(t *testing.T, provider1, provider2 Provider) { require.NoError(err) } } + +func TestConsulCAProvider_MigrateOldID(t *testing.T) { + t.Parallel() + + require := require.New(t) + conf := testConsulCAConfig() + delegate := newMockDelegate(t, conf) + + // Create an entry with an old-style ID. + err := delegate.ApplyCARequest(&structs.CARequest{ + Op: structs.CAOpSetProviderState, + ProviderState: &structs.CAConsulProviderState{ + ID: ",", + }, + }) + require.NoError(err) + _, providerState, err := delegate.state.CAProviderState(",") + require.NoError(err) + require.NotNil(providerState) + + provider := &ConsulProvider{Delegate: delegate} + require.NoError(provider.Configure(conf.ClusterID, true, conf.Config)) + require.NoError(provider.GenerateRoot()) + + // After running Configure, the old ID entry should be gone. + _, providerState, err = delegate.state.CAProviderState(",") + require.NoError(err) + require.Nil(providerState) +}