From 6a2fc00997c63f5332a699e5f5691ddf2f726636 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Thu, 14 Jun 2018 14:20:10 -0700 Subject: [PATCH] connect/ca: add URI SAN support to the Vault provider --- agent/config/default.go | 1 + agent/connect/ca/provider_vault.go | 8 +-- agent/connect/ca/provider_vault_test.go | 70 +++++++++++++++++++++++++ agent/connect/csr.go | 8 +++ agent/connect/testing_ca.go | 6 +++ 5 files changed, 89 insertions(+), 4 deletions(-) diff --git a/agent/config/default.go b/agent/config/default.go index c95a0c6434..99082d09c6 100644 --- a/agent/config/default.go +++ b/agent/config/default.go @@ -113,6 +113,7 @@ func DevSource() Source { connect = { enabled = true + ca_provider = "consul" } performance = { raft_multiplier = 1 diff --git a/agent/connect/ca/provider_vault.go b/agent/connect/ca/provider_vault.go index 9d7626d65d..0fbfd75ca1 100644 --- a/agent/connect/ca/provider_vault.go +++ b/agent/connect/ca/provider_vault.go @@ -163,8 +163,9 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) { spiffeID := connect.SpiffeIDSigning{ClusterID: v.clusterId, Domain: "consul"} if role == nil { _, err := v.client.Logical().Write(rolePath, map[string]interface{}{ - "allowed_domains": spiffeID.Host(), + "allow_any_name": true, "allowed_uri_sans": "spiffe://*", + "key_type": "any", "max_ttl": "72h", }) if err != nil { @@ -218,11 +219,10 @@ func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) { // Use the leaf cert role to sign a new cert for this CSR. response, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"sign/"+VaultCALeafCertRole, map[string]interface{}{ - "csr": pemBuf.String(), - "common_name": csr.Subject.CommonName, + "csr": pemBuf.String(), }) if err != nil { - return "", nil + return "", fmt.Errorf("error issuing cert: %v", err) } if response == nil || response.Data["certificate"] == "" || response.Data["issuing_ca"] == "" { return "", fmt.Errorf("certificate info returned from Vault was blank") diff --git a/agent/connect/ca/provider_vault_test.go b/agent/connect/ca/provider_vault_test.go index 9f18356fca..fc5552e616 100644 --- a/agent/connect/ca/provider_vault_test.go +++ b/agent/connect/ca/provider_vault_test.go @@ -1,9 +1,11 @@ package ca import ( + "fmt" "io/ioutil" "net" "testing" + "time" "github.com/hashicorp/consul/agent/connect" vaultapi "github.com/hashicorp/vault/api" @@ -76,5 +78,73 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) { 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 := 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) + + 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()) + require.Equal(parsed.Subject.CommonName, "foo") + firstSerial = parsed.SerialNumber.Uint64() + + // Ensure the cert is valid now and expires within the correct limit. + require.True(parsed.NotAfter.Sub(time.Now()) < 3*24*time.Hour) + require.True(parsed.NotBefore.Before(time.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.Equal(parsed.Subject.CommonName, "bar") + 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()) < 3*24*time.Hour) + require.True(parsed.NotBefore.Before(time.Now())) } } diff --git a/agent/connect/csr.go b/agent/connect/csr.go index 16a46af3fd..44ee53d188 100644 --- a/agent/connect/csr.go +++ b/agent/connect/csr.go @@ -5,16 +5,24 @@ import ( "crypto" "crypto/rand" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" + "fmt" "net/url" ) // CreateCSR returns a CSR to sign the given service along with the PEM-encoded // private key for this certificate. func CreateCSR(uri CertURI, privateKey crypto.Signer) (string, error) { + serviceId, ok := uri.(*SpiffeIDService) + if !ok { + return "", fmt.Errorf("SPIFFE ID in CSR must be a service ID") + } + template := &x509.CertificateRequest{ URIs: []*url.URL{uri.URI()}, SignatureAlgorithm: x509.ECDSAWithSHA256, + Subject: pkix.Name{CommonName: serviceId.Service}, } // Create the CSR itself diff --git a/agent/connect/testing_ca.go b/agent/connect/testing_ca.go index cc015af81e..93cfca13af 100644 --- a/agent/connect/testing_ca.go +++ b/agent/connect/testing_ca.go @@ -201,9 +201,15 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string // TestCSR returns a CSR to sign the given service along with the PEM-encoded // private key for this certificate. func TestCSR(t testing.T, uri CertURI) (string, string) { + serviceId, ok := uri.(*SpiffeIDService) + if !ok { + t.Fatalf("SPIFFE ID in CSR must be a service ID") + } + template := &x509.CertificateRequest{ URIs: []*url.URL{uri.URI()}, SignatureAlgorithm: x509.ECDSAWithSHA256, + Subject: pkix.Name{CommonName: serviceId.Service}, } // Create the private key we'll use