consul/agent/connect/ca/provider_vault_test.go

256 lines
6.8 KiB
Go

package ca
import (
"fmt"
"io/ioutil"
"net"
"sync"
"testing"
"time"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
vaultapi "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/logical/pki"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/vault"
"github.com/stretchr/testify/require"
)
var vaultLock sync.Mutex
func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener) {
return testVaultClusterWithConfig(t, true, nil)
}
func testVaultClusterWithConfig(t *testing.T, isRoot bool, rawConf map[string]interface{}) (*VaultProvider, *vault.Core, net.Listener) {
vaultLock.Lock()
defer vaultLock.Unlock()
if err := vault.AddTestLogicalBackend("pki", pki.Factory); err != nil {
t.Fatal(err)
}
core, _, token := vault.TestCoreUnsealedRaw(t)
ln, addr := vaulthttp.TestServer(t, core)
conf := map[string]interface{}{
"Address": addr,
"Token": token,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
// Tests duration parsing after msgpack type mangling during raft apply.
"LeafCertTTL": []uint8("72h"),
}
for k, v := range rawConf {
conf[k] = v
}
require := require.New(t)
provider := &VaultProvider{}
require.NoError(provider.Configure("asdf", isRoot, conf))
if isRoot {
require.NoError(provider.GenerateRoot())
_, err := provider.GenerateIntermediate()
require.NoError(err)
}
return provider, core, ln
}
func TestVaultCAProvider_VaultTLSConfig(t *testing.T) {
config := &structs.VaultCAProviderConfig{
CAFile: "/capath/ca.pem",
CAPath: "/capath/",
CertFile: "/certpath/cert.pem",
KeyFile: "/certpath/key.pem",
TLSServerName: "server.name",
TLSSkipVerify: true,
}
tlsConfig := vaultTLSConfig(config)
require := require.New(t)
require.Equal(config.CAFile, tlsConfig.CACert)
require.Equal(config.CAPath, tlsConfig.CAPath)
require.Equal(config.CertFile, tlsConfig.ClientCert)
require.Equal(config.KeyFile, tlsConfig.ClientKey)
require.Equal(config.TLSServerName, tlsConfig.TLSServerName)
require.Equal(config.TLSSkipVerify, tlsConfig.Insecure)
}
func TestVaultCAProvider_Bootstrap(t *testing.T) {
t.Parallel()
require := require.New(t)
provider, core, listener := testVaultCluster(t)
defer core.Shutdown()
defer listener.Close()
client, err := vaultapi.NewClient(&vaultapi.Config{
Address: "http://" + listener.Addr().String(),
})
require.NoError(err)
client.SetToken(provider.config.Token)
cases := []struct {
certFunc func() (string, error)
backendPath string
}{
{
certFunc: provider.ActiveRoot,
backendPath: "pki-root/",
},
{
certFunc: provider.ActiveIntermediate,
backendPath: "pki-intermediate/",
},
}
// Verify the root and intermediate certs match the ones in the vault backends
for _, tc := range cases {
cert, err := tc.certFunc()
require.NoError(err)
req := client.NewRequest("GET", "/v1/"+tc.backendPath+"ca/pem")
resp, err := client.RawRequest(req)
require.NoError(err)
bytes, err := ioutil.ReadAll(resp.Body)
require.NoError(err)
require.Equal(cert, string(bytes))
// Should be a valid CA cert
parsed, err := connect.ParseCert(cert)
require.NoError(err)
require.True(parsed.IsCA)
require.Len(parsed.URIs, 1)
require.Equal(parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", provider.clusterId))
}
}
func TestVaultCAProvider_SignLeaf(t *testing.T) {
t.Parallel()
require := require.New(t)
provider, core, listener := testVaultClusterWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
})
defer core.Shutdown()
defer listener.Close()
client, err := vaultapi.NewClient(&vaultapi.Config{
Address: "http://" + listener.Addr().String(),
})
require.NoError(err)
client.SetToken(provider.config.Token)
spiffeService := &connect.SpiffeIDService{
Host: "node1",
Namespace: "default",
Datacenter: "dc1",
Service: "foo",
}
// Generate a leaf cert for the service.
var firstSerial uint64
{
raw, _ := connect.TestCSR(t, spiffeService)
csr, err := connect.ParseCSR(raw)
require.NoError(err)
cert, err := provider.Sign(csr)
require.NoError(err)
parsed, err := connect.ParseCert(cert)
require.NoError(err)
require.Equal(parsed.URIs[0], spiffeService.URI())
firstSerial = parsed.SerialNumber.Uint64()
// Ensure the cert is valid now and expires within the correct limit.
now := time.Now()
require.True(parsed.NotAfter.Sub(now) < time.Hour)
require.True(parsed.NotBefore.Before(now))
}
// Generate a new cert for another service and make sure
// the serial number is unique.
spiffeService.Service = "bar"
{
raw, _ := connect.TestCSR(t, spiffeService)
csr, err := connect.ParseCSR(raw)
require.NoError(err)
cert, err := provider.Sign(csr)
require.NoError(err)
parsed, err := connect.ParseCert(cert)
require.NoError(err)
require.Equal(parsed.URIs[0], spiffeService.URI())
require.NotEqual(firstSerial, parsed.SerialNumber.Uint64())
// Ensure the cert is valid now and expires within the correct limit.
require.True(parsed.NotAfter.Sub(time.Now()) < time.Hour)
require.True(parsed.NotBefore.Before(time.Now()))
}
}
func TestVaultCAProvider_CrossSignCA(t *testing.T) {
t.Parallel()
provider1, core1, listener1 := testVaultCluster(t)
defer core1.Shutdown()
defer listener1.Close()
provider2, core2, listener2 := testVaultCluster(t)
defer core2.Shutdown()
defer listener2.Close()
testCrossSignProviders(t, provider1, provider2)
}
func TestVaultProvider_SignIntermediate(t *testing.T) {
t.Parallel()
provider1, core1, listener1 := testVaultCluster(t)
defer core1.Shutdown()
defer listener1.Close()
provider2, core2, listener2 := testVaultClusterWithConfig(t, false, nil)
defer core2.Shutdown()
defer listener2.Close()
testSignIntermediateCrossDC(t, provider1, provider2)
}
func TestVaultProvider_SignIntermediateConsul(t *testing.T) {
t.Parallel()
require := require.New(t)
// primary = Vault, secondary = Consul
{
provider1, core, listener := testVaultCluster(t)
defer core.Shutdown()
defer listener.Close()
conf := testConsulCAConfig()
delegate := newMockDelegate(t, conf)
provider2 := &ConsulProvider{Delegate: delegate}
require.NoError(provider2.Configure(conf.ClusterID, false, conf.Config))
testSignIntermediateCrossDC(t, provider1, provider2)
}
// primary = Consul, secondary = Vault
{
conf := testConsulCAConfig()
delegate := newMockDelegate(t, conf)
provider1 := &ConsulProvider{Delegate: delegate}
require.NoError(provider1.Configure(conf.ClusterID, true, conf.Config))
require.NoError(provider1.GenerateRoot())
provider2, core, listener := testVaultClusterWithConfig(t, false, nil)
defer core.Shutdown()
defer listener.Close()
testSignIntermediateCrossDC(t, provider1, provider2)
}
}