mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
713 lines
18 KiB
Go
713 lines
18 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package pki
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"math/big"
|
|
mathrand "math/rand"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-test/deep"
|
|
"github.com/hashicorp/vault/api"
|
|
vaulthttp "github.com/hashicorp/vault/http"
|
|
"github.com/hashicorp/vault/sdk/helper/certutil"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/hashicorp/vault/vault"
|
|
)
|
|
|
|
func TestBackend_CA_Steps(t *testing.T) {
|
|
t.Parallel()
|
|
var b *backend
|
|
|
|
factory := func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
|
be, err := Factory(ctx, conf)
|
|
if err == nil {
|
|
b = be.(*backend)
|
|
}
|
|
return be, err
|
|
}
|
|
|
|
coreConfig := &vault.CoreConfig{
|
|
LogicalBackends: map[string]logical.Factory{
|
|
"pki": factory,
|
|
},
|
|
}
|
|
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
|
HandlerFunc: vaulthttp.Handler,
|
|
})
|
|
cluster.Start()
|
|
defer cluster.Cleanup()
|
|
|
|
client := cluster.Cores[0].Client
|
|
|
|
// Set RSA/EC CA certificates
|
|
var rsaCAKey, rsaCACert, ecCAKey, ecCACert, edCAKey, edCACert string
|
|
{
|
|
cak, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
marshaledKey, err := x509.MarshalECPrivateKey(cak)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyPEMBlock := &pem.Block{
|
|
Type: "EC PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
ecCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subjKeyID, err := certutil.GetSubjKeyID(cak)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caCertTemplate := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "root.localhost",
|
|
},
|
|
SubjectKeyId: subjKeyID,
|
|
DNSNames: []string{"root.localhost"},
|
|
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, cak.Public(), cak)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caCertPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: caBytes,
|
|
}
|
|
ecCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
|
|
|
|
rak, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
marshaledKey = x509.MarshalPKCS1PrivateKey(rak)
|
|
keyPEMBlock = &pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
rsaCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
_, err = certutil.GetSubjKeyID(rak)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, rak.Public(), rak)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caCertPEMBlock = &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: caBytes,
|
|
}
|
|
rsaCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
|
|
|
|
_, edk, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
marshaledKey, err = x509.MarshalPKCS8PrivateKey(edk)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyPEMBlock = &pem.Block{
|
|
Type: "PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
edCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
_, err = certutil.GetSubjKeyID(edk)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, edk.Public(), edk)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caCertPEMBlock = &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: caBytes,
|
|
}
|
|
edCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
|
|
}
|
|
|
|
// Setup backends
|
|
var rsaRoot, rsaInt, ecRoot, ecInt, edRoot, edInt *backend
|
|
{
|
|
if err := client.Sys().Mount("rsaroot", &api.MountInput{
|
|
Type: "pki",
|
|
Config: api.MountConfigInput{
|
|
DefaultLeaseTTL: "16h",
|
|
MaxLeaseTTL: "60h",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rsaRoot = b
|
|
|
|
if err := client.Sys().Mount("rsaint", &api.MountInput{
|
|
Type: "pki",
|
|
Config: api.MountConfigInput{
|
|
DefaultLeaseTTL: "16h",
|
|
MaxLeaseTTL: "60h",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rsaInt = b
|
|
|
|
if err := client.Sys().Mount("ecroot", &api.MountInput{
|
|
Type: "pki",
|
|
Config: api.MountConfigInput{
|
|
DefaultLeaseTTL: "16h",
|
|
MaxLeaseTTL: "60h",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ecRoot = b
|
|
|
|
if err := client.Sys().Mount("ecint", &api.MountInput{
|
|
Type: "pki",
|
|
Config: api.MountConfigInput{
|
|
DefaultLeaseTTL: "16h",
|
|
MaxLeaseTTL: "60h",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ecInt = b
|
|
|
|
if err := client.Sys().Mount("ed25519root", &api.MountInput{
|
|
Type: "pki",
|
|
Config: api.MountConfigInput{
|
|
DefaultLeaseTTL: "16h",
|
|
MaxLeaseTTL: "60h",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
edRoot = b
|
|
|
|
if err := client.Sys().Mount("ed25519int", &api.MountInput{
|
|
Type: "pki",
|
|
Config: api.MountConfigInput{
|
|
DefaultLeaseTTL: "16h",
|
|
MaxLeaseTTL: "60h",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
edInt = b
|
|
}
|
|
|
|
t.Run("teststeps", func(t *testing.T) {
|
|
t.Run("rsa", func(t *testing.T) {
|
|
t.Parallel()
|
|
subClient, err := client.Clone()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
subClient.SetToken(client.Token())
|
|
runSteps(t, rsaRoot, rsaInt, subClient, "rsaroot/", "rsaint/", rsaCACert, rsaCAKey)
|
|
})
|
|
t.Run("ec", func(t *testing.T) {
|
|
t.Parallel()
|
|
subClient, err := client.Clone()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
subClient.SetToken(client.Token())
|
|
runSteps(t, ecRoot, ecInt, subClient, "ecroot/", "ecint/", ecCACert, ecCAKey)
|
|
})
|
|
t.Run("ed25519", func(t *testing.T) {
|
|
t.Parallel()
|
|
subClient, err := client.Clone()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
subClient.SetToken(client.Token())
|
|
runSteps(t, edRoot, edInt, subClient, "ed25519root/", "ed25519int/", edCACert, edCAKey)
|
|
})
|
|
})
|
|
}
|
|
|
|
func runSteps(t *testing.T, rootB, intB *backend, client *api.Client, rootName, intName, caCert, caKey string) {
|
|
// Load CA cert/key in and ensure we can fetch it back in various formats,
|
|
// unauthenticated
|
|
{
|
|
// Attempt import but only provide one the cert; this should work.
|
|
{
|
|
_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
|
|
"pem_bundle": caCert,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// Same but with only the key
|
|
{
|
|
_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
|
|
"pem_bundle": caKey,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// Import entire CA bundle; this should work as well
|
|
{
|
|
_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
|
|
"pem_bundle": strings.Join([]string{caKey, caCert}, "\n"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
prevToken := client.Token()
|
|
client.SetToken("")
|
|
|
|
// cert/ca and issuer/default/json path
|
|
for _, path := range []string{"cert/ca", "issuer/default/json"} {
|
|
resp, err := client.Logical().Read(rootName + path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
expected := caCert
|
|
if path == "issuer/default/json" {
|
|
// Preserves the new line.
|
|
expected += "\n"
|
|
_, present := resp.Data["issuer_id"]
|
|
if !present {
|
|
t.Fatalf("expected issuer/default/json to include issuer_id")
|
|
}
|
|
_, present = resp.Data["issuer_name"]
|
|
if !present {
|
|
t.Fatalf("expected issuer/default/json to include issuer_name")
|
|
}
|
|
}
|
|
if diff := deep.Equal(resp.Data["certificate"].(string), expected); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
|
|
// ca/pem and issuer/default/pem path (raw string)
|
|
for _, path := range []string{"ca/pem", "issuer/default/pem"} {
|
|
req := &logical.Request{
|
|
Path: path,
|
|
Operation: logical.ReadOperation,
|
|
Storage: rootB.storage,
|
|
}
|
|
resp, err := rootB.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
expected := []byte(caCert)
|
|
if path == "issuer/default/pem" {
|
|
// Preserves the new line.
|
|
expected = []byte(caCert + "\n")
|
|
}
|
|
if diff := deep.Equal(resp.Data["http_raw_body"].([]byte), expected); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
if resp.Data["http_content_type"].(string) != "application/pem-certificate-chain" {
|
|
t.Fatal("wrong content type")
|
|
}
|
|
}
|
|
|
|
// ca and issuer/default/der (raw DER bytes)
|
|
for _, path := range []string{"ca", "issuer/default/der"} {
|
|
req := &logical.Request{
|
|
Path: path,
|
|
Operation: logical.ReadOperation,
|
|
Storage: rootB.storage,
|
|
}
|
|
resp, err := rootB.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
rawBytes := resp.Data["http_raw_body"].([]byte)
|
|
pemBytes := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: rawBytes,
|
|
})))
|
|
if diff := deep.Equal(pemBytes, caCert); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
if resp.Data["http_content_type"].(string) != "application/pkix-cert" {
|
|
t.Fatal("wrong content type")
|
|
}
|
|
}
|
|
|
|
client.SetToken(prevToken)
|
|
}
|
|
|
|
// Configure an expiry on the CRL and verify what comes back
|
|
{
|
|
// Set CRL config
|
|
{
|
|
_, err := client.Logical().Write(rootName+"config/crl", map[string]interface{}{
|
|
"expiry": "16h",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Verify it
|
|
{
|
|
resp, err := client.Logical().Read(rootName + "config/crl")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
if resp.Data["expiry"].(string) != "16h" {
|
|
t.Fatal("expected a 16 hour expiry")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test generating a root, an intermediate, signing it, setting signed, and
|
|
// revoking it
|
|
|
|
// We'll need this later
|
|
var intSerialNumber string
|
|
{
|
|
// First, delete the existing CA info
|
|
{
|
|
_, err := client.Logical().Delete(rootName + "root")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
var rootPEM, rootKey, rootPEMBundle string
|
|
// Test exported root generation
|
|
{
|
|
resp, err := client.Logical().Write(rootName+"root/generate/exported", map[string]interface{}{
|
|
"common_name": "Root Cert",
|
|
"ttl": "180h",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
rootPEM = resp.Data["certificate"].(string)
|
|
rootKey = resp.Data["private_key"].(string)
|
|
rootPEMBundle = strings.Join([]string{rootPEM, rootKey}, "\n")
|
|
// This is really here to keep the use checker happy
|
|
if rootPEMBundle == "" {
|
|
t.Fatal("bad root pem bundle")
|
|
}
|
|
}
|
|
|
|
var intPEM, intCSR, intKey string
|
|
// Test exported intermediate CSR generation
|
|
{
|
|
resp, err := client.Logical().Write(intName+"intermediate/generate/exported", map[string]interface{}{
|
|
"common_name": "intermediate.cert.com",
|
|
"ttl": "180h",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
intCSR = resp.Data["csr"].(string)
|
|
intKey = resp.Data["private_key"].(string)
|
|
// This is really here to keep the use checker happy
|
|
if intCSR == "" || intKey == "" {
|
|
t.Fatal("int csr or key empty")
|
|
}
|
|
}
|
|
|
|
// Test signing
|
|
{
|
|
resp, err := client.Logical().Write(rootName+"root/sign-intermediate", map[string]interface{}{
|
|
"common_name": "intermediate.cert.com",
|
|
"ttl": "10s",
|
|
"csr": intCSR,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
intPEM = resp.Data["certificate"].(string)
|
|
intSerialNumber = resp.Data["serial_number"].(string)
|
|
}
|
|
|
|
// Test setting signed
|
|
{
|
|
resp, err := client.Logical().Write(intName+"intermediate/set-signed", map[string]interface{}{
|
|
"certificate": intPEM,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
}
|
|
|
|
// Verify we can find it via the root
|
|
{
|
|
resp, err := client.Logical().Read(rootName + "cert/" + intSerialNumber)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
if resp.Data["revocation_time"].(json.Number).String() != "0" {
|
|
t.Fatal("expected a zero revocation time")
|
|
}
|
|
}
|
|
|
|
// Revoke the intermediate
|
|
{
|
|
resp, err := client.Logical().Write(rootName+"revoke", map[string]interface{}{
|
|
"serial_number": intSerialNumber,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
}
|
|
}
|
|
|
|
verifyRevocation := func(t *testing.T, serial string, shouldFind bool) {
|
|
t.Helper()
|
|
// Verify it is now revoked
|
|
{
|
|
resp, err := client.Logical().Read(rootName + "cert/" + intSerialNumber)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
switch shouldFind {
|
|
case true:
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
if resp.Data["revocation_time"].(json.Number).String() == "0" {
|
|
t.Fatal("expected a non-zero revocation time")
|
|
}
|
|
default:
|
|
if resp != nil {
|
|
t.Fatalf("expected nil response, got %#v", *resp)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fetch the CRL and make sure it shows up
|
|
for path, derPemOrJSON := range map[string]int{
|
|
"crl": 0,
|
|
"issuer/default/crl/der": 0,
|
|
"crl/pem": 1,
|
|
"issuer/default/crl/pem": 1,
|
|
"cert/crl": 2,
|
|
"issuer/default/crl": 3,
|
|
} {
|
|
req := &logical.Request{
|
|
Path: path,
|
|
Operation: logical.ReadOperation,
|
|
Storage: rootB.storage,
|
|
}
|
|
resp, err := rootB.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("nil response")
|
|
}
|
|
|
|
var crlBytes []byte
|
|
if derPemOrJSON == 2 {
|
|
// Old endpoint
|
|
crlBytes = []byte(resp.Data["certificate"].(string))
|
|
} else if derPemOrJSON == 3 {
|
|
// New endpoint
|
|
crlBytes = []byte(resp.Data["crl"].(string))
|
|
} else {
|
|
// DER or PEM
|
|
crlBytes = resp.Data["http_raw_body"].([]byte)
|
|
}
|
|
|
|
if derPemOrJSON >= 1 {
|
|
// Do for both PEM and JSON endpoints
|
|
pemBlock, _ := pem.Decode(crlBytes)
|
|
crlBytes = pemBlock.Bytes
|
|
}
|
|
|
|
certList, err := x509.ParseCRL(crlBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
switch shouldFind {
|
|
case true:
|
|
revokedList := certList.TBSCertList.RevokedCertificates
|
|
if len(revokedList) != 1 {
|
|
t.Fatalf("bad length of revoked list: %d", len(revokedList))
|
|
}
|
|
revokedString := certutil.GetHexFormatted(revokedList[0].SerialNumber.Bytes(), ":")
|
|
if revokedString != intSerialNumber {
|
|
t.Fatalf("bad revoked serial: %s", revokedString)
|
|
}
|
|
default:
|
|
revokedList := certList.TBSCertList.RevokedCertificates
|
|
if len(revokedList) != 0 {
|
|
t.Fatalf("bad length of revoked list: %d", len(revokedList))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
verifyTidyStatus := func(expectedCertStoreDeleteCount int, expectedRevokedCertDeletedCount int) {
|
|
tidyStatus, err := client.Logical().Read(rootName + "tidy-status")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if tidyStatus.Data["state"] != "Finished" {
|
|
t.Fatalf("Expected tidy operation to be finished, but tidy-status reports its state is %v", tidyStatus.Data)
|
|
}
|
|
|
|
var count int64
|
|
if count, err = tidyStatus.Data["cert_store_deleted_count"].(json.Number).Int64(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if int64(expectedCertStoreDeleteCount) != count {
|
|
t.Fatalf("Expected %d for cert_store_deleted_count, but got %d", expectedCertStoreDeleteCount, count)
|
|
}
|
|
|
|
if count, err = tidyStatus.Data["revoked_cert_deleted_count"].(json.Number).Int64(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if int64(expectedRevokedCertDeletedCount) != count {
|
|
t.Fatalf("Expected %d for revoked_cert_deleted_count, but got %d", expectedRevokedCertDeletedCount, count)
|
|
}
|
|
}
|
|
|
|
// Validate current state of revoked certificates
|
|
verifyRevocation(t, intSerialNumber, true)
|
|
|
|
// Give time for the safety buffer to pass before tidying
|
|
time.Sleep(10 * time.Second)
|
|
|
|
// Test tidying
|
|
{
|
|
// Run with a high safety buffer, nothing should happen
|
|
{
|
|
resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
|
|
"safety_buffer": "3h",
|
|
"tidy_cert_store": true,
|
|
"tidy_revoked_certs": true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected warnings")
|
|
}
|
|
|
|
// Wait a few seconds as it runs in a goroutine
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Check to make sure we still find the cert and see it on the CRL
|
|
verifyRevocation(t, intSerialNumber, true)
|
|
|
|
verifyTidyStatus(0, 0)
|
|
}
|
|
|
|
// Run with both values set false, nothing should happen
|
|
{
|
|
resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
|
|
"safety_buffer": "1s",
|
|
"tidy_cert_store": false,
|
|
"tidy_revoked_certs": false,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected warnings")
|
|
}
|
|
|
|
// Wait a few seconds as it runs in a goroutine
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Check to make sure we still find the cert and see it on the CRL
|
|
verifyRevocation(t, intSerialNumber, true)
|
|
|
|
verifyTidyStatus(0, 0)
|
|
}
|
|
|
|
// Run with a short safety buffer and both set to true, both should be cleared
|
|
{
|
|
resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
|
|
"safety_buffer": "1s",
|
|
"tidy_cert_store": true,
|
|
"tidy_revoked_certs": true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected warnings")
|
|
}
|
|
|
|
// Wait a few seconds as it runs in a goroutine
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Check to make sure we still find the cert and see it on the CRL
|
|
verifyRevocation(t, intSerialNumber, false)
|
|
|
|
verifyTidyStatus(1, 1)
|
|
}
|
|
}
|
|
}
|