backport of commit 15aee2e0babebaeb7ef3c49fc6221a2bc08cd8fa (#21398)
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
parent
352865372e
commit
b0b2e07f86
|
@ -88,6 +88,22 @@ func migrateStorage(ctx context.Context, b *backend, s logical.Storage) error {
|
||||||
var keyIdentifier keyID
|
var keyIdentifier keyID
|
||||||
sc := b.makeStorageContext(ctx, s)
|
sc := b.makeStorageContext(ctx, s)
|
||||||
if migrationInfo.legacyBundle != nil {
|
if migrationInfo.legacyBundle != nil {
|
||||||
|
// When the legacy bundle still exists, there's three scenarios we
|
||||||
|
// need to worry about:
|
||||||
|
//
|
||||||
|
// 1. When we have no migration log, we definitely want to migrate.
|
||||||
|
haveNoLog := migrationInfo.migrationLog == nil
|
||||||
|
// 2. When we have an (empty) log and the version is zero, we want to
|
||||||
|
// migrate.
|
||||||
|
haveOldVersion := !haveNoLog && migrationInfo.migrationLog.MigrationVersion == 0
|
||||||
|
// 3. When we have a log and the version is at least 1 (where this
|
||||||
|
// migration was introduced), we want to run the migration again
|
||||||
|
// only if the legacy bundle hash has changed.
|
||||||
|
isCurrentOrBetterVersion := !haveNoLog && migrationInfo.migrationLog.MigrationVersion >= 1
|
||||||
|
haveChange := !haveNoLog && migrationInfo.migrationLog.Hash != migrationInfo.legacyBundleHash
|
||||||
|
haveVersionWithChange := isCurrentOrBetterVersion && haveChange
|
||||||
|
|
||||||
|
if haveNoLog || haveOldVersion || haveVersionWithChange {
|
||||||
// Generate a unique name for the migrated items in case things were to be re-migrated again
|
// Generate a unique name for the migrated items in case things were to be re-migrated again
|
||||||
// for some weird reason in the future...
|
// for some weird reason in the future...
|
||||||
migrationName := fmt.Sprintf("current-%d", time.Now().Unix())
|
migrationName := fmt.Sprintf("current-%d", time.Now().Unix())
|
||||||
|
@ -106,6 +122,7 @@ func migrateStorage(ctx context.Context, b *backend, s logical.Storage) error {
|
||||||
// the CRL to be rebuilt at a later time.
|
// the CRL to be rebuilt at a later time.
|
||||||
b.crlBuilder.requestRebuildIfActiveNode(b)
|
b.crlBuilder.requestRebuildIfActiveNode(b)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if migrationInfo.migrationLog != nil && migrationInfo.migrationLog.MigrationVersion == 1 {
|
if migrationInfo.migrationLog != nil && migrationInfo.migrationLog.MigrationVersion == 1 {
|
||||||
// We've seen a bundle with migration version 1; this means an
|
// We've seen a bundle with migration version 1; this means an
|
||||||
|
|
|
@ -777,6 +777,98 @@ func TestBackupBundle(t *testing.T) {
|
||||||
require.NotEmpty(t, keyIds)
|
require.NotEmpty(t, keyIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeletedIssuersPostMigration(t *testing.T) {
|
||||||
|
// We want to simulate the following scenario:
|
||||||
|
//
|
||||||
|
// 1.10.x: -> Create a CA.
|
||||||
|
// 1.11.0: -> Migrate to new issuer layout but version 1.
|
||||||
|
// -> Delete existing issuers, create new ones.
|
||||||
|
// (now): -> Migrate to version 2 layout, make sure we don't see
|
||||||
|
// re-migration.
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
ctx := context.Background()
|
||||||
|
b, s := CreateBackendWithStorage(t)
|
||||||
|
sc := b.makeStorageContext(ctx, s)
|
||||||
|
|
||||||
|
// Reset the version the helper above set to 1.
|
||||||
|
b.pkiStorageVersion.Store(0)
|
||||||
|
require.True(t, b.useLegacyBundleCaStorage(), "pre migration we should have been told to use legacy storage.")
|
||||||
|
|
||||||
|
// Create a legacy CA bundle and write it out.
|
||||||
|
bundle := genCertBundle(t, b, s)
|
||||||
|
json, err := logical.StorageEntryJSON(legacyCertBundlePath, bundle)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = s.Put(ctx, json)
|
||||||
|
require.NoError(t, err)
|
||||||
|
legacyContents := requireFileExists(t, sc, legacyCertBundlePath, nil)
|
||||||
|
|
||||||
|
// Do a migration; this should provision an issuer and key.
|
||||||
|
initReq := &logical.InitializationRequest{Storage: s}
|
||||||
|
err = b.initialize(ctx, initReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireFileExists(t, sc, legacyCertBundlePath, legacyContents)
|
||||||
|
issuerIds, err := sc.listIssuers()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, issuerIds)
|
||||||
|
keyIds, err := sc.listKeys()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, keyIds)
|
||||||
|
|
||||||
|
// Hack: reset the version to 1, to simulate a pre-version-2 migration
|
||||||
|
// log.
|
||||||
|
info, err := getMigrationInfo(sc.Context, sc.Storage)
|
||||||
|
require.NoError(t, err, "failed to read migration info")
|
||||||
|
info.migrationLog.MigrationVersion = 1
|
||||||
|
err = setLegacyBundleMigrationLog(sc.Context, sc.Storage, info.migrationLog)
|
||||||
|
require.NoError(t, err, "failed to write migration info")
|
||||||
|
|
||||||
|
// Now delete all issuers and keys and create some new ones.
|
||||||
|
for _, issuerId := range issuerIds {
|
||||||
|
deleted, err := sc.deleteIssuer(issuerId)
|
||||||
|
require.True(t, deleted, "expected it to be deleted")
|
||||||
|
require.NoError(t, err, "error removing issuer")
|
||||||
|
}
|
||||||
|
for _, keyId := range keyIds {
|
||||||
|
deleted, err := sc.deleteKey(keyId)
|
||||||
|
require.True(t, deleted, "expected it to be deleted")
|
||||||
|
require.NoError(t, err, "error removing key")
|
||||||
|
}
|
||||||
|
emptyIssuers, err := sc.listIssuers()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, emptyIssuers)
|
||||||
|
emptyKeys, err := sc.listKeys()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, emptyKeys)
|
||||||
|
|
||||||
|
// Create a new issuer + key.
|
||||||
|
bundle = genCertBundle(t, b, s)
|
||||||
|
_, _, err = sc.writeCaBundle(bundle, "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// List which issuers + keys we currently have.
|
||||||
|
postDeletionIssuers, err := sc.listIssuers()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, postDeletionIssuers)
|
||||||
|
postDeletionKeys, err := sc.listKeys()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, postDeletionKeys)
|
||||||
|
|
||||||
|
// Now do another migration from 1->2. This should retain the newly
|
||||||
|
// created issuers+keys, but not revive any deleted ones.
|
||||||
|
err = b.initialize(ctx, initReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireFileExists(t, sc, legacyCertBundlePath, legacyContents)
|
||||||
|
postMigrationIssuers, err := sc.listIssuers()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, postMigrationIssuers)
|
||||||
|
require.Equal(t, postMigrationIssuers, postDeletionIssuers, "regression failed: expected second migration from v1->v2 to not introduce new issuers")
|
||||||
|
postMigrationKeys, err := sc.listKeys()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, postMigrationKeys)
|
||||||
|
require.Equal(t, postMigrationKeys, postDeletionKeys, "regression failed: expected second migration from v1->v2 to not introduce new keys")
|
||||||
|
}
|
||||||
|
|
||||||
// requireFailInMigration validate that we fail the operation with the appropriate error message to the end-user
|
// requireFailInMigration validate that we fail the operation with the appropriate error message to the end-user
|
||||||
func requireFailInMigration(t *testing.T, b *backend, s logical.Storage, operation logical.Operation, path string) {
|
func requireFailInMigration(t *testing.T, b *backend, s logical.Storage, operation logical.Operation, path string) {
|
||||||
resp, err := b.HandleRequest(context.Background(), &logical.Request{
|
resp, err := b.HandleRequest(context.Background(), &logical.Request{
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
secrets/pki: Prevent deleted issuers from reappearing when migrating from a version 1 bundle to a version 2 bundle (versions including 1.13.0, 1.12.2, and 1.11.6); when managed keys were removed but referenced in the Vault 1.10 legacy CA bundle, this the error: `no managed key found with uuid`.
|
||||||
|
```
|
|
@ -43,3 +43,5 @@ vault write auth/ldap/config max_page_size=-1
|
||||||
#### Impacted Versions
|
#### Impacted Versions
|
||||||
|
|
||||||
Affects Vault 1.11.10.
|
Affects Vault 1.11.10.
|
||||||
|
|
||||||
|
@include 'pki-double-migration-bug.mdx'
|
||||||
|
|
|
@ -213,3 +213,5 @@ flag for [PKI roles](/vault/api-docs/secret/pki#createupdate-role).
|
||||||
#### Impacted Versions
|
#### Impacted Versions
|
||||||
|
|
||||||
Affects Vault 1.12.0+
|
Affects Vault 1.12.0+
|
||||||
|
|
||||||
|
@include 'pki-double-migration-bug.mdx'
|
||||||
|
|
|
@ -159,3 +159,5 @@ Affects Vault 1.13.0+
|
||||||
@include 'perf-standby-token-create-forwarding-failure.mdx'
|
@include 'perf-standby-token-create-forwarding-failure.mdx'
|
||||||
|
|
||||||
@include 'update-primary-known-issue.mdx'
|
@include 'update-primary-known-issue.mdx'
|
||||||
|
|
||||||
|
@include 'pki-double-migration-bug.mdx'
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
### PKI storage migration revives deleted issuers
|
||||||
|
|
||||||
|
Vault 1.11 introduced Storage v1, a new storage layout that supported
|
||||||
|
multiple issuers within a single mount. Bug fixes in Vault 1.11.6, 1.12.2,
|
||||||
|
and 1.13.0 corrected a write-ordering issue that lead to invalid CA chains.
|
||||||
|
Specifically, incorrectly ordered writes could fail due to load, resulting
|
||||||
|
in the mount being re-migrated next time it was loaded or silently
|
||||||
|
truncating CA chains. This collection of bug fixes introduced Storage v2.
|
||||||
|
|
||||||
|
#### Affected versions
|
||||||
|
|
||||||
|
Vault may incorrectly re-migrated legacy issuers created before Vault 1.11 that
|
||||||
|
were migrated to Storage v1 and deleted before upgrading to a Vault version with
|
||||||
|
Storage v2.
|
||||||
|
|
||||||
|
The migration fails when Vault finds managed keys associated with the legacy
|
||||||
|
issuers that were removed from the managed key repository prior to the upgrade.
|
||||||
|
|
||||||
|
The migration error appears in Vault logs as:
|
||||||
|
|
||||||
|
> Error during migration of PKI mount:
|
||||||
|
> failed to lookup public key from managed key:
|
||||||
|
> no managed key found with uuid
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Issuers created in Vault 1.11+ and direct upgrades to a Storage v2 layout are
|
||||||
|
not affected.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
The Storage v1 upgrade bug was fixed in Vault 1.14.1, 1.13.5, and 1.12.9.
|
Loading…
Reference in New Issue