Add new API to PKI to list revoked certificates (#17779)

* Add new API to PKI to list revoked certificates

 - A new API that will return the list of serial numbers of
   revoked certificates on the local cluster.

* Add cl

* PR feedback
This commit is contained in:
Steven Clark 2022-11-03 14:17:17 -04:00 committed by GitHub
parent 4e122214f7
commit 419ba9159c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 158 additions and 0 deletions

View File

@ -134,6 +134,7 @@ func Backend(conf *logical.BackendConfig) *backend {
pathRotateDeltaCRL(&b), pathRotateDeltaCRL(&b),
pathRevoke(&b), pathRevoke(&b),
pathRevokeWithKey(&b), pathRevokeWithKey(&b),
pathListCertsRevoked(&b),
pathTidy(&b), pathTidy(&b),
pathTidyCancel(&b), pathTidyCancel(&b),
pathTidyStatus(&b), pathTidyStatus(&b),

View File

@ -5889,6 +5889,81 @@ func TestPKI_EmptyCRLConfigUpgraded(t *testing.T) {
require.Equal(t, resp.Data["delta_rebuild_interval"], defaultCrlConfig.DeltaRebuildInterval) require.Equal(t, resp.Data["delta_rebuild_interval"], defaultCrlConfig.DeltaRebuildInterval)
} }
func TestPKI_ListRevokedCerts(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
// Test empty cluster
resp, err := CBList(b, s, "certs/revoked")
requireSuccessNonNilResponse(t, resp, err, "failed listing empty cluster")
require.Empty(t, resp.Data, "response map contained data that we did not expect")
// Set up a mount that we can revoke under (We will create 3 leaf certs, 2 of which will be revoked)
resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "test.com",
"key_type": "ec",
})
requireSuccessNonNilResponse(t, resp, err, "error generating root CA")
requireFieldsSetInResp(t, resp, "serial_number")
issuerSerial := resp.Data["serial_number"]
resp, err = CBWrite(b, s, "roles/test", map[string]interface{}{
"allowed_domains": "test.com",
"allow_subdomains": "true",
"max_ttl": "1h",
})
requireSuccessNilResponse(t, resp, err, "error setting up pki role")
resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{
"common_name": "test1.test.com",
})
requireSuccessNonNilResponse(t, resp, err, "error issuing cert 1")
requireFieldsSetInResp(t, resp, "serial_number")
serial1 := resp.Data["serial_number"]
resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{
"common_name": "test2.test.com",
})
requireSuccessNonNilResponse(t, resp, err, "error issuing cert 2")
requireFieldsSetInResp(t, resp, "serial_number")
serial2 := resp.Data["serial_number"]
resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{
"common_name": "test3.test.com",
})
requireSuccessNonNilResponse(t, resp, err, "error issuing cert 2")
requireFieldsSetInResp(t, resp, "serial_number")
serial3 := resp.Data["serial_number"]
resp, err = CBWrite(b, s, "revoke", map[string]interface{}{"serial_number": serial1})
requireSuccessNonNilResponse(t, resp, err, "error revoking cert 1")
resp, err = CBWrite(b, s, "revoke", map[string]interface{}{"serial_number": serial2})
requireSuccessNonNilResponse(t, resp, err, "error revoking cert 2")
// Test that we get back the expected revoked serial numbers.
resp, err = CBList(b, s, "certs/revoked")
requireSuccessNonNilResponse(t, resp, err, "failed listing revoked certs")
requireFieldsSetInResp(t, resp, "keys")
revokedKeys := resp.Data["keys"].([]string)
require.Contains(t, revokedKeys, serial1)
require.Contains(t, revokedKeys, serial2)
require.Equal(t, 2, len(revokedKeys), "Expected 2 revoked entries got %d: %v", len(revokedKeys), revokedKeys)
// Test that listing our certs returns a different response
resp, err = CBList(b, s, "certs")
requireSuccessNonNilResponse(t, resp, err, "failed listing written certs")
requireFieldsSetInResp(t, resp, "keys")
certKeys := resp.Data["keys"].([]string)
require.Contains(t, certKeys, serial1)
require.Contains(t, certKeys, serial2)
require.Contains(t, certKeys, serial3)
require.Contains(t, certKeys, issuerSerial)
require.Equal(t, 4, len(certKeys), "Expected 4 cert entries got %d: %v", len(certKeys), certKeys)
}
var ( var (
initTest sync.Once initTest sync.Once
rsaCAKey string rsaCAKey string

View File

@ -18,6 +18,21 @@ import (
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
) )
func pathListCertsRevoked(b *backend) *framework.Path {
return &framework.Path{
Pattern: "certs/revoked/?$",
Operations: map[logical.Operation]framework.OperationHandler{
logical.ListOperation: &framework.PathOperation{
Callback: b.pathListRevokedCertsHandler,
},
},
HelpSynopsis: pathListRevokedHelpSyn,
HelpDescription: pathListRevokedHelpDesc,
}
}
func pathRevoke(b *backend) *framework.Path { func pathRevoke(b *backend) *framework.Path {
return &framework.Path{ return &framework.Path{
Pattern: `revoke`, Pattern: `revoke`,
@ -466,6 +481,22 @@ func (b *backend) pathRotateDeltaCRLRead(ctx context.Context, req *logical.Reque
return resp, nil return resp, nil
} }
func (b *backend) pathListRevokedCertsHandler(ctx context.Context, request *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
sc := b.makeStorageContext(ctx, request.Storage)
revokedCerts, err := sc.listRevokedCerts()
if err != nil {
return nil, err
}
// Normalize serial back to a format people are expecting.
for i, serial := range revokedCerts {
revokedCerts[i] = denormalizeSerial(serial)
}
return logical.ListResponse(revokedCerts), nil
}
const pathRevokeHelpSyn = ` const pathRevokeHelpSyn = `
Revoke a certificate by serial number or with explicit certificate. Revoke a certificate by serial number or with explicit certificate.
@ -493,3 +524,11 @@ Force a rebuild of the delta CRL.
const pathRotateDeltaCRLHelpDesc = ` const pathRotateDeltaCRLHelpDesc = `
Force a rebuild of the delta CRL. This can be used to force an update of the otherwise periodically-rebuilt delta CRLs. Force a rebuild of the delta CRL. This can be used to force an update of the otherwise periodically-rebuilt delta CRLs.
` `
const pathListRevokedHelpSyn = `
List all revoked serial numbers within the local cluster
`
const pathListRevokedHelpDesc = `
Returns a list of serial numbers for revoked certificates in the local cluster.
`

View File

@ -1216,3 +1216,12 @@ func (sc *storageContext) writeAutoTidyConfig(config *tidyConfig) error {
return sc.Storage.Put(sc.Context, entry) return sc.Storage.Put(sc.Context, entry)
} }
func (sc *storageContext) listRevokedCerts() ([]string, error) {
list, err := sc.Storage.List(sc.Context, revokedPath)
if err != nil {
return nil, fmt.Errorf("failed listing revoked certs: %w", err)
}
return list, err
}

3
changelog/17779.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
secrets/pki: Add a new API that returns the serial numbers of revoked certificates on the local cluster
```

View File

@ -29,6 +29,7 @@ update your API calls accordingly.
- [Sign Verbatim](#sign-verbatim) - [Sign Verbatim](#sign-verbatim)
- [Revoke Certificate](#revoke-certificate) - [Revoke Certificate](#revoke-certificate)
- [Revoke Certificate with Private Key](#revoke-certificate-with-private-key) - [Revoke Certificate with Private Key](#revoke-certificate-with-private-key)
- [List Revoked Certificates](#list-revoked-certificates)
- [Accessing Authority Information](#accessing-authority-information) - [Accessing Authority Information](#accessing-authority-information)
- [List Issuers](#list-issuers) - [List Issuers](#list-issuers)
- [Read Issuer Certificate](#read-issuer-certificate) - [Read Issuer Certificate](#read-issuer-certificate)
@ -958,6 +959,36 @@ $ curl \
} }
``` ```
### List Revoked Certificates
This endpoint returns a list of serial numbers that have been revoked on the local cluster.
| Method | Path |
|:-------|:------------------|
| `LIST` | `/certs/revoked` |
#### Sample Request
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
--request LIST \
http://127.0.0.1:8200/v1/pki/certs/revoked
```
#### Sample Response
```json
{
"data": {
"keys": [
"3d:80:91:c3:c2:34:3b:81:69:3d:92:a3:80:69:db:53:04:26:ab:b4"
]
}
}
```
--- ---
## Accessing Authority Information ## Accessing Authority Information