Don't revoke CA certificates with leases.

This commit is contained in:
Jeff Mitchell 2016-05-09 19:53:28 -04:00
parent 0aad4e68a7
commit 9de0ea081a
7 changed files with 53 additions and 24 deletions

View file

@ -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 {

View file

@ -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()

View file

@ -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) {

View file

@ -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,

View file

@ -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)
}

View file

@ -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

View file

@ -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",