mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
backport of commit 5f8e67d8cd (#20090)
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
committed by
GitHub
parent
f980e0b3d9
commit
3d84957788
@@ -364,7 +364,28 @@ func (cb *crlBuilder) getPresentLocalDeltaWALForClearing(sc *storageContext) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cb *crlBuilder) getPresentUnifiedDeltaWALForClearing(sc *storageContext) ([]string, error) {
|
func (cb *crlBuilder) getPresentUnifiedDeltaWALForClearing(sc *storageContext) ([]string, error) {
|
||||||
return cb._getPresentDeltaWALForClearing(sc, unifiedDeltaWALPath)
|
walClusters, err := sc.Storage.List(sc.Context, unifiedDeltaWALPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetching list of clusters with delta WAL entries: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPaths []string
|
||||||
|
for index, cluster := range walClusters {
|
||||||
|
prefix := unifiedDeltaWALPrefix + cluster
|
||||||
|
clusterPaths, err := cb._getPresentDeltaWALForClearing(sc, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetching delta WAL entries for cluster (%v / %v): %w", index, cluster, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here, we don't want to include the unifiedDeltaWALPrefix because
|
||||||
|
// clearUnifiedDeltaWAL handles that for us. Instead, just include
|
||||||
|
// the cluster identifier.
|
||||||
|
for _, clusterPath := range clusterPaths {
|
||||||
|
allPaths = append(allPaths, cluster+clusterPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allPaths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb *crlBuilder) _clearDeltaWAL(sc *storageContext, walSerials []string, path string) error {
|
func (cb *crlBuilder) _clearDeltaWAL(sc *storageContext, walSerials []string, path string) error {
|
||||||
@@ -511,49 +532,70 @@ func (cb *crlBuilder) _shouldRebuildUnifiedCRLs(sc *storageContext, override boo
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're overriding whether we should build Delta CRLs, always return
|
||||||
|
// true, even if storage errors might've happen.
|
||||||
|
if override {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch two storage entries to see if we actually need to do this
|
// Fetch two storage entries to see if we actually need to do this
|
||||||
// rebuild, given we're within the window.
|
// rebuild, given we're within the window. We need to fetch these
|
||||||
lastWALEntry, err := sc.Storage.Get(sc.Context, unifiedDeltaWALLastRevokedSerial)
|
// two entries per cluster.
|
||||||
if err != nil || !override && (lastWALEntry == nil || lastWALEntry.Value == nil) {
|
clusters, err := sc.Storage.List(sc.Context, unifiedDeltaWALPrefix)
|
||||||
// If this entry does not exist, we don't need to rebuild the
|
|
||||||
// delta WAL due to the expiration assumption above. There must
|
|
||||||
// not have been any new revocations. Since err should be nil
|
|
||||||
// in this case, we can safely return it.
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lastBuildEntry, err := sc.Storage.Get(sc.Context, unifiedDeltaWALLastBuildSerial)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, fmt.Errorf("failed to get the list of clusters having written Delta WALs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !override && lastBuildEntry != nil && lastBuildEntry.Value != nil {
|
// If any cluster tells us to rebuild, we should rebuild.
|
||||||
|
shouldRebuild := false
|
||||||
|
for index, cluster := range clusters {
|
||||||
|
prefix := unifiedDeltaWALPrefix + cluster
|
||||||
|
clusterUnifiedLastRevokedWALEntry := prefix + deltaWALLastRevokedSerialName
|
||||||
|
clusterUnifiedLastBuiltWALEntry := prefix + deltaWALLastBuildSerialName
|
||||||
|
|
||||||
|
lastWALEntry, err := sc.Storage.Get(sc.Context, clusterUnifiedLastRevokedWALEntry)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed fetching last revoked WAL entry for cluster (%v / %v): %w", index, cluster, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastWALEntry == nil || lastWALEntry.Value == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lastBuildEntry, err := sc.Storage.Get(sc.Context, clusterUnifiedLastBuiltWALEntry)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed fetching last built CRL WAL entry for cluster (%v / %v): %w", index, cluster, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastBuildEntry == nil || lastBuildEntry.Value == nil {
|
||||||
// If the last build entry doesn't exist, we still want to build a
|
// If the last build entry doesn't exist, we still want to build a
|
||||||
// new delta WAL, since this could be our very first time doing so.
|
// new delta WAL, since this could be our very first time doing so.
|
||||||
//
|
shouldRebuild = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, here, now that we know it exists, we want to check this
|
// Otherwise, here, now that we know it exists, we want to check this
|
||||||
// value against the other value. Since we previously guarded the WAL
|
// value against the other value. Since we previously guarded the WAL
|
||||||
// entry being non-empty, we're good to decode everything within this
|
// entry being non-empty, we're good to decode everything within this
|
||||||
// guard.
|
// guard.
|
||||||
var walInfo lastWALInfo
|
var walInfo lastWALInfo
|
||||||
if err := lastWALEntry.DecodeJSON(&walInfo); err != nil {
|
if err := lastWALEntry.DecodeJSON(&walInfo); err != nil {
|
||||||
return false, err
|
return false, fmt.Errorf("failed decoding last revoked WAL entry for cluster (%v / %v): %w", index, cluster, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var deltaInfo lastDeltaInfo
|
var deltaInfo lastDeltaInfo
|
||||||
if err := lastBuildEntry.DecodeJSON(&deltaInfo); err != nil {
|
if err := lastBuildEntry.DecodeJSON(&deltaInfo); err != nil {
|
||||||
return false, err
|
return false, fmt.Errorf("failed decoding last built CRL WAL entry for cluster (%v / %v): %w", index, cluster, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here, everything decoded properly and we know that no new certs
|
if walInfo.Serial != deltaInfo.Serial {
|
||||||
// have been revoked since we built this last delta CRL. We can exit
|
shouldRebuild = true
|
||||||
// without rebuilding then.
|
break
|
||||||
if walInfo.Serial == deltaInfo.Serial {
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
// No errors occurred, so return the result.
|
||||||
|
return shouldRebuild, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb *crlBuilder) rebuildDeltaCRLs(sc *storageContext, forceNew bool) error {
|
func (cb *crlBuilder) rebuildDeltaCRLs(sc *storageContext, forceNew bool) error {
|
||||||
@@ -979,8 +1021,20 @@ func revokeCert(sc *storageContext, config *crlConfig, cert *x509.Certificate) (
|
|||||||
}
|
}
|
||||||
sc.Backend.incrementTotalRevokedCertificatesCount(certsCounted, revEntry.Key)
|
sc.Backend.incrementTotalRevokedCertificatesCount(certsCounted, revEntry.Key)
|
||||||
|
|
||||||
|
// From here on out, the certificate has been revoked locally. Any other
|
||||||
|
// persistence issues might still err, but any other failure messages
|
||||||
|
// should be added as warnings to the revocation.
|
||||||
|
resp := &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"revocation_time": revInfo.RevocationTime,
|
||||||
|
"revocation_time_rfc3339": revInfo.RevocationTimeUTC.Format(time.RFC3339Nano),
|
||||||
|
"state": "revoked",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// If this flag is enabled after the fact, existing local entries will be published to
|
// If this flag is enabled after the fact, existing local entries will be published to
|
||||||
// the unified storage space through a periodic function.
|
// the unified storage space through a periodic function.
|
||||||
|
failedWritingUnifiedCRL := false
|
||||||
if config.UnifiedCRL {
|
if config.UnifiedCRL {
|
||||||
entry := &unifiedRevocationEntry{
|
entry := &unifiedRevocationEntry{
|
||||||
SerialNumber: colonSerial,
|
SerialNumber: colonSerial,
|
||||||
@@ -996,6 +1050,9 @@ func revokeCert(sc *storageContext, config *crlConfig, cert *x509.Certificate) (
|
|||||||
sc.Backend.Logger().Error("Failed to write unified revocation entry, will re-attempt later",
|
sc.Backend.Logger().Error("Failed to write unified revocation entry, will re-attempt later",
|
||||||
"serial_number", colonSerial, "error", ignoreErr)
|
"serial_number", colonSerial, "error", ignoreErr)
|
||||||
sc.Backend.unifiedTransferStatus.forceRun()
|
sc.Backend.unifiedTransferStatus.forceRun()
|
||||||
|
|
||||||
|
resp.AddWarning(fmt.Sprintf("Failed to write unified revocation entry, will re-attempt later: %v", err))
|
||||||
|
failedWritingUnifiedCRL = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1014,26 +1071,20 @@ func revokeCert(sc *storageContext, config *crlConfig, cert *x509.Certificate) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if config.EnableDelta {
|
} else if config.EnableDelta {
|
||||||
if err := writeRevocationDeltaWALs(sc, config, hyphenSerial, colonSerial); err != nil {
|
if err := writeRevocationDeltaWALs(sc, config, resp, failedWritingUnifiedCRL, hyphenSerial, colonSerial); err != nil {
|
||||||
return nil, fmt.Errorf("failed to write WAL entries for Delta CRLs: %w", err)
|
return nil, fmt.Errorf("failed to write WAL entries for Delta CRLs: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &logical.Response{
|
return resp, nil
|
||||||
Data: map[string]interface{}{
|
|
||||||
"revocation_time": revInfo.RevocationTime,
|
|
||||||
"revocation_time_rfc3339": revInfo.RevocationTimeUTC.Format(time.RFC3339Nano),
|
|
||||||
"state": "revoked",
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRevocationDeltaWALs(sc *storageContext, config *crlConfig, hyphenSerial string, colonSerial string) error {
|
func writeRevocationDeltaWALs(sc *storageContext, config *crlConfig, resp *logical.Response, failedWritingUnifiedCRL bool, hyphenSerial string, colonSerial string) error {
|
||||||
if err := writeSpecificRevocationDeltaWALs(sc, hyphenSerial, colonSerial, localDeltaWALPath); err != nil {
|
if err := writeSpecificRevocationDeltaWALs(sc, hyphenSerial, colonSerial, localDeltaWALPath); err != nil {
|
||||||
return fmt.Errorf("failed to write local delta WAL entry: %w", err)
|
return fmt.Errorf("failed to write local delta WAL entry: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.UnifiedCRL {
|
if config.UnifiedCRL && !failedWritingUnifiedCRL {
|
||||||
// We only need to write cross-cluster unified Delta WAL entries when
|
// We only need to write cross-cluster unified Delta WAL entries when
|
||||||
// it is enabled; in particular, because we rebuild CRLs when enabling
|
// it is enabled; in particular, because we rebuild CRLs when enabling
|
||||||
// this flag, any revocations that happened prior to enabling unified
|
// this flag, any revocations that happened prior to enabling unified
|
||||||
@@ -1043,13 +1094,21 @@ func writeRevocationDeltaWALs(sc *storageContext, config *crlConfig, hyphenSeria
|
|||||||
// listing for the unified CRL rebuild, this revocation will not
|
// listing for the unified CRL rebuild, this revocation will not
|
||||||
// appear on either the main or the next delta CRL, but will need to
|
// appear on either the main or the next delta CRL, but will need to
|
||||||
// wait for a subsequent complete CRL rebuild).
|
// wait for a subsequent complete CRL rebuild).
|
||||||
|
//
|
||||||
|
// Lastly, we don't attempt this if the unified CRL entry failed to
|
||||||
|
// write, as we need that entry before the delta WAL entry will make
|
||||||
|
// sense.
|
||||||
if ignoredErr := writeSpecificRevocationDeltaWALs(sc, hyphenSerial, colonSerial, unifiedDeltaWALPath); ignoredErr != nil {
|
if ignoredErr := writeSpecificRevocationDeltaWALs(sc, hyphenSerial, colonSerial, unifiedDeltaWALPath); ignoredErr != nil {
|
||||||
// Just log the error if we fail to write across clusters, a separate background
|
// Just log the error if we fail to write across clusters, a separate background
|
||||||
// thread will reattempt it later on as we have the local write done.
|
// thread will reattempt it later on as we have the local write done.
|
||||||
sc.Backend.Logger().Error("Failed to write cross-cluster delta WAL entry, will re-attempt later",
|
sc.Backend.Logger().Error("Failed to write cross-cluster delta WAL entry, will re-attempt later",
|
||||||
"serial_number", colonSerial, "error", ignoredErr)
|
"serial_number", colonSerial, "error", ignoredErr)
|
||||||
sc.Backend.unifiedTransferStatus.forceRun()
|
sc.Backend.unifiedTransferStatus.forceRun()
|
||||||
|
|
||||||
|
resp.AddWarning(fmt.Sprintf("Failed to write cross-cluster delta WAL entry, will re-attempt later: %v", ignoredErr))
|
||||||
}
|
}
|
||||||
|
} else if failedWritingUnifiedCRL {
|
||||||
|
resp.AddWarning("Skipping cross-cluster delta WAL entry as cross-cluster revocation failed to write; will re-attempt later.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1272,7 +1331,7 @@ func buildAnyCRLs(sc *storageContext, forceNew bool, isDelta bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getLastWALSerial(sc *storageContext, path string) (string, error) {
|
func getLastWALSerial(sc *storageContext, path string) (string, error) {
|
||||||
lastWALEntry, err := sc.Storage.Get(sc.Context, localDeltaWALLastRevokedSerial)
|
lastWALEntry, err := sc.Storage.Get(sc.Context, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -1435,11 +1494,23 @@ func buildAnyUnifiedCRLs(
|
|||||||
// (and potentially more) in it; when we're done writing the delta CRL,
|
// (and potentially more) in it; when we're done writing the delta CRL,
|
||||||
// we'll write this serial as a sentinel to see if we need to rebuild it
|
// we'll write this serial as a sentinel to see if we need to rebuild it
|
||||||
// in the future.
|
// in the future.
|
||||||
var lastDeltaSerial string
|
//
|
||||||
|
// We need to do this per-cluster.
|
||||||
|
lastDeltaSerial := map[string]string{}
|
||||||
if isDelta {
|
if isDelta {
|
||||||
lastDeltaSerial, err = getLastWALSerial(sc, unifiedDeltaWALLastRevokedSerial)
|
clusters, err := sc.Storage.List(sc.Context, unifiedDeltaWALPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error listing clusters for unified delta WAL building: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, cluster := range clusters {
|
||||||
|
path := unifiedDeltaWALPrefix + cluster + deltaWALLastRevokedSerialName
|
||||||
|
serial, err := getLastWALSerial(sc, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting last written Delta WAL serial for cluster (%v / %v): %w", index, cluster, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastDeltaSerial[cluster] = serial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1510,12 +1581,20 @@ func buildAnyUnifiedCRLs(
|
|||||||
// for a while.
|
// for a while.
|
||||||
sc.Backend.crlBuilder.lastDeltaRebuildCheck = time.Now()
|
sc.Backend.crlBuilder.lastDeltaRebuildCheck = time.Now()
|
||||||
|
|
||||||
if len(lastDeltaSerial) > 0 {
|
// Persist all of our known last revoked serial numbers here, as the
|
||||||
// When we have a last delta serial, write out the relevant info
|
// last seen serial during build. This will allow us to detect if any
|
||||||
// so we can skip extra CRL rebuilds.
|
// new revocations have occurred, forcing us to rebuild the delta CRL.
|
||||||
deltaInfo := lastDeltaInfo{Serial: lastDeltaSerial}
|
for cluster, serial := range lastDeltaSerial {
|
||||||
|
if len(serial) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
lastDeltaBuildEntry, err := logical.StorageEntryJSON(unifiedDeltaWALLastBuildSerial, deltaInfo)
|
// Make sure to use the cluster-specific path. Since we're on the
|
||||||
|
// active node of the primary cluster, we own this entry and can
|
||||||
|
// safely write it.
|
||||||
|
path := unifiedDeltaWALPrefix + cluster + deltaWALLastBuildSerialName
|
||||||
|
deltaInfo := lastDeltaInfo{Serial: serial}
|
||||||
|
lastDeltaBuildEntry, err := logical.StorageEntryJSON(path, deltaInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating last delta CRL rebuild serial entry: %w", err)
|
return nil, fmt.Errorf("error creating last delta CRL rebuild serial entry: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -84,10 +85,19 @@ func runUnifiedTransfer(sc *storageContext) {
|
|||||||
status.forceRerun.Store(false)
|
status.forceRerun.Store(false)
|
||||||
|
|
||||||
err = doUnifiedTransferMissingLocalSerials(sc, clusterId)
|
err = doUnifiedTransferMissingLocalSerials(sc, clusterId)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger().Error("an error occurred running unified transfer", "error", err.Error())
|
||||||
|
status.forceRerun.Store(true)
|
||||||
|
} else {
|
||||||
|
if config.EnableDelta {
|
||||||
|
err = doUnifiedTransferMissingDeltaWALSerials(sc, clusterId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Logger().Error("an error occurred running unified transfer", "error", err.Error())
|
b.Logger().Error("an error occurred running unified transfer", "error", err.Error())
|
||||||
status.forceRerun.Store(true)
|
status.forceRerun.Store(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
status.lastRun = time.Now()
|
status.lastRun = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +129,7 @@ func doUnifiedTransferMissingLocalSerials(sc *storageContext, clusterId string)
|
|||||||
err := readRevocationEntryAndTransfer(sc, serialNum)
|
err := readRevocationEntryAndTransfer(sc, serialNum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCount++
|
errCount++
|
||||||
sc.Backend.Logger().Debug("Failed transferring local revocation to unified space",
|
sc.Backend.Logger().Error("Failed transferring local revocation to unified space",
|
||||||
"serial", serialNum, "error", err)
|
"serial", serialNum, "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +142,152 @@ func doUnifiedTransferMissingLocalSerials(sc *storageContext, clusterId string)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doUnifiedTransferMissingDeltaWALSerials(sc *storageContext, clusterId string) error {
|
||||||
|
// We need to do a similar thing for Delta WAL entry certificates.
|
||||||
|
// When the delta WAL failed to write for one or more entries,
|
||||||
|
// we'll need to replicate these up to the primary cluster. When it
|
||||||
|
// has performed a new delta WAL build, it will empty storage and
|
||||||
|
// update to a last written WAL entry that exceeds what we've seen
|
||||||
|
// locally.
|
||||||
|
thisUnifiedWALEntryPath := unifiedDeltaWALPath + deltaWALLastRevokedSerialName
|
||||||
|
lastUnifiedWALEntry, err := getLastWALSerial(sc, thisUnifiedWALEntryPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch last cross-cluster unified revoked delta WAL serial number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastLocalWALEntry, err := getLastWALSerial(sc, localDeltaWALLastRevokedSerial)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch last locally revoked delta WAL serial number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now need to transfer all the entries and then write the last WAL
|
||||||
|
// entry at the end. Start by listing all certificates; any missing
|
||||||
|
// certificates will be copied over and then the WAL entry will be
|
||||||
|
// updated once.
|
||||||
|
//
|
||||||
|
// We do not delete entries either locally or remotely, as either
|
||||||
|
// cluster could've rebuilt delta CRLs with out-of-sync information,
|
||||||
|
// removing some entries (and, we cannot differentiate between these
|
||||||
|
// two cases). On next full CRL rebuild (on either cluster), the state
|
||||||
|
// should get synchronized, and future delta CRLs after this function
|
||||||
|
// returns without issue will see the remaining entries.
|
||||||
|
//
|
||||||
|
// Lastly, we need to ensure we don't accidentally write any unified
|
||||||
|
// delta WAL entries that aren't present in the main cross-cluster
|
||||||
|
// revoked storage location. This would mean the above function failed
|
||||||
|
// to copy them for some reason, despite them presumably appearing
|
||||||
|
// locally.
|
||||||
|
_unifiedWALEntries, err := sc.Storage.List(sc.Context, unifiedDeltaWALPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list cross-cluster unified delta WAL storage: %w", err)
|
||||||
|
}
|
||||||
|
unifiedWALEntries := sliceToMapKey(_unifiedWALEntries)
|
||||||
|
|
||||||
|
_unifiedRevokedSerials, err := listClusterSpecificUnifiedRevokedCerts(sc, clusterId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list cross-cluster revoked certificates: %w", err)
|
||||||
|
}
|
||||||
|
unifiedRevokedSerials := sliceToMapKey(_unifiedRevokedSerials)
|
||||||
|
|
||||||
|
localWALEntries, err := sc.Storage.List(sc.Context, localDeltaWALPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list local delta WAL storage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastUnifiedWALEntry == lastLocalWALEntry && len(_unifiedWALEntries) == len(localWALEntries) {
|
||||||
|
// Writing the last revoked WAL entry is the last thing that we do.
|
||||||
|
// Because these entries match (across clusters) and we have the same
|
||||||
|
// number of entries, assume we don't have anything to sync and exit
|
||||||
|
// early.
|
||||||
|
//
|
||||||
|
// We need both checks as, in the event of PBPWF failing and then
|
||||||
|
// returning while more revocations are happening, we could have
|
||||||
|
// been schedule to run, but then skip running (if only the first
|
||||||
|
// condition was checked) because a later revocation succeeded
|
||||||
|
// in writing a unified WAL entry, before we started replicating
|
||||||
|
// the rest back up.
|
||||||
|
//
|
||||||
|
// The downside of this approach is that, if the main cluster
|
||||||
|
// does a full rebuild in the mean time, we could re-sync more
|
||||||
|
// entries back up to the primary cluster that are already
|
||||||
|
// included in the complete CRL. Users can manually rebuild the
|
||||||
|
// full CRL (clearing these duplicate delta CRL entries) if this
|
||||||
|
// affects them.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errCount := 0
|
||||||
|
for index, serial := range localWALEntries {
|
||||||
|
if index%25 == 0 {
|
||||||
|
config, _ := sc.Backend.crlBuilder.getConfigWithUpdate(sc)
|
||||||
|
if config != nil && (!config.UnifiedCRL || !config.EnableDelta) {
|
||||||
|
return errors.New("unified or delta CRLs have been disabled after we started, stopping")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if serial == deltaWALLastBuildSerialName || serial == deltaWALLastRevokedSerialName {
|
||||||
|
// Skip our special serial numbers.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isAlreadyPresent := unifiedWALEntries[serial]
|
||||||
|
if isAlreadyPresent {
|
||||||
|
// Serial exists on both local and unified cluster. We're
|
||||||
|
// presuming we don't need to read and re-write these entries
|
||||||
|
// and that only missing entries need to be updated.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isRevokedCopied := unifiedRevokedSerials[serial]
|
||||||
|
if !isRevokedCopied {
|
||||||
|
// We need to wait here to copy over.
|
||||||
|
errCount += 1
|
||||||
|
sc.Backend.Logger().Debug("Delta WAL exists locally, but corresponding cross-cluster full revocation entry is missing; skipping", "serial", serial)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// All good: read the local entry and write to the remote variant.
|
||||||
|
localPath := localDeltaWALPath + serial
|
||||||
|
unifiedPath := unifiedDeltaWALPath + serial
|
||||||
|
|
||||||
|
entry, err := sc.Storage.Get(sc.Context, localPath)
|
||||||
|
if err != nil || entry == nil {
|
||||||
|
errCount += 1
|
||||||
|
sc.Backend.Logger().Error("Failed reading local delta WAL entry to copy to cross-cluster", "serial", serial, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Key = unifiedPath
|
||||||
|
err = sc.Storage.Put(sc.Context, entry)
|
||||||
|
if err != nil {
|
||||||
|
errCount += 1
|
||||||
|
sc.Backend.Logger().Error("Failed sync local delta WAL entry to cross-cluster unified delta WAL location", "serial", serial, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errCount > 0 {
|
||||||
|
// See note above about why we don't fail here.
|
||||||
|
sc.Backend.Logger().Warn(fmt.Sprintf("Failed transfering %d local delta WAL serials to unified storage", errCount))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything worked. Here, we can write over the delta WAL last revoked
|
||||||
|
// value. By using the earlier value, even if new revocations have
|
||||||
|
// occurred, we ensure any further missing entries can be handled in the
|
||||||
|
// next round.
|
||||||
|
lastRevSerial := lastWALInfo{Serial: lastLocalWALEntry}
|
||||||
|
lastWALEntry, err := logical.StorageEntryJSON(thisUnifiedWALEntryPath, lastRevSerial)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create cross-cluster unified last delta CRL WAL entry: %w", err)
|
||||||
|
}
|
||||||
|
if err = sc.Storage.Put(sc.Context, lastWALEntry); err != nil {
|
||||||
|
return fmt.Errorf("error saving cross-cluster unified last delta CRL WAL entry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func readRevocationEntryAndTransfer(sc *storageContext, serial string) error {
|
func readRevocationEntryAndTransfer(sc *storageContext, serial string) error {
|
||||||
hyphenSerial := normalizeSerial(serial)
|
hyphenSerial := normalizeSerial(serial)
|
||||||
revInfo, err := sc.fetchRevocationInfo(hyphenSerial)
|
revInfo, err := sc.fetchRevocationInfo(hyphenSerial)
|
||||||
|
|||||||
3
changelog/20058.txt
Normal file
3
changelog/20058.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:bug
|
||||||
|
secrets/pki: Fix building of unified delta CRLs and recovery during unified delta WAL write failures.
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user