27bb03bbc0
* adding copyright header * fix fmt and a test
180 lines
5 KiB
Go
180 lines
5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package pki
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
)
|
|
|
|
const (
|
|
minUnifiedTransferDelay = 30 * time.Minute
|
|
)
|
|
|
|
type unifiedTransferStatus struct {
|
|
isRunning atomic.Bool
|
|
lastRun time.Time
|
|
forceRerun atomic.Bool
|
|
}
|
|
|
|
func (uts *unifiedTransferStatus) forceRun() {
|
|
uts.forceRerun.Store(true)
|
|
}
|
|
|
|
func newUnifiedTransferStatus() *unifiedTransferStatus {
|
|
return &unifiedTransferStatus{}
|
|
}
|
|
|
|
// runUnifiedTransfer meant to run as a background, this will process all and
|
|
// send all missing local revocation entries to the unified space if the feature
|
|
// is enabled.
|
|
func runUnifiedTransfer(sc *storageContext) {
|
|
b := sc.Backend
|
|
status := b.unifiedTransferStatus
|
|
|
|
isPerfStandby := b.System().ReplicationState().HasState(consts.ReplicationDRSecondary | consts.ReplicationPerformanceStandby)
|
|
|
|
if isPerfStandby || b.System().LocalMount() {
|
|
// We only do this on active enterprise nodes, when we aren't a local mount
|
|
return
|
|
}
|
|
|
|
config, err := b.crlBuilder.getConfigWithUpdate(sc)
|
|
if err != nil {
|
|
b.Logger().Error("failed to retrieve crl config from storage for unified transfer background process",
|
|
"error", err)
|
|
return
|
|
}
|
|
|
|
if !status.lastRun.IsZero() {
|
|
// We have run before, we only run again if we have
|
|
// been requested to forceRerun, and we haven't run since our
|
|
// minimum delay
|
|
if !(status.forceRerun.Load() && time.Since(status.lastRun) < minUnifiedTransferDelay) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if !config.UnifiedCRL {
|
|
// Feature is disabled, no need to run
|
|
return
|
|
}
|
|
|
|
clusterId, err := b.System().ClusterID(sc.Context)
|
|
if err != nil {
|
|
b.Logger().Error("failed to fetch cluster id for unified transfer background process",
|
|
"error", err)
|
|
return
|
|
}
|
|
|
|
if !status.isRunning.CompareAndSwap(false, true) {
|
|
b.Logger().Debug("an existing unified transfer process is already running")
|
|
return
|
|
}
|
|
defer status.isRunning.Store(false)
|
|
|
|
// Reset our flag before we begin, we do this before we start as
|
|
// we can't guarantee that we can properly parse/fix the error from an
|
|
// error that comes in from the revoke API after that. This will
|
|
// force another run, which worst case, we will fix it on the next
|
|
// periodic function call that passes our min delay.
|
|
status.forceRerun.Store(false)
|
|
|
|
err = doUnifiedTransferMissingLocalSerials(sc, clusterId)
|
|
if err != nil {
|
|
b.Logger().Error("an error occurred running unified transfer", "error", err.Error())
|
|
status.forceRerun.Store(true)
|
|
}
|
|
status.lastRun = time.Now()
|
|
}
|
|
|
|
func doUnifiedTransferMissingLocalSerials(sc *storageContext, clusterId string) error {
|
|
localRevokedSerialNums, err := sc.listRevokedCerts()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(localRevokedSerialNums) == 0 {
|
|
// No local certs to transfer, no further work to do.
|
|
return nil
|
|
}
|
|
|
|
unifiedSerials, err := listClusterSpecificUnifiedRevokedCerts(sc, clusterId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
unifiedCertLookup := sliceToMapKey(unifiedSerials)
|
|
|
|
errCount := 0
|
|
for i, serialNum := range localRevokedSerialNums {
|
|
if i%25 == 0 {
|
|
config, _ := sc.Backend.crlBuilder.getConfigWithUpdate(sc)
|
|
if config != nil && !config.UnifiedCRL {
|
|
return errors.New("unified crl has been disabled after we started, stopping")
|
|
}
|
|
}
|
|
if _, ok := unifiedCertLookup[serialNum]; !ok {
|
|
err := readRevocationEntryAndTransfer(sc, serialNum)
|
|
if err != nil {
|
|
errCount++
|
|
sc.Backend.Logger().Debug("Failed transferring local revocation to unified space",
|
|
"serial", serialNum, "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if errCount > 0 {
|
|
sc.Backend.Logger().Warn(fmt.Sprintf("Failed transfering %d local serials to unified storage", errCount))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func readRevocationEntryAndTransfer(sc *storageContext, serial string) error {
|
|
hyphenSerial := normalizeSerial(serial)
|
|
revInfo, err := sc.fetchRevocationInfo(hyphenSerial)
|
|
if err != nil {
|
|
return fmt.Errorf("failed loading revocation entry for serial: %s: %w", serial, err)
|
|
}
|
|
if revInfo == nil {
|
|
sc.Backend.Logger().Debug("no certificate revocation entry for serial", "serial", serial)
|
|
return nil
|
|
}
|
|
cert, err := x509.ParseCertificate(revInfo.CertificateBytes)
|
|
if err != nil {
|
|
sc.Backend.Logger().Debug("failed parsing certificate stored in revocation entry for serial",
|
|
"serial", serial, "error", err)
|
|
return nil
|
|
}
|
|
if revInfo.CertificateIssuer == "" {
|
|
// No certificate issuer assigned to this serial yet, just drop it for now,
|
|
// as a crl rebuild/tidy needs to happen
|
|
return nil
|
|
}
|
|
|
|
revocationTime := revInfo.RevocationTimeUTC
|
|
if revInfo.RevocationTimeUTC.IsZero() {
|
|
// Legacy revocation entries only had this field and not revocationTimeUTC set...
|
|
revocationTime = time.Unix(revInfo.RevocationTime, 0)
|
|
}
|
|
|
|
if time.Now().After(cert.NotAfter) {
|
|
// ignore transferring this entry as it has already expired.
|
|
return nil
|
|
}
|
|
|
|
entry := &unifiedRevocationEntry{
|
|
SerialNumber: hyphenSerial,
|
|
CertExpiration: cert.NotAfter,
|
|
RevocationTimeUTC: revocationTime,
|
|
CertificateIssuer: revInfo.CertificateIssuer,
|
|
}
|
|
|
|
return writeUnifiedRevocationEntry(sc, entry)
|
|
}
|