Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pki: fix tidy removal on revoked entries #11367

Merged
merged 2 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 70 additions & 5 deletions builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"math"
"math/big"
mathrand "math/rand"
Expand Down Expand Up @@ -2985,6 +2986,7 @@ func TestBackend_RevokePlusTidy_Intermediate(t *testing.T) {
secret, err = client.Logical().Write("pki/root/sign-intermediate", map[string]interface{}{
"permitted_dns_domains": ".myvault.com",
"csr": intermediateCSR,
"ttl": "10s",
})
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -3023,14 +3025,77 @@ func TestBackend_RevokePlusTidy_Intermediate(t *testing.T) {
// Sleep a bit to make sure we're past the safety buffer
time.Sleep(2 * time.Second)

// Attempt to read the intermediate cert after revoke + tidy, and ensure
// that it's no longer present
secret, err = client.Logical().Read("pki/cert/" + intermediateCASerialColon)
// Get CRL and ensure the tidied cert is still in the list after the tidy
// operation since it's not past the NotAfter (ttl) value yet.
req := client.NewRequest("GET", "/v1/pki/crl")
resp, err := client.RawRequest(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()

crlBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(crlBytes) == 0 {
t.Fatalf("expected CRL in response body")
}

crl, err := x509.ParseDERCRL(crlBytes)
if err != nil {
t.Fatal(err)
}

revokedCerts := crl.TBSCertList.RevokedCertificates
if len(revokedCerts) == 0 {
t.Fatal("expected CRL to be non-empty")
}

sn := certutil.GetHexFormatted(revokedCerts[0].SerialNumber.Bytes(), ":")
if sn != intermediateCertSerial {
t.Fatalf("expected: %v, got: %v", intermediateCertSerial, sn)
}

// Wait for cert to expire
time.Sleep(10 * time.Second)

// Issue a tidy on /pki
_, err = client.Logical().Write("pki/tidy", map[string]interface{}{
"tidy_cert_store": true,
"tidy_revoked_certs": true,
"safety_buffer": "1s",
})
if err != nil {
t.Fatal(err)
}

// Sleep a bit to make sure we're past the safety buffer
time.Sleep(2 * time.Second)

req = client.NewRequest("GET", "/v1/pki/crl")
resp, err = client.RawRequest(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()

crlBytes, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(crlBytes) == 0 {
t.Fatalf("expected CRL in response body")
}

crl, err = x509.ParseDERCRL(crlBytes)
if err != nil {
t.Fatal(err)
}
if secret != nil {
t.Fatalf("expected empty response data, got: %#v", secret.Data)

revokedCerts = crl.TBSCertList.RevokedCertificates
if len(revokedCerts) != 0 {
t.Fatal("expected CRL to be empty")
}
}

Expand Down
18 changes: 8 additions & 10 deletions builtin/logical/pki/path_tidy.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (b *backend) pathTidyWrite(ctx context.Context, req *logical.Request, d *fr
b.revokeStorageLock.Lock()
defer b.revokeStorageLock.Unlock()

tidiedRevoked := false
rebuildCRL := false

revokedSerials, err := req.Storage.List(ctx, "revoked/")
if err != nil {
Expand Down Expand Up @@ -178,24 +178,22 @@ func (b *backend) pathTidyWrite(ctx context.Context, req *logical.Request, d *fr
return errwrap.Wrapf(fmt.Sprintf("unable to parse stored revoked certificate with serial %q: {{err}}", serial), err)
}

// Remove the matched certificate entries from revoked/ and
// cert/ paths. We compare against both the NotAfter time
// within the cert itself and the time from the revocation
// entry, and perform tidy if either one tells us that the
// certificate has already been revoked.
now := time.Now()
if now.After(revokedCert.NotAfter.Add(bufferDuration)) || now.After(revInfo.RevocationTimeUTC.Add(bufferDuration)) {
// Only remove the entries from revoked/ and certs/ if we're
// past its NotAfter value. This is because we use the
// information on revoked/ to build the CRL and the
// information on certs/ for lookup.
if time.Now().After(revokedCert.NotAfter.Add(bufferDuration)) {
if err := req.Storage.Delete(ctx, "revoked/"+serial); err != nil {
return errwrap.Wrapf(fmt.Sprintf("error deleting serial %q from revoked list: {{err}}", serial), err)
}
if err := req.Storage.Delete(ctx, "certs/"+serial); err != nil {
return errwrap.Wrapf(fmt.Sprintf("error deleting serial %q from store when tidying revoked: {{err}}", serial), err)
}
tidiedRevoked = true
rebuildCRL = true
}
}

if tidiedRevoked {
if rebuildCRL {
if err := buildCRL(ctx, b, req, false); err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions changelog/11367.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
pki: Only remove revoked entry for certificates during tidy if they are past their NotAfter value
```
10 changes: 6 additions & 4 deletions website/content/api-docs/secret/pki.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1572,9 +1572,11 @@ expiration time.
- `tidy_cert_store` `(bool: false)` Specifies whether to tidy up the certificate
store.

- `tidy_revoked_certs` `(bool: false)` Set to true to expire all revoked and
expired certificates, removing them both from the CRL and from storage. The
CRL will be rotated if this causes any values to be removed.
- `tidy_revoked_certs` `(bool: false)` Set to true to remove all invalid and
expired certificates from storage. A revoked storage entry is considered
invalid if the entry is empty, or the value within the entry is empty. If a
certificate is removed due to expiry, the entry will also be removed from the
CRL, and the CRL will be rotated.

- `safety_buffer` `(string: "")` Specifies A duration (given as an integer
number of seconds or a string; defaults to `72h`) used as a safety buffer to
Expand Down Expand Up @@ -1605,7 +1607,7 @@ $ curl \
# Cluster Scalability

Most non-introspection operations in the PKI secrets engine require a write to
storage, and so are forwarded to the cluster's active node for execution.
storage, and so are forwarded to the cluster's active node for execution.
This table outlines which operations can be executed on performance standbys
and thus scale horizontally.

Expand Down