mirror of https://github.com/hashicorp/consul
connect/ca: update Vault provider to add cross-signing methods
parent
6a2fc00997
commit
8a70ea64a6
|
@ -32,7 +32,12 @@ type Provider interface {
|
||||||
// intemediate and any cross-signed intermediates managed by Consul.
|
// intemediate and any cross-signed intermediates managed by Consul.
|
||||||
Sign(*x509.CertificateRequest) (string, error)
|
Sign(*x509.CertificateRequest) (string, error)
|
||||||
|
|
||||||
// CrossSignCA must accept a CA certificate signed by another CA's key
|
// GetCrossSigningCSR returns a CSR that can be signed by another root
|
||||||
|
// to create an intermediate cert that forms a chain of trust back to
|
||||||
|
// that root CA.
|
||||||
|
GetCrossSigningCSR() (*x509.CertificateRequest, error)
|
||||||
|
|
||||||
|
// CrossSignCA must accept a CA CSR signed by another CA's key
|
||||||
// and cross sign it exactly as it is such that it forms a chain back the the
|
// and cross sign it exactly as it is such that it forms a chain back the the
|
||||||
// CAProvider's current root. Specifically, the Distinguished Name, Subject
|
// CAProvider's current root. Specifically, the Distinguished Name, Subject
|
||||||
// Alternative Name, SubjectKeyID and other relevant extensions must be kept.
|
// Alternative Name, SubjectKeyID and other relevant extensions must be kept.
|
||||||
|
@ -40,7 +45,7 @@ type Provider interface {
|
||||||
// AuthorityKeyID set to the CAProvider's current signing key as well as the
|
// AuthorityKeyID set to the CAProvider's current signing key as well as the
|
||||||
// Issuer related fields changed as necessary. The resulting certificate is
|
// Issuer related fields changed as necessary. The resulting certificate is
|
||||||
// returned as a PEM formatted string.
|
// returned as a PEM formatted string.
|
||||||
CrossSignCA(*x509.Certificate) (string, error)
|
CrossSignCA(*x509.CertificateRequest) (string, error)
|
||||||
|
|
||||||
// Cleanup performs any necessary cleanup that should happen when the provider
|
// Cleanup performs any necessary cleanup that should happen when the provider
|
||||||
// is shut down permanently, such as removing a temporary PKI backend in Vault
|
// is shut down permanently, such as removing a temporary PKI backend in Vault
|
||||||
|
|
|
@ -181,7 +181,7 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if csr == nil || csr.Data["csr"] == nil {
|
if csr == nil || csr.Data["csr"] == "" {
|
||||||
return "", fmt.Errorf("got empty value when generating intermediate CSR")
|
return "", fmt.Errorf("got empty value when generating intermediate CSR")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if intermediate == nil || intermediate.Data["certificate"] == nil {
|
if intermediate == nil || intermediate.Data["certificate"] == "" {
|
||||||
return "", fmt.Errorf("got empty value when generating intermediate certificate")
|
return "", fmt.Errorf("got empty value when generating intermediate certificate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,14 +240,62 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(kyhavlov): decide which vault endpoint to use here
|
// GetCrossSigningCSR creates a CSR for an intermediate CA certificate to be signed
|
||||||
func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
// by another CA provider.
|
||||||
var pemBuf bytes.Buffer
|
func (v *VaultProvider) GetCrossSigningCSR() (*x509.CertificateRequest, error) {
|
||||||
if err := pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
|
// Generate a new intermediate CSR for the root to sign.
|
||||||
|
spiffeID := connect.SpiffeIDSigning{ClusterID: v.clusterId, Domain: "consul"}
|
||||||
|
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 nil, err
|
||||||
|
}
|
||||||
|
if csr == nil || csr.Data["csr"] == "" {
|
||||||
|
return nil, fmt.Errorf("got empty value when generating intermediate CSR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the parsed CSR.
|
||||||
|
pem, ok := csr.Data["csr"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("CSR was not a string")
|
||||||
|
}
|
||||||
|
result, err := connect.ParseCSR(pem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrossSignCA creates a CSR from the given CA certificate and uses the
|
||||||
|
// configured root PKI backend to issue a cert from the request.
|
||||||
|
func (v *VaultProvider) CrossSignCA(cert *x509.CertificateRequest) (string, error) {
|
||||||
|
var csrBuf bytes.Buffer
|
||||||
|
err := pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: cert.Raw})
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return pemBuf.String(), nil
|
// Have the root PKI backend sign this CSR.
|
||||||
|
response, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-intermediate", map[string]interface{}{
|
||||||
|
"csr": csrBuf.String(),
|
||||||
|
"use_csr_values": true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error having Vault cross-sign cert: %v", err)
|
||||||
|
}
|
||||||
|
if response == nil || response.Data["certificate"] == "" {
|
||||||
|
return "", fmt.Errorf("certificate info returned from Vault was blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
xcCert, ok := response.Data["certificate"].(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("certificate was not a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
return xcCert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup unmounts the configured intermediate PKI backend. It's fine to tear
|
// Cleanup unmounts the configured intermediate PKI backend. It's fine to tear
|
||||||
|
|
|
@ -148,3 +148,43 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
|
||||||
require.True(parsed.NotBefore.Before(time.Now()))
|
require.True(parsed.NotBefore.Before(time.Now()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVaultCAProvider_CrossSignCA(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
require := require.New(t)
|
||||||
|
provider1, core1, listener1 := testVaultCluster(t)
|
||||||
|
defer core1.Shutdown()
|
||||||
|
defer listener1.Close()
|
||||||
|
|
||||||
|
provider2, core2, listener2 := testVaultCluster(t)
|
||||||
|
defer core2.Shutdown()
|
||||||
|
defer listener2.Close()
|
||||||
|
|
||||||
|
// Have provider2 generate a cross-signing CSR
|
||||||
|
csr, err := provider2.GetCrossSigningCSR()
|
||||||
|
require.NoError(err)
|
||||||
|
oldSubject := csr.Subject.CommonName
|
||||||
|
|
||||||
|
// Have the provider cross sign our new CA cert.
|
||||||
|
xcPEM, err := provider1.CrossSignCA(csr)
|
||||||
|
require.NoError(err)
|
||||||
|
xc, err := connect.ParseCert(xcPEM)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
rootPEM, err := provider1.ActiveRoot()
|
||||||
|
require.NoError(err)
|
||||||
|
root, err := connect.ParseCert(rootPEM)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// AuthorityKeyID should be the signing root's, SubjectKeyId should be different.
|
||||||
|
require.Equal(root.AuthorityKeyId, xc.AuthorityKeyId)
|
||||||
|
require.NotEqual(root.SubjectKeyId, xc.SubjectKeyId)
|
||||||
|
|
||||||
|
// Subject name should not have changed.
|
||||||
|
require.NotEqual(root.Subject.CommonName, xc.Subject.CommonName)
|
||||||
|
require.Equal(oldSubject, xc.Subject.CommonName)
|
||||||
|
|
||||||
|
// Issuer should be the signing root.
|
||||||
|
require.Equal(root.Issuer.CommonName, xc.Issuer.CommonName)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue