mirror of https://github.com/status-im/consul.git
connect: derive connect certificate serial numbers from a memdb index instead of the provider table max index (#7011)
This commit is contained in:
parent
50c879923c
commit
10f04a8c4a
|
@ -45,7 +45,7 @@ type ConsulProvider struct {
|
||||||
|
|
||||||
type ConsulProviderStateDelegate interface {
|
type ConsulProviderStateDelegate interface {
|
||||||
State() *state.Store
|
State() *state.Store
|
||||||
ApplyCARequest(*structs.CARequest) error
|
ApplyCARequest(*structs.CARequest) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure sets up the provider using the given configuration.
|
// Configure sets up the provider using the given configuration.
|
||||||
|
@ -91,7 +91,7 @@ func (c *ConsulProvider) Configure(cfg ProviderConfig) error {
|
||||||
Op: structs.CAOpSetProviderState,
|
Op: structs.CAOpSetProviderState,
|
||||||
ProviderState: &newState,
|
ProviderState: &newState,
|
||||||
}
|
}
|
||||||
if err := c.Delegate.ApplyCARequest(createReq); err != nil {
|
if _, err := c.Delegate.ApplyCARequest(createReq); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ func (c *ConsulProvider) Configure(cfg ProviderConfig) error {
|
||||||
Op: structs.CAOpDeleteProviderState,
|
Op: structs.CAOpDeleteProviderState,
|
||||||
ProviderState: providerState,
|
ProviderState: providerState,
|
||||||
}
|
}
|
||||||
if err := c.Delegate.ApplyCARequest(deleteReq); err != nil {
|
if _, err := c.Delegate.ApplyCARequest(deleteReq); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ func (c *ConsulProvider) Configure(cfg ProviderConfig) error {
|
||||||
Op: structs.CAOpSetProviderState,
|
Op: structs.CAOpSetProviderState,
|
||||||
ProviderState: &newState,
|
ProviderState: &newState,
|
||||||
}
|
}
|
||||||
if err := c.Delegate.ApplyCARequest(args); err != nil {
|
if _, err := c.Delegate.ApplyCARequest(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ func (c *ConsulProvider) ActiveRoot() (string, error) {
|
||||||
// GenerateRoot initializes a new root certificate and private key
|
// GenerateRoot initializes a new root certificate and private key
|
||||||
// if needed.
|
// if needed.
|
||||||
func (c *ConsulProvider) GenerateRoot() error {
|
func (c *ConsulProvider) GenerateRoot() error {
|
||||||
idx, providerState, err := c.getState()
|
_, providerState, err := c.getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,12 @@ func (c *ConsulProvider) GenerateRoot() error {
|
||||||
|
|
||||||
// Generate the root CA if necessary
|
// Generate the root CA if necessary
|
||||||
if c.config.RootCert == "" {
|
if c.config.RootCert == "" {
|
||||||
ca, err := c.generateCA(newState.PrivateKey, idx+1)
|
nextSerial, err := c.incrementAndGetNextSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error computing next serial number: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, err := c.generateCA(newState.PrivateKey, nextSerial)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating CA: %v", err)
|
return fmt.Errorf("error generating CA: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +192,7 @@ func (c *ConsulProvider) GenerateRoot() error {
|
||||||
Op: structs.CAOpSetProviderState,
|
Op: structs.CAOpSetProviderState,
|
||||||
ProviderState: &newState,
|
ProviderState: &newState,
|
||||||
}
|
}
|
||||||
if err := c.Delegate.ApplyCARequest(args); err != nil {
|
if _, err := c.Delegate.ApplyCARequest(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +236,7 @@ func (c *ConsulProvider) GenerateIntermediateCSR() (string, error) {
|
||||||
Op: structs.CAOpSetProviderState,
|
Op: structs.CAOpSetProviderState,
|
||||||
ProviderState: &newState,
|
ProviderState: &newState,
|
||||||
}
|
}
|
||||||
if err := c.Delegate.ApplyCARequest(args); err != nil {
|
if _, err := c.Delegate.ApplyCARequest(args); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +272,7 @@ func (c *ConsulProvider) SetIntermediate(intermediatePEM, rootPEM string) error
|
||||||
Op: structs.CAOpSetProviderState,
|
Op: structs.CAOpSetProviderState,
|
||||||
ProviderState: &newState,
|
ProviderState: &newState,
|
||||||
}
|
}
|
||||||
if err := c.Delegate.ApplyCARequest(args); err != nil {
|
if _, err := c.Delegate.ApplyCARequest(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,7 +306,7 @@ func (c *ConsulProvider) Cleanup() error {
|
||||||
Op: structs.CAOpDeleteProviderState,
|
Op: structs.CAOpDeleteProviderState,
|
||||||
ProviderState: &structs.CAConsulProviderState{ID: c.id},
|
ProviderState: &structs.CAConsulProviderState{ID: c.id},
|
||||||
}
|
}
|
||||||
if err := c.Delegate.ApplyCARequest(args); err != nil {
|
if _, err := c.Delegate.ApplyCARequest(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +322,7 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
|
||||||
// Get the provider state
|
// Get the provider state
|
||||||
idx, providerState, err := c.getState()
|
_, providerState, err := c.getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -362,9 +367,14 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
||||||
return "", fmt.Errorf("error parsing CA cert: %s", err)
|
return "", fmt.Errorf("error parsing CA cert: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextSerial, err := c.incrementAndGetNextSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error computing next serial number: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Cert template for generation
|
// Cert template for generation
|
||||||
sn := &big.Int{}
|
sn := &big.Int{}
|
||||||
sn.SetUint64(idx + 1)
|
sn.SetUint64(nextSerial)
|
||||||
// Sign the certificate valid from 1 minute in the past, this helps it be
|
// Sign the certificate valid from 1 minute in the past, this helps it be
|
||||||
// accepted right away even when nodes are not in close time sync across the
|
// accepted right away even when nodes are not in close time sync across the
|
||||||
// cluster. A minute is more than enough for typical DC clock drift.
|
// cluster. A minute is more than enough for typical DC clock drift.
|
||||||
|
@ -407,11 +417,6 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
||||||
return "", fmt.Errorf("error encoding certificate: %s", err)
|
return "", fmt.Errorf("error encoding certificate: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.incrementProviderIndex(providerState)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the response
|
// Set the response
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
@ -421,7 +426,7 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
||||||
// are met. It should return a signed CA certificate with a path length constraint
|
// are met. It should return a signed CA certificate with a path length constraint
|
||||||
// of 0 to ensure that the certificate cannot be used to generate further CA certs.
|
// of 0 to ensure that the certificate cannot be used to generate further CA certs.
|
||||||
func (c *ConsulProvider) SignIntermediate(csr *x509.CertificateRequest) (string, error) {
|
func (c *ConsulProvider) SignIntermediate(csr *x509.CertificateRequest) (string, error) {
|
||||||
idx, providerState, err := c.getState()
|
_, providerState, err := c.getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -447,9 +452,14 @@ func (c *ConsulProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
|
||||||
return "", fmt.Errorf("error parsing CA cert: %s", err)
|
return "", fmt.Errorf("error parsing CA cert: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextSerial, err := c.incrementAndGetNextSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error computing next serial number: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Cert template for generation
|
// Cert template for generation
|
||||||
sn := &big.Int{}
|
sn := &big.Int{}
|
||||||
sn.SetUint64(idx + 1)
|
sn.SetUint64(nextSerial)
|
||||||
// Sign the certificate valid from 1 minute in the past, this helps it be
|
// Sign the certificate valid from 1 minute in the past, this helps it be
|
||||||
// accepted right away even when nodes are not in close time sync across the
|
// accepted right away even when nodes are not in close time sync across the
|
||||||
// cluster. A minute is more than enough for typical DC clock drift.
|
// cluster. A minute is more than enough for typical DC clock drift.
|
||||||
|
@ -485,11 +495,6 @@ func (c *ConsulProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
|
||||||
return "", fmt.Errorf("error encoding certificate: %s", err)
|
return "", fmt.Errorf("error encoding certificate: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.incrementProviderIndex(providerState)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the response
|
// Set the response
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
@ -504,7 +509,7 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the provider state
|
// Get the provider state
|
||||||
idx, providerState, err := c.getState()
|
_, providerState, err := c.getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -524,9 +529,14 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextSerial, err := c.incrementAndGetNextSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error computing next serial number: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create the cross-signing template from the existing root CA
|
// Create the cross-signing template from the existing root CA
|
||||||
serialNum := &big.Int{}
|
serialNum := &big.Int{}
|
||||||
serialNum.SetUint64(idx + 1)
|
serialNum.SetUint64(nextSerial)
|
||||||
template := *cert
|
template := *cert
|
||||||
template.SerialNumber = serialNum
|
template.SerialNumber = serialNum
|
||||||
template.SignatureAlgorithm = rootCA.SignatureAlgorithm
|
template.SignatureAlgorithm = rootCA.SignatureAlgorithm
|
||||||
|
@ -555,11 +565,6 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||||
return "", fmt.Errorf("error encoding private key: %s", err)
|
return "", fmt.Errorf("error encoding private key: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.incrementProviderIndex(providerState)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,19 +589,17 @@ func (c *ConsulProvider) getState() (uint64, *structs.CAConsulProviderState, err
|
||||||
return idx, providerState, nil
|
return idx, providerState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// incrementProviderIndex does a write to increment the provider state store table index
|
func (c *ConsulProvider) incrementAndGetNextSerialNumber() (uint64, error) {
|
||||||
// used for serial numbers when generating certificates.
|
|
||||||
func (c *ConsulProvider) incrementProviderIndex(providerState *structs.CAConsulProviderState) error {
|
|
||||||
newState := *providerState
|
|
||||||
args := &structs.CARequest{
|
args := &structs.CARequest{
|
||||||
Op: structs.CAOpSetProviderState,
|
Op: structs.CAOpIncrementProviderSerialNumber,
|
||||||
ProviderState: &newState,
|
|
||||||
}
|
|
||||||
if err := c.Delegate.ApplyCARequest(args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
raw, err := c.Delegate.ApplyCARequest(args)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.(uint64), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateCA makes a new root CA using the current private key
|
// generateCA makes a new root CA using the current private key
|
||||||
|
|
|
@ -20,28 +20,30 @@ func (c *consulCAMockDelegate) State() *state.Store {
|
||||||
return c.state
|
return c.state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *consulCAMockDelegate) ApplyCARequest(req *structs.CARequest) error {
|
func (c *consulCAMockDelegate) ApplyCARequest(req *structs.CARequest) (interface{}, error) {
|
||||||
idx, _, err := c.state.CAConfig(nil)
|
idx, _, err := c.state.CAConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch req.Op {
|
switch req.Op {
|
||||||
case structs.CAOpSetProviderState:
|
case structs.CAOpSetProviderState:
|
||||||
_, err := c.state.CASetProviderState(idx+1, req.ProviderState)
|
_, err := c.state.CASetProviderState(idx+1, req.ProviderState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return true, nil
|
||||||
case structs.CAOpDeleteProviderState:
|
case structs.CAOpDeleteProviderState:
|
||||||
if err := c.state.CADeleteProviderState(req.ProviderState.ID); err != nil {
|
if err := c.state.CADeleteProviderState(req.ProviderState.ID); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return true, nil
|
||||||
|
case structs.CAOpIncrementProviderSerialNumber:
|
||||||
|
return uint64(2), nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Invalid CA operation '%s'", req.Op)
|
return nil, fmt.Errorf("Invalid CA operation '%s'", req.Op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +454,7 @@ func TestConsulCAProvider_MigrateOldID(t *testing.T) {
|
||||||
delegate := newMockDelegate(t, conf)
|
delegate := newMockDelegate(t, conf)
|
||||||
|
|
||||||
// Create an entry with an old-style ID.
|
// Create an entry with an old-style ID.
|
||||||
err := delegate.ApplyCARequest(&structs.CARequest{
|
_, err := delegate.ApplyCARequest(&structs.CARequest{
|
||||||
Op: structs.CAOpSetProviderState,
|
Op: structs.CAOpSetProviderState,
|
||||||
ProviderState: &structs.CAConsulProviderState{
|
ProviderState: &structs.CAConsulProviderState{
|
||||||
ID: ",",
|
ID: ",",
|
||||||
|
|
|
@ -15,14 +15,14 @@ func (c *consulCADelegate) State() *state.Store {
|
||||||
return c.srv.fsm.State()
|
return c.srv.fsm.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *consulCADelegate) ApplyCARequest(req *structs.CARequest) error {
|
func (c *consulCADelegate) ApplyCARequest(req *structs.CARequest) (interface{}, error) {
|
||||||
resp, err := c.srv.raftApply(structs.ConnectCARequestType, req)
|
resp, err := c.srv.raftApply(structs.ConnectCARequestType, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if respErr, ok := resp.(error); ok {
|
if respErr, ok := resp.(error); ok {
|
||||||
return respErr
|
return nil, respErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,6 +357,13 @@ func (c *FSM) applyConnectCAOperation(buf []byte, index uint64) interface{} {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return act
|
return act
|
||||||
|
case structs.CAOpIncrementProviderSerialNumber:
|
||||||
|
sn, err := c.state.CAIncrementProviderSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sn
|
||||||
default:
|
default:
|
||||||
c.logger.Printf("[WARN] consul.fsm: Invalid CA operation '%s'", req.Op)
|
c.logger.Printf("[WARN] consul.fsm: Invalid CA operation '%s'", req.Op)
|
||||||
return fmt.Errorf("Invalid CA operation '%s'", req.Op)
|
return fmt.Errorf("Invalid CA operation '%s'", req.Op)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
caBuiltinProviderTableName = "connect-ca-builtin"
|
caBuiltinProviderTableName = "connect-ca-builtin"
|
||||||
|
caBuiltinProviderSerialNumber = "connect-ca-builtin-serial"
|
||||||
caConfigTableName = "connect-ca-config"
|
caConfigTableName = "connect-ca-config"
|
||||||
caRootTableName = "connect-ca-roots"
|
caRootTableName = "connect-ca-roots"
|
||||||
caLeafIndexName = "connect-ca-leaf-certs"
|
caLeafIndexName = "connect-ca-leaf-certs"
|
||||||
|
@ -482,3 +483,31 @@ func (s *Store) CARootsAndConfig(ws memdb.WatchSet) (uint64, structs.CARoots, *s
|
||||||
|
|
||||||
return idx, roots, config, nil
|
return idx, roots, config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) CAIncrementProviderSerialNumber() (uint64, error) {
|
||||||
|
tx := s.db.Txn(true)
|
||||||
|
defer tx.Abort()
|
||||||
|
|
||||||
|
existing, err := tx.First("index", "id", caBuiltinProviderSerialNumber)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed built-in CA serial number lookup: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var last uint64
|
||||||
|
if existing != nil {
|
||||||
|
last = existing.(*IndexEntry).Value
|
||||||
|
} else {
|
||||||
|
// Serials used to be based on the raft indexes in the provider table,
|
||||||
|
// so bootstrap off of that.
|
||||||
|
last = maxIndexTxn(tx, caBuiltinProviderTableName)
|
||||||
|
}
|
||||||
|
next := last + 1
|
||||||
|
|
||||||
|
if err := tx.Insert("index", &IndexEntry{caBuiltinProviderSerialNumber, next}); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed updating index: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
|
|
@ -424,6 +424,23 @@ func TestStore_CABuiltinProvider(t *testing.T) {
|
||||||
assert.Equal(idx, uint64(1))
|
assert.Equal(idx, uint64(1))
|
||||||
assert.Equal(expected, state)
|
assert.Equal(expected, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Since we've already written to the builtin provider table the serial
|
||||||
|
// numbers will initialize from the max index of the provider table.
|
||||||
|
// That's why this first serial is 2 and not 1.
|
||||||
|
sn, err := s.CAIncrementProviderSerialNumber()
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(uint64(2), sn)
|
||||||
|
|
||||||
|
sn, err = s.CAIncrementProviderSerialNumber()
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(uint64(3), sn)
|
||||||
|
|
||||||
|
sn, err = s.CAIncrementProviderSerialNumber()
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(uint64(4), sn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStore_CABuiltinProvider_Snapshot_Restore(t *testing.T) {
|
func TestStore_CABuiltinProvider_Snapshot_Restore(t *testing.T) {
|
||||||
|
|
|
@ -177,6 +177,7 @@ const (
|
||||||
CAOpSetProviderState CAOp = "set-provider-state"
|
CAOpSetProviderState CAOp = "set-provider-state"
|
||||||
CAOpDeleteProviderState CAOp = "delete-provider-state"
|
CAOpDeleteProviderState CAOp = "delete-provider-state"
|
||||||
CAOpSetRootsAndConfig CAOp = "set-roots-config"
|
CAOpSetRootsAndConfig CAOp = "set-roots-config"
|
||||||
|
CAOpIncrementProviderSerialNumber CAOp = "increment-provider-serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CARequest is used to modify connect CA data. This is used by the
|
// CARequest is used to modify connect CA data. This is used by the
|
||||||
|
|
Loading…
Reference in New Issue