mirror of
https://github.com/hashicorp/vault.git
synced 2025-09-01 12:01:10 +02:00
* PKI refactoring to start breaking apart monolith into sub-packages - This was broken down by commit within enterprise for ease of review but would be too difficult to bring back individual commits back to the CE repository. (they would be squashed anyways) - This change was created by exporting a patch of the enterprise PR and applying it to CE repository * Fix TestBackend_OID_SANs to not be rely on map ordering
191 lines
5.7 KiB
Go
191 lines
5.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package issuing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/errutil"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
const (
|
|
StorageLocalCRLConfig = "crls/config"
|
|
StorageUnifiedCRLConfig = "unified-crls/config"
|
|
)
|
|
|
|
type InternalCRLConfigEntry struct {
|
|
IssuerIDCRLMap map[IssuerID]CrlID `json:"issuer_id_crl_map"`
|
|
CRLNumberMap map[CrlID]int64 `json:"crl_number_map"`
|
|
LastCompleteNumberMap map[CrlID]int64 `json:"last_complete_number_map"`
|
|
CRLExpirationMap map[CrlID]time.Time `json:"crl_expiration_map"`
|
|
LastModified time.Time `json:"last_modified"`
|
|
DeltaLastModified time.Time `json:"delta_last_modified"`
|
|
UseGlobalQueue bool `json:"cross_cluster_revocation"`
|
|
}
|
|
|
|
type CrlID string
|
|
|
|
func (p CrlID) String() string {
|
|
return string(p)
|
|
}
|
|
|
|
func GetLocalCRLConfig(ctx context.Context, s logical.Storage) (*InternalCRLConfigEntry, error) {
|
|
return _getInternalCRLConfig(ctx, s, StorageLocalCRLConfig)
|
|
}
|
|
|
|
func GetUnifiedCRLConfig(ctx context.Context, s logical.Storage) (*InternalCRLConfigEntry, error) {
|
|
return _getInternalCRLConfig(ctx, s, StorageUnifiedCRLConfig)
|
|
}
|
|
|
|
func _getInternalCRLConfig(ctx context.Context, s logical.Storage, path string) (*InternalCRLConfigEntry, error) {
|
|
entry, err := s.Get(ctx, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mapping := &InternalCRLConfigEntry{}
|
|
if entry != nil {
|
|
if err := entry.DecodeJSON(mapping); err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode cluster-local CRL configuration: %v", err)}
|
|
}
|
|
}
|
|
|
|
if len(mapping.IssuerIDCRLMap) == 0 {
|
|
mapping.IssuerIDCRLMap = make(map[IssuerID]CrlID)
|
|
}
|
|
|
|
if len(mapping.CRLNumberMap) == 0 {
|
|
mapping.CRLNumberMap = make(map[CrlID]int64)
|
|
}
|
|
|
|
if len(mapping.LastCompleteNumberMap) == 0 {
|
|
mapping.LastCompleteNumberMap = make(map[CrlID]int64)
|
|
|
|
// Since this might not exist on migration, we want to guess as
|
|
// to the last full CRL number was. This was likely the last
|
|
// Value from CRLNumberMap if it existed, since we're just adding
|
|
// the mapping here in this block.
|
|
//
|
|
// After the next full CRL build, we will have set this Value
|
|
// correctly, so it doesn't really matter in the long term if
|
|
// we're off here.
|
|
for id, number := range mapping.CRLNumberMap {
|
|
// Decrement by one, since CRLNumberMap is the future number,
|
|
// not the last built number.
|
|
mapping.LastCompleteNumberMap[id] = number - 1
|
|
}
|
|
}
|
|
|
|
if len(mapping.CRLExpirationMap) == 0 {
|
|
mapping.CRLExpirationMap = make(map[CrlID]time.Time)
|
|
}
|
|
|
|
return mapping, nil
|
|
}
|
|
|
|
func SetLocalCRLConfig(ctx context.Context, s logical.Storage, mapping *InternalCRLConfigEntry) error {
|
|
return _setInternalCRLConfig(ctx, s, mapping, StorageLocalCRLConfig)
|
|
}
|
|
|
|
func SetUnifiedCRLConfig(ctx context.Context, s logical.Storage, mapping *InternalCRLConfigEntry) error {
|
|
return _setInternalCRLConfig(ctx, s, mapping, StorageUnifiedCRLConfig)
|
|
}
|
|
|
|
func _setInternalCRLConfig(ctx context.Context, s logical.Storage, mapping *InternalCRLConfigEntry, path string) error {
|
|
if err := _cleanupInternalCRLMapping(ctx, s, mapping, path); err != nil {
|
|
return fmt.Errorf("failed to clean up internal CRL mapping: %w", err)
|
|
}
|
|
|
|
json, err := logical.StorageEntryJSON(path, mapping)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.Put(ctx, json)
|
|
}
|
|
|
|
func _cleanupInternalCRLMapping(ctx context.Context, s logical.Storage, 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 := s.Delete(ctx, crlPath); err != nil {
|
|
return fmt.Errorf("failed to delete unreferenced CRL %v: %w", id, err)
|
|
}
|
|
if err := s.Delete(ctx, 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 := s.List(ctx, 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 := s.Delete(ctx, baseCRLPath+"/"+crl); err != nil {
|
|
return fmt.Errorf("failed cleaning up orphaned CRL %v: %w", crl, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|