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

     1  package ca
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hashicorp/consul/agent/connect"
    12  	"github.com/hashicorp/consul/agent/structs"
    13  	vaultapi "github.com/hashicorp/vault/api"
    14  	"github.com/hashicorp/vault/builtin/logical/pki"
    15  	vaulthttp "github.com/hashicorp/vault/http"
    16  	"github.com/hashicorp/vault/vault"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  var vaultLock sync.Mutex
    21  
    22  func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener) {
    23  	return testVaultClusterWithConfig(t, true, nil)
    24  }
    25  
    26  func testVaultClusterWithConfig(t *testing.T, isRoot bool, rawConf map[string]interface{}) (*VaultProvider, *vault.Core, net.Listener) {
    27  	vaultLock.Lock()
    28  	defer vaultLock.Unlock()
    29  
    30  	if err := vault.AddTestLogicalBackend("pki", pki.Factory); err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	core, _, token := vault.TestCoreUnsealedRaw(t)
    34  
    35  	ln, addr := vaulthttp.TestServer(t, core)
    36  
    37  	conf := map[string]interface{}{
    38  		"Address":             addr,
    39  		"Token":               token,
    40  		"RootPKIPath":         "pki-root/",
    41  		"IntermediatePKIPath": "pki-intermediate/",
    42  		// Tests duration parsing after msgpack type mangling during raft apply.
    43  		"LeafCertTTL": []uint8("72h"),
    44  	}
    45  	for k, v := range rawConf {
    46  		conf[k] = v
    47  	}
    48  
    49  	require := require.New(t)
    50  	provider := &VaultProvider{}
    51  	require.NoError(provider.Configure("asdf", isRoot, conf))
    52  	if isRoot {
    53  		require.NoError(provider.GenerateRoot())
    54  		_, err := provider.GenerateIntermediate()
    55  		require.NoError(err)
    56  	}
    57  
    58  	return provider, core, ln
    59  }
    60  
    61  func TestVaultCAProvider_VaultTLSConfig(t *testing.T) {
    62  	config := &structs.VaultCAProviderConfig{
    63  		CAFile:        "/capath/ca.pem",
    64  		CAPath:        "/capath/",
    65  		CertFile:      "/certpath/cert.pem",
    66  		KeyFile:       "/certpath/key.pem",
    67  		TLSServerName: "server.name",
    68  		TLSSkipVerify: true,
    69  	}
    70  	tlsConfig := vaultTLSConfig(config)
    71  	require := require.New(t)
    72  	require.Equal(config.CAFile, tlsConfig.CACert)
    73  	require.Equal(config.CAPath, tlsConfig.CAPath)
    74  	require.Equal(config.CertFile, tlsConfig.ClientCert)
    75  	require.Equal(config.KeyFile, tlsConfig.ClientKey)
    76  	require.Equal(config.TLSServerName, tlsConfig.TLSServerName)
    77  	require.Equal(config.TLSSkipVerify, tlsConfig.Insecure)
    78  }
    79  
    80  func TestVaultCAProvider_Bootstrap(t *testing.T) {
    81  	t.Parallel()
    82  
    83  	require := require.New(t)
    84  	provider, core, listener := testVaultCluster(t)
    85  	defer core.Shutdown()
    86  	defer listener.Close()
    87  	client, err := vaultapi.NewClient(&vaultapi.Config{
    88  		Address: "http://" + listener.Addr().String(),
    89  	})
    90  	require.NoError(err)
    91  	client.SetToken(provider.config.Token)
    92  
    93  	cases := []struct {
    94  		certFunc    func() (string, error)
    95  		backendPath string
    96  	}{
    97  		{
    98  			certFunc:    provider.ActiveRoot,
    99  			backendPath: "pki-root/",
   100  		},
   101  		{
   102  			certFunc:    provider.ActiveIntermediate,
   103  			backendPath: "pki-intermediate/",
   104  		},
   105  	}
   106  
   107  	// Verify the root and intermediate certs match the ones in the vault backends
   108  	for _, tc := range cases {
   109  		cert, err := tc.certFunc()
   110  		require.NoError(err)
   111  		req := client.NewRequest("GET", "/v1/"+tc.backendPath+"ca/pem")
   112  		resp, err := client.RawRequest(req)
   113  		require.NoError(err)
   114  		bytes, err := ioutil.ReadAll(resp.Body)
   115  		require.NoError(err)
   116  		require.Equal(cert, string(bytes))
   117  
   118  		// Should be a valid CA cert
   119  		parsed, err := connect.ParseCert(cert)
   120  		require.NoError(err)
   121  		require.True(parsed.IsCA)
   122  		require.Len(parsed.URIs, 1)
   123  		require.Equal(parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", provider.clusterId))
   124  	}
   125  }
   126  
   127  func TestVaultCAProvider_SignLeaf(t *testing.T) {
   128  	t.Parallel()
   129  
   130  	require := require.New(t)
   131  	provider, core, listener := testVaultClusterWithConfig(t, true, map[string]interface{}{
   132  		"LeafCertTTL": "1h",
   133  	})
   134  	defer core.Shutdown()
   135  	defer listener.Close()
   136  	client, err := vaultapi.NewClient(&vaultapi.Config{
   137  		Address: "http://" + listener.Addr().String(),
   138  	})
   139  	require.NoError(err)
   140  	client.SetToken(provider.config.Token)
   141  
   142  	spiffeService := &connect.SpiffeIDService{
   143  		Host:       "node1",
   144  		Namespace:  "default",
   145  		Datacenter: "dc1",
   146  		Service:    "foo",
   147  	}
   148  
   149  	// Generate a leaf cert for the service.
   150  	var firstSerial uint64
   151  	{
   152  		raw, _ := connect.TestCSR(t, spiffeService)
   153  
   154  		csr, err := connect.ParseCSR(raw)
   155  		require.NoError(err)
   156  
   157  		cert, err := provider.Sign(csr)
   158  		require.NoError(err)
   159  
   160  		parsed, err := connect.ParseCert(cert)
   161  		require.NoError(err)
   162  		require.Equal(parsed.URIs[0], spiffeService.URI())
   163  		firstSerial = parsed.SerialNumber.Uint64()
   164  
   165  		// Ensure the cert is valid now and expires within the correct limit.
   166  		now := time.Now()
   167  		require.True(parsed.NotAfter.Sub(now) < time.Hour)
   168  		require.True(parsed.NotBefore.Before(now))
   169  	}
   170  
   171  	// Generate a new cert for another service and make sure
   172  	// the serial number is unique.
   173  	spiffeService.Service = "bar"
   174  	{
   175  		raw, _ := connect.TestCSR(t, spiffeService)
   176  
   177  		csr, err := connect.ParseCSR(raw)
   178  		require.NoError(err)
   179  
   180  		cert, err := provider.Sign(csr)
   181  		require.NoError(err)
   182  
   183  		parsed, err := connect.ParseCert(cert)
   184  		require.NoError(err)
   185  		require.Equal(parsed.URIs[0], spiffeService.URI())
   186  		require.NotEqual(firstSerial, parsed.SerialNumber.Uint64())
   187  
   188  		// Ensure the cert is valid now and expires within the correct limit.
   189  		require.True(parsed.NotAfter.Sub(time.Now()) < time.Hour)
   190  		require.True(parsed.NotBefore.Before(time.Now()))
   191  	}
   192  }
   193  
   194  func TestVaultCAProvider_CrossSignCA(t *testing.T) {
   195  	t.Parallel()
   196  
   197  	provider1, core1, listener1 := testVaultCluster(t)
   198  	defer core1.Shutdown()
   199  	defer listener1.Close()
   200  
   201  	provider2, core2, listener2 := testVaultCluster(t)
   202  	defer core2.Shutdown()
   203  	defer listener2.Close()
   204  
   205  	testCrossSignProviders(t, provider1, provider2)
   206  }
   207  
   208  func TestVaultProvider_SignIntermediate(t *testing.T) {
   209  	t.Parallel()
   210  
   211  	provider1, core1, listener1 := testVaultCluster(t)
   212  	defer core1.Shutdown()
   213  	defer listener1.Close()
   214  
   215  	provider2, core2, listener2 := testVaultClusterWithConfig(t, false, nil)
   216  	defer core2.Shutdown()
   217  	defer listener2.Close()
   218  
   219  	testSignIntermediateCrossDC(t, provider1, provider2)
   220  }
   221  
   222  func TestVaultProvider_SignIntermediateConsul(t *testing.T) {
   223  	t.Parallel()
   224  
   225  	require := require.New(t)
   226  
   227  	// primary = Vault, secondary = Consul
   228  	{
   229  		provider1, core, listener := testVaultCluster(t)
   230  		defer core.Shutdown()
   231  		defer listener.Close()
   232  
   233  		conf := testConsulCAConfig()
   234  		delegate := newMockDelegate(t, conf)
   235  		provider2 := &ConsulProvider{Delegate: delegate}
   236  		require.NoError(provider2.Configure(conf.ClusterID, false, conf.Config))
   237  
   238  		testSignIntermediateCrossDC(t, provider1, provider2)
   239  	}
   240  
   241  	// primary = Consul, secondary = Vault
   242  	{
   243  		conf := testConsulCAConfig()
   244  		delegate := newMockDelegate(t, conf)
   245  		provider1 := &ConsulProvider{Delegate: delegate}
   246  		require.NoError(provider1.Configure(conf.ClusterID, true, conf.Config))
   247  		require.NoError(provider1.GenerateRoot())
   248  
   249  		provider2, core, listener := testVaultClusterWithConfig(t, false, nil)
   250  		defer core.Shutdown()
   251  		defer listener.Close()
   252  
   253  		testSignIntermediateCrossDC(t, provider1, provider2)
   254  	}
   255  }