mirror of https://github.com/status-im/consul.git
connect/ca: add intermediate functions to Vault ca provider
This commit is contained in:
parent
52e8652ac5
commit
2919519665
|
@ -27,6 +27,8 @@ type Provider interface {
|
||||||
// true, calling this is an error.
|
// true, calling this is an error.
|
||||||
GenerateIntermediateCSR() (string, 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
|
SetIntermediate(intermediatePEM, rootPEM string) error
|
||||||
|
|
||||||
// ActiveIntermediate returns the current signing cert used by this provider
|
// ActiveIntermediate returns the current signing cert used by this provider
|
||||||
|
|
|
@ -208,6 +208,8 @@ func (c *ConsulProvider) GenerateIntermediateCSR() (string, error) {
|
||||||
return csr, nil
|
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 {
|
func (c *ConsulProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
|
||||||
_, providerState, err := c.getState()
|
_, providerState, err := c.getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -291,6 +291,12 @@ func TestConsulProvider_SignIntermediate(t *testing.T) {
|
||||||
provider2 := &ConsulProvider{Delegate: delegate2}
|
provider2 := &ConsulProvider{Delegate: delegate2}
|
||||||
require.NoError(provider2.Configure(conf2.ClusterID, false, conf2.Config))
|
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.
|
// Get the intermediate CSR from provider2.
|
||||||
csrPEM, err := provider2.GenerateIntermediateCSR()
|
csrPEM, err := provider2.GenerateIntermediateCSR()
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
|
@ -102,11 +102,92 @@ func (v *VaultProvider) GenerateRoot() error {
|
||||||
return nil
|
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) {
|
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 {
|
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
|
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
|
// necessary, then generates and signs a new CA CSR using the root PKI backend
|
||||||
// and updates the intermediate backend to use that new certificate.
|
// and updates the intermediate backend to use that new certificate.
|
||||||
func (v *VaultProvider) GenerateIntermediate() (string, error) {
|
func (v *VaultProvider) GenerateIntermediate() (string, error) {
|
||||||
mounts, err := v.client.Sys().ListMounts()
|
csr, err := v.generateIntermediateCSR()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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.
|
// Sign the CSR with the root backend.
|
||||||
intermediate, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-intermediate", map[string]interface{}{
|
intermediate, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-intermediate", map[string]interface{}{
|
||||||
"csr": csr.Data["csr"],
|
"csr": csr,
|
||||||
"format": "pem_bundle",
|
"format": "pem_bundle",
|
||||||
})
|
})
|
||||||
if err != nil {
|
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
|
return fmt.Sprintf("%s\n%s", cert, ca), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VaultProvider) SignIntermediate(*x509.CertificateRequest) (string, error) {
|
// SignIntermediate returns a signed CA certificate with a path length constraint
|
||||||
return "", nil
|
// 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
|
// CrossSignCA takes a CA certificate and cross-signs it to form a trust chain
|
||||||
|
|
|
@ -16,10 +16,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener) {
|
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 {
|
if err := vault.AddTestLogicalBackend("pki", pki.Factory); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,12 @@ func testVaultClusterWithConfig(t *testing.T, rawConf map[string]interface{}) (*
|
||||||
|
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
provider := &VaultProvider{}
|
provider := &VaultProvider{}
|
||||||
require.NoError(provider.Configure("asdf", true, conf))
|
require.NoError(provider.Configure("asdf", isRoot, conf))
|
||||||
require.NoError(provider.GenerateRoot())
|
if isRoot {
|
||||||
_, err := provider.GenerateIntermediate()
|
require.NoError(provider.GenerateRoot())
|
||||||
require.NoError(err)
|
_, err := provider.GenerateIntermediate()
|
||||||
|
require.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
return provider, core, ln
|
return provider, core, ln
|
||||||
}
|
}
|
||||||
|
@ -100,7 +102,7 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
provider, core, listener := testVaultClusterWithConfig(t, map[string]interface{}{
|
provider, core, listener := testVaultClusterWithConfig(t, true, map[string]interface{}{
|
||||||
"LeafCertTTL": "1h",
|
"LeafCertTTL": "1h",
|
||||||
})
|
})
|
||||||
defer core.Shutdown()
|
defer core.Shutdown()
|
||||||
|
@ -176,3 +178,32 @@ func TestVaultCAProvider_CrossSignCA(t *testing.T) {
|
||||||
|
|
||||||
testCrossSignProviders(t, provider1, provider2)
|
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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue