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.
|
||||
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue