connect/ca: add intermediate functions to Vault ca provider

This commit is contained in:
Kyle Havlovitz 2018-09-13 13:09:07 -07:00
parent 52e8652ac5
commit 2919519665
5 changed files with 160 additions and 59 deletions

View File

@ -27,6 +27,8 @@ type Provider interface {
// true, calling this is an error.
GenerateIntermediateCSR() (string, error)
// SetIntermediate sets the provider to use the given intermediate certificate
// as well as the root it was signed by.
SetIntermediate(intermediatePEM, rootPEM string) error
// ActiveIntermediate returns the current signing cert used by this provider

View File

@ -208,6 +208,8 @@ func (c *ConsulProvider) GenerateIntermediateCSR() (string, error) {
return csr, nil
}
// SetIntermediate validates that the given intermediate is for the right private key
// and writes the given intermediate and root certificates to the state.
func (c *ConsulProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
_, providerState, err := c.getState()
if err != nil {

View File

@ -291,6 +291,12 @@ func TestConsulProvider_SignIntermediate(t *testing.T) {
provider2 := &ConsulProvider{Delegate: delegate2}
require.NoError(provider2.Configure(conf2.ClusterID, false, conf2.Config))
testSignIntermediateCrossDC(t, provider1, provider2)
}
func testSignIntermediateCrossDC(t *testing.T, provider1, provider2 Provider) {
require := require.New(t)
// Get the intermediate CSR from provider2.
csrPEM, err := provider2.GenerateIntermediateCSR()
require.NoError(err)

View File

@ -102,11 +102,92 @@ func (v *VaultProvider) GenerateRoot() error {
return nil
}
// GenerateIntermediateCSR creates a private key and generates a CSR
// for another datacenter's root to sign, overwriting the intermediate backend
// in the process.
func (v *VaultProvider) GenerateIntermediateCSR() (string, error) {
return "", nil
if v.isRoot {
return "", fmt.Errorf("provider is the root certificate authority, " +
"cannot generate an intermediate CSR")
}
return v.generateIntermediateCSR()
}
func (v *VaultProvider) generateIntermediateCSR() (string, error) {
mounts, err := v.client.Sys().ListMounts()
if err != nil {
return "", err
}
// Mount the backend if it isn't mounted already.
if _, ok := mounts[v.config.IntermediatePKIPath]; !ok {
err := v.client.Sys().Mount(v.config.IntermediatePKIPath, &vaultapi.MountInput{
Type: "pki",
Description: "intermediate CA backend for Consul Connect",
Config: vaultapi.MountConfigInput{
MaxLeaseTTL: "2160h",
},
})
if err != nil {
return "", err
}
}
// Create the role for issuing leaf certs if it doesn't exist yet
rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole
role, err := v.client.Logical().Read(rolePath)
if err != nil {
return "", err
}
spiffeID := connect.SpiffeIDSigning{ClusterID: v.clusterId, Domain: "consul"}
if role == nil {
_, err := v.client.Logical().Write(rolePath, map[string]interface{}{
"allow_any_name": true,
"allowed_uri_sans": "spiffe://*",
"key_type": "any",
"max_ttl": v.config.LeafCertTTL.String(),
"require_cn": false,
})
if err != nil {
return "", err
}
}
// Generate a new intermediate CSR for the root to sign.
data, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/generate/internal", map[string]interface{}{
"common_name": "Vault CA Intermediate Authority",
"uri_sans": spiffeID.URI().String(),
})
if err != nil {
return "", err
}
if data == nil || data.Data["csr"] == "" {
return "", fmt.Errorf("got empty value when generating intermediate CSR")
}
csr, ok := data.Data["csr"].(string)
if !ok {
return "", fmt.Errorf("csr result is not a string")
}
return csr, nil
}
// SetIntermediate writes the incoming intermediate and root certificates to the
// intermediate backend (as a chain).
func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
if v.isRoot {
return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter")
}
_, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{
"certificate": fmt.Sprintf("%s\n%s", intermediatePEM, rootPEM),
})
if err != nil {
return err
}
return nil
}
@ -149,61 +230,14 @@ func (v *VaultProvider) getCA(path string) (string, error) {
// necessary, then generates and signs a new CA CSR using the root PKI backend
// and updates the intermediate backend to use that new certificate.
func (v *VaultProvider) GenerateIntermediate() (string, error) {
mounts, err := v.client.Sys().ListMounts()
csr, err := v.generateIntermediateCSR()
if err != nil {
return "", err
}
// Mount the backend if it isn't mounted already.
if _, ok := mounts[v.config.IntermediatePKIPath]; !ok {
err := v.client.Sys().Mount(v.config.IntermediatePKIPath, &vaultapi.MountInput{
Type: "pki",
Description: "intermediate CA backend for Consul Connect",
Config: vaultapi.MountConfigInput{
MaxLeaseTTL: "2160h",
},
})
if err != nil {
return "", err
}
}
// Create the role for issuing leaf certs if it doesn't exist yet
rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole
role, err := v.client.Logical().Read(rolePath)
if err != nil {
return "", err
}
spiffeID := connect.SpiffeIDSigning{ClusterID: v.clusterId, Domain: "consul"}
if role == nil {
_, err := v.client.Logical().Write(rolePath, map[string]interface{}{
"allow_any_name": true,
"allowed_uri_sans": "spiffe://*",
"key_type": "any",
"max_ttl": v.config.LeafCertTTL.String(),
"require_cn": false,
})
if err != nil {
return "", err
}
}
// Generate a new intermediate CSR for the root to sign.
csr, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/generate/internal", map[string]interface{}{
"common_name": "Vault CA Intermediate Authority",
"uri_sans": spiffeID.URI().String(),
})
if err != nil {
return "", err
}
if csr == nil || csr.Data["csr"] == "" {
return "", fmt.Errorf("got empty value when generating intermediate CSR")
}
// Sign the CSR with the root backend.
intermediate, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-intermediate", map[string]interface{}{
"csr": csr.Data["csr"],
"csr": csr,
"format": "pem_bundle",
})
if err != nil {
@ -257,8 +291,34 @@ func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) {
return fmt.Sprintf("%s\n%s", cert, ca), nil
}
func (v *VaultProvider) SignIntermediate(*x509.CertificateRequest) (string, error) {
return "", nil
// SignIntermediate returns a signed CA certificate with a path length constraint
// of 0 to ensure that the certificate cannot be used to generate further CA certs.
func (v *VaultProvider) SignIntermediate(csr *x509.CertificateRequest) (string, error) {
var pemBuf bytes.Buffer
err := pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr.Raw})
if err != nil {
return "", err
}
// Sign the CSR with the root backend.
data, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-intermediate", map[string]interface{}{
"csr": pemBuf.String(),
"format": "pem_bundle",
"max_path_length": 0,
})
if err != nil {
return "", err
}
if data == nil || data.Data["certificate"] == "" {
return "", fmt.Errorf("got empty value when generating intermediate certificate")
}
intermediate, ok := data.Data["certificate"].(string)
if !ok {
return "", fmt.Errorf("signed intermediate result is not a string")
}
return intermediate, nil
}
// CrossSignCA takes a CA certificate and cross-signs it to form a trust chain

View File

@ -16,10 +16,10 @@ import (
)
func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener) {
return testVaultClusterWithConfig(t, nil)
return testVaultClusterWithConfig(t, true, nil)
}
func testVaultClusterWithConfig(t *testing.T, rawConf map[string]interface{}) (*VaultProvider, *vault.Core, net.Listener) {
func testVaultClusterWithConfig(t *testing.T, isRoot bool, rawConf map[string]interface{}) (*VaultProvider, *vault.Core, net.Listener) {
if err := vault.AddTestLogicalBackend("pki", pki.Factory); err != nil {
t.Fatal(err)
}
@ -41,10 +41,12 @@ func testVaultClusterWithConfig(t *testing.T, rawConf map[string]interface{}) (*
require := require.New(t)
provider := &VaultProvider{}
require.NoError(provider.Configure("asdf", true, conf))
require.NoError(provider.Configure("asdf", isRoot, conf))
if isRoot {
require.NoError(provider.GenerateRoot())
_, err := provider.GenerateIntermediate()
require.NoError(err)
}
return provider, core, ln
}
@ -100,7 +102,7 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
t.Parallel()
require := require.New(t)
provider, core, listener := testVaultClusterWithConfig(t, map[string]interface{}{
provider, core, listener := testVaultClusterWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
})
defer core.Shutdown()
@ -176,3 +178,32 @@ func TestVaultCAProvider_CrossSignCA(t *testing.T) {
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()
provider1, core1, listener1 := testVaultCluster(t)
defer core1.Shutdown()
defer listener1.Close()
conf := testConsulCAConfig()
delegate := newMockDelegate(t, conf)
provider2 := &ConsulProvider{Delegate: delegate}
require.NoError(t, provider2.Configure(conf.ClusterID, false, conf.Config))
testSignIntermediateCrossDC(t, provider1, provider2)
}