mirror of https://github.com/status-im/consul.git
connect/ca: update Vault provider to add cross-signing methods
This commit is contained in:
parent
6a2fc00997
commit
8a70ea64a6
|
@ -32,7 +32,12 @@ type Provider interface {
|
|||
// intemediate and any cross-signed intermediates managed by Consul.
|
||||
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
|
||||
// CAProvider's current root. Specifically, the Distinguished Name, Subject
|
||||
// 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
|
||||
// Issuer related fields changed as necessary. The resulting certificate is
|
||||
// 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
|
||||
// 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 {
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) {
|
|||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -240,14 +240,62 @@ func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
|||
return fmt.Sprintf("%s\n%s", cert, ca), nil
|
||||
}
|
||||
|
||||
// todo(kyhavlov): decide which vault endpoint to use here
|
||||
func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||
var pemBuf bytes.Buffer
|
||||
if err := pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
|
||||
// GetCrossSigningCSR creates a CSR for an intermediate CA certificate to be signed
|
||||
// by another CA provider.
|
||||
func (v *VaultProvider) GetCrossSigningCSR() (*x509.CertificateRequest, error) {
|
||||
// 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 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
|
||||
|
|
|
@ -148,3 +148,43 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
|
|||
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