diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index b12afd47b2..f1efe6d3f3 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -489,6 +489,12 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s "common_name": "Root Cert", "ttl": "180h", }, + Check: func(resp *logical.Response) error { + if resp.Secret != nil && resp.Secret.LeaseID != "" { + return fmt.Errorf("root returned with a lease") + } + return nil + }, }, logicaltest.TestStep{ @@ -556,6 +562,9 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s if certString == "" { return fmt.Errorf("no certificate returned") } + if resp.Secret != nil && resp.Secret.LeaseID != "" { + return fmt.Errorf("signed intermediate returned with a lease") + } certBytes, _ := base64.StdEncoding.DecodeString(certString) certs, err := x509.ParseCertificates(certBytes) if err != nil { @@ -596,6 +605,9 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s if certString == "" { return fmt.Errorf("no certificate returned") } + if resp.Secret != nil && resp.Secret.LeaseID != "" { + return fmt.Errorf("signed intermediate returned with a lease") + } certBytes, _ := base64.StdEncoding.DecodeString(certString) certs, err := x509.ParseCertificates(certBytes) if err != nil { diff --git a/builtin/logical/pki/crl_util.go b/builtin/logical/pki/crl_util.go index 53459d48d0..2dd32b5f3a 100644 --- a/builtin/logical/pki/crl_util.go +++ b/builtin/logical/pki/crl_util.go @@ -17,7 +17,7 @@ type revocationInfo struct { } // Revokes a cert, and tries to be smart about error recovery -func revokeCert(b *backend, req *logical.Request, serial string) (*logical.Response, error) { +func revokeCert(b *backend, req *logical.Request, serial string, fromLease bool) (*logical.Response, error) { // As this backend is self-contained and this function does not hook into // third parties to manage users or resources, if the mount is tainted, // revocation doesn't matter anyways -- the CRL that would be written will @@ -80,6 +80,12 @@ func revokeCert(b *backend, req *logical.Request, serial string) (*logical.Respo return nil, nil } + // Compatibility: Don't revoke CAs if they had leases. New CAs going + // forward aren't issued leases. + if cert.IsCA && fromLease { + return nil, nil + } + revInfo.CertificateBytes = certEntry.Value revInfo.RevocationTime = time.Now().Unix() diff --git a/builtin/logical/pki/path_revoke.go b/builtin/logical/pki/path_revoke.go index 2bd205fe14..ce0ae1afe9 100644 --- a/builtin/logical/pki/path_revoke.go +++ b/builtin/logical/pki/path_revoke.go @@ -55,7 +55,7 @@ func (b *backend) pathRevokeWrite(req *logical.Request, data *framework.FieldDat b.revokeStorageLock.Lock() defer b.revokeStorageLock.Unlock() - return revokeCert(b, req, serial) + return revokeCert(b, req, serial, false) } func (b *backend) pathRotateCRLRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { diff --git a/builtin/logical/pki/path_root.go b/builtin/logical/pki/path_root.go index d82dc8e2c4..b127533dc8 100644 --- a/builtin/logical/pki/path_root.go +++ b/builtin/logical/pki/path_root.go @@ -3,7 +3,6 @@ package pki import ( "encoding/base64" "fmt" - "time" "github.com/hashicorp/vault/helper/certutil" "github.com/hashicorp/vault/logical" @@ -97,14 +96,12 @@ func (b *backend) pathCAGenerateRoot( return nil, fmt.Errorf("error converting raw cert bundle to cert bundle: %s", err) } - resp := b.Secret(SecretCertsType).Response( - map[string]interface{}{ + resp := &logical.Response{ + Data: map[string]interface{}{ "expiration": int64(parsedBundle.Certificate.NotAfter.Unix()), "serial_number": cb.SerialNumber, }, - map[string]interface{}{ - "serial_number": cb.SerialNumber, - }) + } switch format { case "pem": @@ -135,8 +132,6 @@ func (b *backend) pathCAGenerateRoot( } } - resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now()) - // Store it as the CA bundle entry, err := logical.StorageEntryJSON("config/ca_bundle", cb) if err != nil { @@ -237,14 +232,12 @@ func (b *backend) pathCASignIntermediate( return nil, fmt.Errorf("Error converting raw cert bundle to cert bundle: %s", err) } - resp := b.Secret(SecretCertsType).Response( - map[string]interface{}{ + resp := &logical.Response{ + Data: map[string]interface{}{ "expiration": int64(parsedBundle.Certificate.NotAfter.Unix()), "serial_number": cb.SerialNumber, }, - map[string]interface{}{ - "serial_number": cb.SerialNumber, - }) + } switch format { case "pem": @@ -260,8 +253,6 @@ func (b *backend) pathCASignIntermediate( resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.IssuingCABytes) } - resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now()) - err = req.Storage.Put(&logical.StorageEntry{ Key: "certs/" + cb.SerialNumber, Value: parsedBundle.CertificateBytes, diff --git a/builtin/logical/pki/secret_certs.go b/builtin/logical/pki/secret_certs.go index a65a75a4ba..69bc6db364 100644 --- a/builtin/logical/pki/secret_certs.go +++ b/builtin/logical/pki/secret_certs.go @@ -51,5 +51,5 @@ func (b *backend) secretCredsRevoke( b.revokeStorageLock.Lock() defer b.revokeStorageLock.Unlock() - return revokeCert(b, req, serial) + return revokeCert(b, req, serial, true) } diff --git a/website/source/docs/install/upgrade-to-0.6.html.md b/website/source/docs/install/upgrade-to-0.6.html.md index 801988d4f4..c10e43e1ff 100644 --- a/website/source/docs/install/upgrade-to-0.6.html.md +++ b/website/source/docs/install/upgrade-to-0.6.html.md @@ -39,6 +39,19 @@ certificate storage, or both. In addition, you can specify a safety buffer (defaulting to 72 hours) to ensure that any time discrepancies between your hosts is accounted for. +## PKI Backend Does Not Issue Leases for CA Certificates + +When a token expires, it revokes all leases associated with it. This means that +long-lived CA certs need correspondingly long-lived tokens, something that is +easy to forget, resulting in an unintended revocation of the CA certificate +when the token expires. To prevent this, root and intermediate CA certs no +longer have associated leases. To revoke these certificates, use the +`pki/revoke` endpoint. + +CA certificates that have already been issued and acquired leases will report +to the lease manager that revocation was successful, but will not actually be +revoked and placed onto the CRL. + ## Cert Authentication Backend Performs Client Checking During Renewals The `cert` backend now performs a variant of channel binding at renewal time diff --git a/website/source/docs/secrets/pki/index.html.md b/website/source/docs/secrets/pki/index.html.md index e2385363df..f8da8092c3 100644 --- a/website/source/docs/secrets/pki/index.html.md +++ b/website/source/docs/secrets/pki/index.html.md @@ -127,6 +127,15 @@ enforced. Software that can handle SHA256 signatures should also be able to handle 2048-bit keys, and 1024-bit keys are considered unsafe and are disallowed in the Internet PKI. +### Token Lifetimes and Revocation + +When a token expires, it revokes all leases associated with it. This means that +long-lived CA certs need correspondingly long-lived tokens, something that is +easy to forget. Starting with 0.6, root and intermediate CA certs no longer +have associated leases, to prevent unintended revocation when not using a token +with a long enough lifetime. To revoke these certificates, use the `pki/revoke` +endpoint. + ## Quick Start #### Mount the backend @@ -166,8 +175,6 @@ Now, we generate our root certificate: ```text $ vault write pki/root/generate/internal common_name=myvault.com ttl=87600h Key Value -lease_id pki/root/generate/internal/aa959dd4-467e-e5ff-642b-371add518b40 -lease_duration 315359999 certificate -----BEGIN CERTIFICATE----- MIIDvTCCAqWgAwIBAgIUAsza+fvOw+Xh9ifYQ0gNN0ruuWcwDQYJKoZIhvcNAQEL BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wHhcNMTUxMTE5MTYwNDU5WhcNMjUx @@ -1308,8 +1315,8 @@ subpath for interactive help output. ```javascript { - "lease_id": "pki/root/generate/internal/aa959dd4-467e-e5ff-642b-371add518b40", - "lease_duration": 315359999, + "lease_id": "", + "lease_duration": 0, "renewable": false, "data": { "certificate": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n", @@ -1418,9 +1425,9 @@ subpath for interactive help output. ```javascript { - "lease_id": "pki/root/sign-intermediate/bc23e3c6-8dcd-48c6-f3af-dd2db7f815c2", + "lease_id": "", "renewable": false, - "lease_duration": 21600, + "lease_duration": 0, "data": { "certificate": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n", "issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDUTCCAjmgAwIBAgIJAKM+z4MSfw2mMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV\n...\nG/7g4koczXLoUM3OQXd5Aq2cs4SS1vODrYmgbioFsQ3eDHd1fg==\n-----END CERTIFICATE-----\n",