27bb03bbc0
* adding copyright header * fix fmt and a test
218 lines
6.6 KiB
Go
218 lines
6.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package pki
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/certutil"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
// This allows us to record the version of the migration code within the log entry
|
|
// in case we find out in the future that something was horribly wrong with the migration,
|
|
// and we need to perform it again...
|
|
const (
|
|
latestMigrationVersion = 2
|
|
legacyBundleShimID = issuerID("legacy-entry-shim-id")
|
|
legacyBundleShimKeyID = keyID("legacy-entry-shim-key-id")
|
|
)
|
|
|
|
type legacyBundleMigrationLog struct {
|
|
Hash string `json:"hash"`
|
|
Created time.Time `json:"created"`
|
|
CreatedIssuer issuerID `json:"issuer_id"`
|
|
CreatedKey keyID `json:"key_id"`
|
|
MigrationVersion int `json:"migrationVersion"`
|
|
}
|
|
|
|
type migrationInfo struct {
|
|
isRequired bool
|
|
legacyBundle *certutil.CertBundle
|
|
legacyBundleHash string
|
|
migrationLog *legacyBundleMigrationLog
|
|
}
|
|
|
|
func getMigrationInfo(ctx context.Context, s logical.Storage) (migrationInfo, error) {
|
|
migrationInfo := migrationInfo{
|
|
isRequired: false,
|
|
legacyBundle: nil,
|
|
legacyBundleHash: "",
|
|
migrationLog: nil,
|
|
}
|
|
|
|
var err error
|
|
_, migrationInfo.legacyBundle, err = getLegacyCertBundle(ctx, s)
|
|
if err != nil {
|
|
return migrationInfo, err
|
|
}
|
|
|
|
migrationInfo.migrationLog, err = getLegacyBundleMigrationLog(ctx, s)
|
|
if err != nil {
|
|
return migrationInfo, err
|
|
}
|
|
|
|
migrationInfo.legacyBundleHash, err = computeHashOfLegacyBundle(migrationInfo.legacyBundle)
|
|
if err != nil {
|
|
return migrationInfo, err
|
|
}
|
|
|
|
// Even if there isn't anything to migrate, we always want to write out the log entry
|
|
// as that will trigger the secondary clusters to toggle/wake up
|
|
if (migrationInfo.migrationLog == nil) ||
|
|
(migrationInfo.migrationLog.Hash != migrationInfo.legacyBundleHash) ||
|
|
(migrationInfo.migrationLog.MigrationVersion != latestMigrationVersion) {
|
|
migrationInfo.isRequired = true
|
|
}
|
|
|
|
return migrationInfo, nil
|
|
}
|
|
|
|
func migrateStorage(ctx context.Context, b *backend, s logical.Storage) error {
|
|
migrationInfo, err := getMigrationInfo(ctx, s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !migrationInfo.isRequired {
|
|
// No migration was deemed to be required.
|
|
return nil
|
|
}
|
|
|
|
var issuerIdentifier issuerID
|
|
var keyIdentifier keyID
|
|
sc := b.makeStorageContext(ctx, s)
|
|
if migrationInfo.legacyBundle != nil {
|
|
// Generate a unique name for the migrated items in case things were to be re-migrated again
|
|
// for some weird reason in the future...
|
|
migrationName := fmt.Sprintf("current-%d", time.Now().Unix())
|
|
|
|
b.Logger().Info("performing PKI migration to new keys/issuers layout")
|
|
anIssuer, aKey, err := sc.writeCaBundle(migrationInfo.legacyBundle, migrationName, migrationName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.Logger().Info("Migration generated the following ids and set them as defaults",
|
|
"issuer id", anIssuer.ID, "key id", aKey.ID)
|
|
issuerIdentifier = anIssuer.ID
|
|
keyIdentifier = aKey.ID
|
|
|
|
// Since we do not have all the mount information available we must schedule
|
|
// the CRL to be rebuilt at a later time.
|
|
b.crlBuilder.requestRebuildIfActiveNode(b)
|
|
}
|
|
|
|
if migrationInfo.migrationLog != nil && migrationInfo.migrationLog.MigrationVersion == 1 {
|
|
// We've seen a bundle with migration version 1; this means an
|
|
// earlier version of the code ran which didn't have the fix for
|
|
// correct write order in rebuildIssuersChains(...). Rather than
|
|
// having every user read the migrated active issuer and see if
|
|
// their chains need rebuilding, we'll schedule a one-off chain
|
|
// migration here.
|
|
b.Logger().Info(fmt.Sprintf("%v: performing maintenance rebuild of ca_chains", b.backendUUID))
|
|
if err := sc.rebuildIssuersChains(nil); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// We always want to write out this log entry as the secondary clusters leverage this path to wake up
|
|
// if they were upgraded prior to the primary cluster's migration occurred.
|
|
err = setLegacyBundleMigrationLog(ctx, s, &legacyBundleMigrationLog{
|
|
Hash: migrationInfo.legacyBundleHash,
|
|
Created: time.Now(),
|
|
CreatedIssuer: issuerIdentifier,
|
|
CreatedKey: keyIdentifier,
|
|
MigrationVersion: latestMigrationVersion,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.Logger().Info(fmt.Sprintf("%v: succeeded in migrating to issuer storage version %v", b.backendUUID, latestMigrationVersion))
|
|
|
|
return nil
|
|
}
|
|
|
|
func computeHashOfLegacyBundle(bundle *certutil.CertBundle) (string, error) {
|
|
hasher := sha256.New()
|
|
// Generate an empty hash if the bundle does not exist.
|
|
if bundle != nil {
|
|
// We only hash the main certificate and the certs within the CAChain,
|
|
// assuming that any sort of change that occurred would have influenced one of those two fields.
|
|
if _, err := hasher.Write([]byte(bundle.Certificate)); err != nil {
|
|
return "", err
|
|
}
|
|
for _, cert := range bundle.CAChain {
|
|
if _, err := hasher.Write([]byte(cert)); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
}
|
|
return hex.EncodeToString(hasher.Sum(nil)), nil
|
|
}
|
|
|
|
func getLegacyBundleMigrationLog(ctx context.Context, s logical.Storage) (*legacyBundleMigrationLog, error) {
|
|
entry, err := s.Get(ctx, legacyMigrationBundleLogKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if entry == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
lbm := &legacyBundleMigrationLog{}
|
|
err = entry.DecodeJSON(lbm)
|
|
if err != nil {
|
|
// If we can't decode our bundle, lets scrap it and assume a blank value,
|
|
// re-running the migration will at most bring back an older certificate/private key
|
|
return nil, nil
|
|
}
|
|
return lbm, nil
|
|
}
|
|
|
|
func setLegacyBundleMigrationLog(ctx context.Context, s logical.Storage, lbm *legacyBundleMigrationLog) error {
|
|
json, err := logical.StorageEntryJSON(legacyMigrationBundleLogKey, lbm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.Put(ctx, json)
|
|
}
|
|
|
|
func getLegacyCertBundle(ctx context.Context, s logical.Storage) (*issuerEntry, *certutil.CertBundle, error) {
|
|
entry, err := s.Get(ctx, legacyCertBundlePath)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if entry == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
cb := &certutil.CertBundle{}
|
|
err = entry.DecodeJSON(cb)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Fake a storage entry with backwards compatibility in mind.
|
|
issuer := &issuerEntry{
|
|
ID: legacyBundleShimID,
|
|
KeyID: legacyBundleShimKeyID,
|
|
Name: "legacy-entry-shim",
|
|
Certificate: cb.Certificate,
|
|
CAChain: cb.CAChain,
|
|
SerialNumber: cb.SerialNumber,
|
|
LeafNotAfterBehavior: certutil.ErrNotAfterBehavior,
|
|
}
|
|
issuer.Usage.ToggleUsage(AllIssuerUsages)
|
|
|
|
return issuer, cb, nil
|
|
}
|