github.com/outbrain/consul@v1.4.5/agent/connect/ca/provider_consul_test.go (about)

     1  package ca
     2  
     3  import (
     4  	"crypto/x509"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/consul/agent/connect"
    10  	"github.com/hashicorp/consul/agent/consul/state"
    11  	"github.com/hashicorp/consul/agent/structs"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  type consulCAMockDelegate struct {
    16  	state *state.Store
    17  }
    18  
    19  func (c *consulCAMockDelegate) State() *state.Store {
    20  	return c.state
    21  }
    22  
    23  func (c *consulCAMockDelegate) ApplyCARequest(req *structs.CARequest) error {
    24  	idx, _, err := c.state.CAConfig()
    25  	if err != nil {
    26  		return err
    27  	}
    28  
    29  	switch req.Op {
    30  	case structs.CAOpSetProviderState:
    31  		_, err := c.state.CASetProviderState(idx+1, req.ProviderState)
    32  		if err != nil {
    33  			return err
    34  		}
    35  
    36  		return nil
    37  	case structs.CAOpDeleteProviderState:
    38  		if err := c.state.CADeleteProviderState(req.ProviderState.ID); err != nil {
    39  			return err
    40  		}
    41  
    42  		return nil
    43  	default:
    44  		return fmt.Errorf("Invalid CA operation '%s'", req.Op)
    45  	}
    46  }
    47  
    48  func newMockDelegate(t *testing.T, conf *structs.CAConfiguration) *consulCAMockDelegate {
    49  	s, err := state.NewStateStore(nil)
    50  	if err != nil {
    51  		t.Fatalf("err: %s", err)
    52  	}
    53  	if s == nil {
    54  		t.Fatalf("missing state store")
    55  	}
    56  	if err := s.CASetConfig(conf.RaftIndex.CreateIndex, conf); err != nil {
    57  		t.Fatalf("err: %s", err)
    58  	}
    59  
    60  	return &consulCAMockDelegate{s}
    61  }
    62  
    63  func testConsulCAConfig() *structs.CAConfiguration {
    64  	return &structs.CAConfiguration{
    65  		ClusterID: "asdf",
    66  		Provider:  "consul",
    67  		Config: map[string]interface{}{
    68  			// Tests duration parsing after msgpack type mangling during raft apply.
    69  			"LeafCertTTL": []uint8("72h"),
    70  		},
    71  	}
    72  }
    73  
    74  func TestConsulCAProvider_Bootstrap(t *testing.T) {
    75  	t.Parallel()
    76  
    77  	require := require.New(t)
    78  	conf := testConsulCAConfig()
    79  	delegate := newMockDelegate(t, conf)
    80  
    81  	provider := &ConsulProvider{Delegate: delegate}
    82  	require.NoError(provider.Configure(conf.ClusterID, true, conf.Config))
    83  	require.NoError(provider.GenerateRoot())
    84  
    85  	root, err := provider.ActiveRoot()
    86  	require.NoError(err)
    87  
    88  	// Intermediate should be the same cert.
    89  	inter, err := provider.ActiveIntermediate()
    90  	require.NoError(err)
    91  	require.Equal(root, inter)
    92  
    93  	// Should be a valid cert
    94  	parsed, err := connect.ParseCert(root)
    95  	require.NoError(err)
    96  	require.Equal(parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", conf.ClusterID))
    97  }
    98  
    99  func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
   100  	t.Parallel()
   101  
   102  	// Make sure setting a custom private key/root cert works.
   103  	require := require.New(t)
   104  	rootCA := connect.TestCA(t, nil)
   105  	conf := testConsulCAConfig()
   106  	conf.Config = map[string]interface{}{
   107  		"PrivateKey": rootCA.SigningKey,
   108  		"RootCert":   rootCA.RootCert,
   109  	}
   110  	delegate := newMockDelegate(t, conf)
   111  
   112  	provider := &ConsulProvider{Delegate: delegate}
   113  	require.NoError(provider.Configure(conf.ClusterID, true, conf.Config))
   114  	require.NoError(provider.GenerateRoot())
   115  
   116  	root, err := provider.ActiveRoot()
   117  	require.NoError(err)
   118  	require.Equal(root, rootCA.RootCert)
   119  }
   120  
   121  func TestConsulCAProvider_SignLeaf(t *testing.T) {
   122  	t.Parallel()
   123  
   124  	require := require.New(t)
   125  	conf := testConsulCAConfig()
   126  	conf.Config["LeafCertTTL"] = "1h"
   127  	delegate := newMockDelegate(t, conf)
   128  
   129  	provider := &ConsulProvider{Delegate: delegate}
   130  	require.NoError(provider.Configure(conf.ClusterID, true, conf.Config))
   131  	require.NoError(provider.GenerateRoot())
   132  
   133  	spiffeService := &connect.SpiffeIDService{
   134  		Host:       "node1",
   135  		Namespace:  "default",
   136  		Datacenter: "dc1",
   137  		Service:    "foo",
   138  	}
   139  
   140  	// Generate a leaf cert for the service.
   141  	{
   142  		raw, _ := connect.TestCSR(t, spiffeService)
   143  
   144  		csr, err := connect.ParseCSR(raw)
   145  		require.NoError(err)
   146  
   147  		cert, err := provider.Sign(csr)
   148  		require.NoError(err)
   149  
   150  		parsed, err := connect.ParseCert(cert)
   151  		require.NoError(err)
   152  		require.Equal(parsed.URIs[0], spiffeService.URI())
   153  		require.Equal(parsed.Subject.CommonName, "foo")
   154  		require.Equal(uint64(2), parsed.SerialNumber.Uint64())
   155  
   156  		// Ensure the cert is valid now and expires within the correct limit.
   157  		now := time.Now()
   158  		require.True(parsed.NotAfter.Sub(now) < time.Hour)
   159  		require.True(parsed.NotBefore.Before(now))
   160  	}
   161  
   162  	// Generate a new cert for another service and make sure
   163  	// the serial number is incremented.
   164  	spiffeService.Service = "bar"
   165  	{
   166  		raw, _ := connect.TestCSR(t, spiffeService)
   167  
   168  		csr, err := connect.ParseCSR(raw)
   169  		require.NoError(err)
   170  
   171  		cert, err := provider.Sign(csr)
   172  		require.NoError(err)
   173  
   174  		parsed, err := connect.ParseCert(cert)
   175  		require.NoError(err)
   176  		require.Equal(parsed.URIs[0], spiffeService.URI())
   177  		require.Equal(parsed.Subject.CommonName, "bar")
   178  		require.Equal(parsed.SerialNumber.Uint64(), uint64(2))
   179  
   180  		// Ensure the cert is valid now and expires within the correct limit.
   181  		require.True(parsed.NotAfter.Sub(time.Now()) < 3*24*time.Hour)
   182  		require.True(parsed.NotBefore.Before(time.Now()))
   183  	}
   184  }
   185  
   186  func TestConsulCAProvider_CrossSignCA(t *testing.T) {
   187  	t.Parallel()
   188  	require := require.New(t)
   189  
   190  	conf1 := testConsulCAConfig()
   191  	delegate1 := newMockDelegate(t, conf1)
   192  	provider1 := &ConsulProvider{Delegate: delegate1}
   193  	require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config))
   194  	require.NoError(provider1.GenerateRoot())
   195  
   196  	conf2 := testConsulCAConfig()
   197  	conf2.CreateIndex = 10
   198  	delegate2 := newMockDelegate(t, conf2)
   199  	provider2 := &ConsulProvider{Delegate: delegate2}
   200  	require.NoError(provider2.Configure(conf2.ClusterID, true, conf2.Config))
   201  	require.NoError(provider2.GenerateRoot())
   202  
   203  	testCrossSignProviders(t, provider1, provider2)
   204  }
   205  
   206  func testCrossSignProviders(t *testing.T, provider1, provider2 Provider) {
   207  	require := require.New(t)
   208  
   209  	// Get the root from the new provider to be cross-signed.
   210  	newRootPEM, err := provider2.ActiveRoot()
   211  	require.NoError(err)
   212  	newRoot, err := connect.ParseCert(newRootPEM)
   213  	require.NoError(err)
   214  	oldSubject := newRoot.Subject.CommonName
   215  
   216  	newInterPEM, err := provider2.ActiveIntermediate()
   217  	require.NoError(err)
   218  	newIntermediate, err := connect.ParseCert(newInterPEM)
   219  	require.NoError(err)
   220  
   221  	// Have provider1 cross sign our new root cert.
   222  	xcPEM, err := provider1.CrossSignCA(newRoot)
   223  	require.NoError(err)
   224  	xc, err := connect.ParseCert(xcPEM)
   225  	require.NoError(err)
   226  
   227  	oldRootPEM, err := provider1.ActiveRoot()
   228  	require.NoError(err)
   229  	oldRoot, err := connect.ParseCert(oldRootPEM)
   230  	require.NoError(err)
   231  
   232  	// AuthorityKeyID should now be the signing root's, SubjectKeyId should be kept.
   233  	require.Equal(oldRoot.AuthorityKeyId, xc.AuthorityKeyId)
   234  	require.Equal(newRoot.SubjectKeyId, xc.SubjectKeyId)
   235  
   236  	// Subject name should not have changed.
   237  	require.Equal(oldSubject, xc.Subject.CommonName)
   238  
   239  	// Issuer should be the signing root.
   240  	require.Equal(oldRoot.Issuer.CommonName, xc.Issuer.CommonName)
   241  
   242  	// Get a leaf cert so we can verify against the cross-signed cert.
   243  	spiffeService := &connect.SpiffeIDService{
   244  		Host:       "node1",
   245  		Namespace:  "default",
   246  		Datacenter: "dc1",
   247  		Service:    "foo",
   248  	}
   249  	raw, _ := connect.TestCSR(t, spiffeService)
   250  
   251  	leafCsr, err := connect.ParseCSR(raw)
   252  	require.NoError(err)
   253  
   254  	leafPEM, err := provider2.Sign(leafCsr)
   255  	require.NoError(err)
   256  
   257  	cert, err := connect.ParseCert(leafPEM)
   258  	require.NoError(err)
   259  
   260  	// Check that the leaf signed by the new cert can be verified by either root
   261  	// certificate by using the new intermediate + cross-signed cert.
   262  	intermediatePool := x509.NewCertPool()
   263  	intermediatePool.AddCert(newIntermediate)
   264  	intermediatePool.AddCert(xc)
   265  
   266  	for _, root := range []*x509.Certificate{oldRoot, newRoot} {
   267  		rootPool := x509.NewCertPool()
   268  		rootPool.AddCert(root)
   269  
   270  		_, err = cert.Verify(x509.VerifyOptions{
   271  			Intermediates: intermediatePool,
   272  			Roots:         rootPool,
   273  		})
   274  		require.NoError(err)
   275  	}
   276  }
   277  
   278  func TestConsulProvider_SignIntermediate(t *testing.T) {
   279  	t.Parallel()
   280  	require := require.New(t)
   281  
   282  	conf1 := testConsulCAConfig()
   283  	delegate1 := newMockDelegate(t, conf1)
   284  	provider1 := &ConsulProvider{Delegate: delegate1}
   285  	require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config))
   286  	require.NoError(provider1.GenerateRoot())
   287  
   288  	conf2 := testConsulCAConfig()
   289  	conf2.CreateIndex = 10
   290  	delegate2 := newMockDelegate(t, conf2)
   291  	provider2 := &ConsulProvider{Delegate: delegate2}
   292  	require.NoError(provider2.Configure(conf2.ClusterID, false, conf2.Config))
   293  
   294  	testSignIntermediateCrossDC(t, provider1, provider2)
   295  }
   296  
   297  func testSignIntermediateCrossDC(t *testing.T, provider1, provider2 Provider) {
   298  	require := require.New(t)
   299  
   300  	// Get the intermediate CSR from provider2.
   301  	csrPEM, err := provider2.GenerateIntermediateCSR()
   302  	require.NoError(err)
   303  	csr, err := connect.ParseCSR(csrPEM)
   304  	require.NoError(err)
   305  
   306  	// Sign the CSR with provider1.
   307  	intermediatePEM, err := provider1.SignIntermediate(csr)
   308  	require.NoError(err)
   309  	rootPEM, err := provider1.ActiveRoot()
   310  	require.NoError(err)
   311  
   312  	// Give the new intermediate to provider2 to use.
   313  	require.NoError(provider2.SetIntermediate(intermediatePEM, rootPEM))
   314  
   315  	// Have provider2 sign a leaf cert and make sure the chain is correct.
   316  	spiffeService := &connect.SpiffeIDService{
   317  		Host:       "node1",
   318  		Namespace:  "default",
   319  		Datacenter: "dc1",
   320  		Service:    "foo",
   321  	}
   322  	raw, _ := connect.TestCSR(t, spiffeService)
   323  
   324  	leafCsr, err := connect.ParseCSR(raw)
   325  	require.NoError(err)
   326  
   327  	leafPEM, err := provider2.Sign(leafCsr)
   328  	require.NoError(err)
   329  
   330  	cert, err := connect.ParseCert(leafPEM)
   331  	require.NoError(err)
   332  
   333  	// Check that the leaf signed by the new cert can be verified using the
   334  	// returned cert chain (signed intermediate + remote root).
   335  	intermediatePool := x509.NewCertPool()
   336  	intermediatePool.AppendCertsFromPEM([]byte(intermediatePEM))
   337  	rootPool := x509.NewCertPool()
   338  	rootPool.AppendCertsFromPEM([]byte(rootPEM))
   339  
   340  	_, err = cert.Verify(x509.VerifyOptions{
   341  		Intermediates: intermediatePool,
   342  		Roots:         rootPool,
   343  	})
   344  	require.NoError(err)
   345  }
   346  
   347  func TestConsulCAProvider_MigrateOldID(t *testing.T) {
   348  	t.Parallel()
   349  
   350  	require := require.New(t)
   351  	conf := testConsulCAConfig()
   352  	delegate := newMockDelegate(t, conf)
   353  
   354  	// Create an entry with an old-style ID.
   355  	err := delegate.ApplyCARequest(&structs.CARequest{
   356  		Op: structs.CAOpSetProviderState,
   357  		ProviderState: &structs.CAConsulProviderState{
   358  			ID: ",",
   359  		},
   360  	})
   361  	require.NoError(err)
   362  	_, providerState, err := delegate.state.CAProviderState(",")
   363  	require.NoError(err)
   364  	require.NotNil(providerState)
   365  
   366  	provider := &ConsulProvider{Delegate: delegate}
   367  	require.NoError(provider.Configure(conf.ClusterID, true, conf.Config))
   368  	require.NoError(provider.GenerateRoot())
   369  
   370  	// After running Configure, the old ID entry should be gone.
   371  	_, providerState, err = delegate.state.CAProviderState(",")
   372  	require.NoError(err)
   373  	require.Nil(providerState)
   374  }