backport of commit e2ff1f1c7117574888db91b4b6027be24533d718 (#23030)
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
parent
e940a1dd82
commit
28c15e2a98
|
@ -12,12 +12,15 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
|
"github.com/hashicorp/vault/helper/constants"
|
||||||
vaulthttp "github.com/hashicorp/vault/http"
|
vaulthttp "github.com/hashicorp/vault/http"
|
||||||
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
|
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
"github.com/hashicorp/vault/vault"
|
"github.com/hashicorp/vault/vault"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1436,3 +1439,89 @@ hbiiPARizZA/Tsna/9ox1qDT
|
||||||
require.NotNil(t, resp)
|
require.NotNil(t, resp)
|
||||||
require.Empty(t, resp.Warnings)
|
require.Empty(t, resp.Warnings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCRLIssuerRemoval(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
b, s := CreateBackendWithStorage(t)
|
||||||
|
|
||||||
|
if constants.IsEnterprise {
|
||||||
|
// We don't really care about the whole cross cluster replication
|
||||||
|
// stuff, but we do want to enable unified CRLs if we can, so that
|
||||||
|
// unified CRLs get built.
|
||||||
|
_, err := CBWrite(b, s, "config/crl", map[string]interface{}{
|
||||||
|
"cross_cluster_revocation": true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "failed enabling unified CRLs on enterprise")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a single root, configure delta CRLs, and rotate CRLs to prep a
|
||||||
|
// starting state.
|
||||||
|
_, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
|
||||||
|
"common_name": "Root R1",
|
||||||
|
"key_type": "ec",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = CBWrite(b, s, "config/crl", map[string]interface{}{
|
||||||
|
"enable_delta": true,
|
||||||
|
"auto_rebuild": true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = CBRead(b, s, "crl/rotate")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// List items in storage under both CRL paths so we know what is there in
|
||||||
|
// the "good" state.
|
||||||
|
crlList, err := s.List(ctx, "crls/")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, crlList, "config")
|
||||||
|
require.Greater(t, len(crlList), 1)
|
||||||
|
|
||||||
|
unifiedCRLList, err := s.List(ctx, "unified-crls/")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, unifiedCRLList, "config")
|
||||||
|
require.Greater(t, len(unifiedCRLList), 1)
|
||||||
|
|
||||||
|
// Now, create a bunch of issuers, generate CRLs, and remove them.
|
||||||
|
var keyIDs []string
|
||||||
|
var issuerIDs []string
|
||||||
|
for i := 1; i <= 25; i++ {
|
||||||
|
resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
|
||||||
|
"common_name": fmt.Sprintf("Root X%v", i),
|
||||||
|
"key_type": "ec",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, resp)
|
||||||
|
|
||||||
|
key := string(resp.Data["key_id"].(keyID))
|
||||||
|
keyIDs = append(keyIDs, key)
|
||||||
|
issuer := string(resp.Data["issuer_id"].(issuerID))
|
||||||
|
issuerIDs = append(issuerIDs, issuer)
|
||||||
|
}
|
||||||
|
_, err = CBRead(b, s, "crl/rotate")
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, issuer := range issuerIDs {
|
||||||
|
_, err := CBDelete(b, s, "issuer/"+issuer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
for _, key := range keyIDs {
|
||||||
|
_, err := CBDelete(b, s, "key/"+key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally list storage entries again to ensure they are cleaned up.
|
||||||
|
afterCRLList, err := s.List(ctx, "crls/")
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, entry := range crlList {
|
||||||
|
require.Contains(t, afterCRLList, entry)
|
||||||
|
}
|
||||||
|
require.Equal(t, len(afterCRLList), len(crlList))
|
||||||
|
|
||||||
|
afterUnifiedCRLList, err := s.List(ctx, "unified-crls/")
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, entry := range unifiedCRLList {
|
||||||
|
require.Contains(t, afterUnifiedCRLList, entry)
|
||||||
|
}
|
||||||
|
require.Equal(t, len(afterUnifiedCRLList), len(unifiedCRLList))
|
||||||
|
}
|
||||||
|
|
|
@ -1117,6 +1117,18 @@ func (b *backend) pathDeleteIssuer(ctx context.Context, req *logical.Request, da
|
||||||
response.AddWarning(msg)
|
response.AddWarning(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, we need to rebuild both the local and the unified CRLs. This
|
||||||
|
// will free up any now unnecessary space used in both the CRL config
|
||||||
|
// and for the underlying CRL.
|
||||||
|
warnings, err := b.crlBuilder.rebuild(sc, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, warning := range warnings {
|
||||||
|
response.AddWarning(fmt.Sprintf("Warning %d during CRL rebuild: %v", index+1, warning))
|
||||||
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -930,7 +930,91 @@ func areCertificatesEqual(cert1 *x509.Certificate, cert2 *x509.Certificate) bool
|
||||||
return bytes.Equal(cert1.Raw, cert2.Raw)
|
return bytes.Equal(cert1.Raw, cert2.Raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sc *storageContext) _cleanupInternalCRLMapping(mapping *internalCRLConfigEntry, path string) error {
|
||||||
|
// Track which CRL IDs are presently referred to by issuers; any other CRL
|
||||||
|
// IDs are subject to cleanup.
|
||||||
|
//
|
||||||
|
// Unused IDs both need to be removed from this map (cleaning up the size
|
||||||
|
// of this storage entry) but also the full CRLs removed from disk.
|
||||||
|
presentMap := make(map[crlID]bool)
|
||||||
|
for _, id := range mapping.IssuerIDCRLMap {
|
||||||
|
presentMap[id] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify which CRL IDs exist and are candidates for removal;
|
||||||
|
// theoretically these three maps should be in sync, but were added
|
||||||
|
// at different times.
|
||||||
|
toRemove := make(map[crlID]bool)
|
||||||
|
for id := range mapping.CRLNumberMap {
|
||||||
|
if !presentMap[id] {
|
||||||
|
toRemove[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id := range mapping.LastCompleteNumberMap {
|
||||||
|
if !presentMap[id] {
|
||||||
|
toRemove[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id := range mapping.CRLExpirationMap {
|
||||||
|
if !presentMap[id] {
|
||||||
|
toRemove[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depending on which path we're writing this config to, we need to
|
||||||
|
// remove CRLs from the relevant folder too.
|
||||||
|
isLocal := path == storageLocalCRLConfig
|
||||||
|
baseCRLPath := "crls/"
|
||||||
|
if !isLocal {
|
||||||
|
baseCRLPath = "unified-crls/"
|
||||||
|
}
|
||||||
|
|
||||||
|
for id := range toRemove {
|
||||||
|
// Clean up space in this mapping...
|
||||||
|
delete(mapping.CRLNumberMap, id)
|
||||||
|
delete(mapping.LastCompleteNumberMap, id)
|
||||||
|
delete(mapping.CRLExpirationMap, id)
|
||||||
|
|
||||||
|
// And clean up space on disk from the fat CRL mapping.
|
||||||
|
crlPath := baseCRLPath + string(id)
|
||||||
|
deltaCRLPath := crlPath + "-delta"
|
||||||
|
if err := sc.Storage.Delete(sc.Context, crlPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete unreferenced CRL %v: %w", id, err)
|
||||||
|
}
|
||||||
|
if err := sc.Storage.Delete(sc.Context, deltaCRLPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete unreferenced delta CRL %v: %w", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lastly, some CRLs could've been partially removed from the map but
|
||||||
|
// not from disk. Check to see if we have any dangling CRLs and remove
|
||||||
|
// them too.
|
||||||
|
list, err := sc.Storage.List(sc.Context, baseCRLPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed listing all CRLs: %w", err)
|
||||||
|
}
|
||||||
|
for _, crl := range list {
|
||||||
|
if crl == "config" || strings.HasSuffix(crl, "/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if presentMap[crlID(crl)] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sc.Storage.Delete(sc.Context, baseCRLPath+"/"+crl); err != nil {
|
||||||
|
return fmt.Errorf("failed cleaning up orphaned CRL %v: %w", crl, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sc *storageContext) _setInternalCRLConfig(mapping *internalCRLConfigEntry, path string) error {
|
func (sc *storageContext) _setInternalCRLConfig(mapping *internalCRLConfigEntry, path string) error {
|
||||||
|
if err := sc._cleanupInternalCRLMapping(mapping, path); err != nil {
|
||||||
|
return fmt.Errorf("failed to clean up internal CRL mapping: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
json, err := logical.StorageEntryJSON(path, mapping)
|
json, err := logical.StorageEntryJSON(path, mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
secrets/pki: Fix removal of issuers to clean up unreferenced CRLs.
|
||||||
|
```
|
Loading…
Reference in New Issue