open-vault/builtin/logical/pki/storage_migrations_test.go
Hamid Ghaf 27bb03bbc0
adding copyright header (#19555)
* adding copyright header

* fix fmt and a test
2023-03-15 09:00:52 -07:00

891 lines
33 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package pki
import (
"context"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
func Test_migrateStorageEmptyStorage(t *testing.T) {
t.Parallel()
startTime := time.Now()
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.")
request := &logical.InitializationRequest{Storage: s}
err := b.initialize(ctx, request)
require.NoError(t, err)
issuerIds, err := sc.listIssuers()
require.NoError(t, err)
require.Empty(t, issuerIds)
keyIds, err := sc.listKeys()
require.NoError(t, err)
require.Empty(t, keyIds)
logEntry, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry)
require.Equal(t, latestMigrationVersion, logEntry.MigrationVersion)
require.True(t, len(strings.TrimSpace(logEntry.Hash)) > 0,
"Hash value (%s) should not have been empty", logEntry.Hash)
require.True(t, startTime.Before(logEntry.Created),
"created log entry time (%v) was before our start time(%v)?", logEntry.Created, startTime)
require.Empty(t, logEntry.CreatedIssuer)
require.Empty(t, logEntry.CreatedKey)
require.False(t, b.useLegacyBundleCaStorage(), "post migration we are still told to use legacy storage")
// Make sure we can re-run the migration without issues
request = &logical.InitializationRequest{Storage: s}
err = b.initialize(ctx, request)
require.NoError(t, err)
logEntry2, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry2)
// Make sure the hash and created times have not changed.
require.Equal(t, logEntry.Created, logEntry2.Created)
require.Equal(t, logEntry.Hash, logEntry2.Hash)
}
func Test_migrateStorageOnlyKey(t *testing.T) {
t.Parallel()
startTime := time.Now()
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.")
bundle := genCertBundle(t, b, s)
// Clear everything except for the key
bundle.SerialNumber = ""
bundle.CAChain = []string{}
bundle.Certificate = ""
bundle.IssuingCA = ""
json, err := logical.StorageEntryJSON(legacyCertBundlePath, bundle)
require.NoError(t, err)
err = s.Put(ctx, json)
require.NoError(t, err)
request := &logical.InitializationRequest{Storage: s}
err = b.initialize(ctx, request)
require.NoError(t, err)
issuerIds, err := sc.listIssuers()
require.NoError(t, err)
require.Equal(t, 0, len(issuerIds))
keyIds, err := sc.listKeys()
require.NoError(t, err)
require.Equal(t, 1, len(keyIds))
logEntry, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry)
require.Equal(t, latestMigrationVersion, logEntry.MigrationVersion)
require.True(t, len(strings.TrimSpace(logEntry.Hash)) > 0,
"Hash value (%s) should not have been empty", logEntry.Hash)
require.True(t, startTime.Before(logEntry.Created),
"created log entry time (%v) was before our start time(%v)?", logEntry.Created, startTime)
require.Equal(t, logEntry.CreatedIssuer, issuerID(""))
require.Equal(t, logEntry.CreatedKey, keyIds[0])
keyId := keyIds[0]
key, err := sc.fetchKeyById(keyId)
require.NoError(t, err)
require.True(t, strings.HasPrefix(key.Name, "current-"),
"expected key name to start with current- was %s", key.Name)
require.Equal(t, keyId, key.ID)
require.Equal(t, strings.TrimSpace(bundle.PrivateKey), strings.TrimSpace(key.PrivateKey))
require.Equal(t, bundle.PrivateKeyType, key.PrivateKeyType)
// Make sure we kept the old bundle
_, certBundle, err := getLegacyCertBundle(ctx, s)
require.NoError(t, err)
require.Equal(t, bundle, certBundle)
// Make sure we setup the default values
keysConfig, err := sc.getKeysConfig()
require.NoError(t, err)
require.Equal(t, &keyConfigEntry{DefaultKeyId: keyId}, keysConfig)
issuersConfig, err := sc.getIssuersConfig()
require.NoError(t, err)
require.Equal(t, issuerID(""), issuersConfig.DefaultIssuerId)
// Make sure if we attempt to re-run the migration nothing happens...
err = migrateStorage(ctx, b, s)
require.NoError(t, err)
logEntry2, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry2)
require.Equal(t, logEntry.Created, logEntry2.Created)
require.Equal(t, logEntry.Hash, logEntry2.Hash)
require.False(t, b.useLegacyBundleCaStorage(), "post migration we are still told to use legacy storage")
}
func Test_migrateStorageSimpleBundle(t *testing.T) {
t.Parallel()
startTime := time.Now()
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.")
bundle := genCertBundle(t, b, s)
json, err := logical.StorageEntryJSON(legacyCertBundlePath, bundle)
require.NoError(t, err)
err = s.Put(ctx, json)
require.NoError(t, err)
request := &logical.InitializationRequest{Storage: s}
err = b.initialize(ctx, request)
require.NoError(t, err)
issuerIds, err := sc.listIssuers()
require.NoError(t, err)
require.Equal(t, 1, len(issuerIds))
keyIds, err := sc.listKeys()
require.NoError(t, err)
require.Equal(t, 1, len(keyIds))
logEntry, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry)
require.Equal(t, latestMigrationVersion, logEntry.MigrationVersion)
require.True(t, len(strings.TrimSpace(logEntry.Hash)) > 0,
"Hash value (%s) should not have been empty", logEntry.Hash)
require.True(t, startTime.Before(logEntry.Created),
"created log entry time (%v) was before our start time(%v)?", logEntry.Created, startTime)
require.Equal(t, logEntry.CreatedIssuer, issuerIds[0])
require.Equal(t, logEntry.CreatedKey, keyIds[0])
issuerId := issuerIds[0]
keyId := keyIds[0]
issuer, err := sc.fetchIssuerById(issuerId)
require.NoError(t, err)
require.True(t, strings.HasPrefix(issuer.Name, "current-"),
"expected issuer name to start with current- was %s", issuer.Name)
require.Equal(t, certutil.ErrNotAfterBehavior, issuer.LeafNotAfterBehavior)
key, err := sc.fetchKeyById(keyId)
require.NoError(t, err)
require.True(t, strings.HasPrefix(key.Name, "current-"),
"expected key name to start with current- was %s", key.Name)
require.Equal(t, issuerId, issuer.ID)
require.Equal(t, bundle.SerialNumber, issuer.SerialNumber)
require.Equal(t, strings.TrimSpace(bundle.Certificate), strings.TrimSpace(issuer.Certificate))
require.Equal(t, keyId, issuer.KeyID)
require.Empty(t, issuer.ManualChain)
require.Equal(t, []string{bundle.Certificate + "\n"}, issuer.CAChain)
require.Equal(t, AllIssuerUsages, issuer.Usage)
require.Equal(t, certutil.ErrNotAfterBehavior, issuer.LeafNotAfterBehavior)
require.Equal(t, keyId, key.ID)
require.Equal(t, strings.TrimSpace(bundle.PrivateKey), strings.TrimSpace(key.PrivateKey))
require.Equal(t, bundle.PrivateKeyType, key.PrivateKeyType)
// Make sure we kept the old bundle
_, certBundle, err := getLegacyCertBundle(ctx, s)
require.NoError(t, err)
require.Equal(t, bundle, certBundle)
// Make sure we setup the default values
keysConfig, err := sc.getKeysConfig()
require.NoError(t, err)
require.Equal(t, &keyConfigEntry{DefaultKeyId: keyId}, keysConfig)
issuersConfig, err := sc.getIssuersConfig()
require.NoError(t, err)
require.Equal(t, issuerId, issuersConfig.DefaultIssuerId)
// Make sure if we attempt to re-run the migration nothing happens...
err = migrateStorage(ctx, b, s)
require.NoError(t, err)
logEntry2, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry2)
require.Equal(t, logEntry.Created, logEntry2.Created)
require.Equal(t, logEntry.Hash, logEntry2.Hash)
require.False(t, b.useLegacyBundleCaStorage(), "post migration we are still told to use legacy storage")
// Make sure we can re-process a migration from scratch for whatever reason
err = s.Delete(ctx, legacyMigrationBundleLogKey)
require.NoError(t, err)
err = migrateStorage(ctx, b, s)
require.NoError(t, err)
logEntry3, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry3)
require.NotEqual(t, logEntry.Created, logEntry3.Created)
require.Equal(t, logEntry.Hash, logEntry3.Hash)
}
func TestMigration_OnceChainRebuild(t *testing.T) {
t.Parallel()
ctx := context.Background()
b, s := CreateBackendWithStorage(t)
sc := b.makeStorageContext(ctx, s)
// Create a legacy CA bundle that we'll migrate to the new layout. We call
// ToParsedCertBundle just to make sure it works and to populate
// bundle.SerialNumber for us.
bundle := &certutil.CertBundle{
PrivateKeyType: certutil.RSAPrivateKey,
Certificate: migIntCA,
IssuingCA: migRootCA,
CAChain: []string{migRootCA},
PrivateKey: migIntPrivKey,
}
_, err := bundle.ToParsedCertBundle()
require.NoError(t, err)
writeLegacyBundle(t, b, s, bundle)
// Do an initial migration. Ensure we end up at least on version 2.
request := &logical.InitializationRequest{Storage: s}
err = b.initialize(ctx, request)
require.NoError(t, err)
issuerIds, err := sc.listIssuers()
require.NoError(t, err)
require.Equal(t, 2, len(issuerIds))
keyIds, err := sc.listKeys()
require.NoError(t, err)
require.Equal(t, 1, len(keyIds))
logEntry, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry)
require.GreaterOrEqual(t, logEntry.MigrationVersion, 2)
require.GreaterOrEqual(t, latestMigrationVersion, 2)
// Verify the chain built correctly: current should have a CA chain of
// length two.
//
// Afterwards, we mutate these issuers to only point at themselves and
// write back out.
var rootIssuerId issuerID
var intIssuerId issuerID
for _, issuerId := range issuerIds {
issuer, err := sc.fetchIssuerById(issuerId)
require.NoError(t, err)
require.NotNil(t, issuer)
if strings.HasPrefix(issuer.Name, "current-") {
require.Equal(t, 2, len(issuer.CAChain))
require.Equal(t, migIntCA, issuer.CAChain[0])
require.Equal(t, migRootCA, issuer.CAChain[1])
intIssuerId = issuerId
issuer.CAChain = []string{migIntCA}
err = sc.writeIssuer(issuer)
require.NoError(t, err)
} else {
require.Equal(t, 1, len(issuer.CAChain))
require.Equal(t, migRootCA, issuer.CAChain[0])
rootIssuerId = issuerId
}
}
// Reset our migration version back to one, as if this never
// happened...
logEntry.MigrationVersion = 1
err = setLegacyBundleMigrationLog(ctx, s, logEntry)
require.NoError(t, err)
b.pkiStorageVersion.Store(1)
// Re-attempt the migration by reinitializing the mount.
err = b.initialize(ctx, request)
require.NoError(t, err)
newIssuerIds, err := sc.listIssuers()
require.NoError(t, err)
require.Equal(t, 2, len(newIssuerIds))
require.Equal(t, issuerIds, newIssuerIds)
newKeyIds, err := sc.listKeys()
require.NoError(t, err)
require.Equal(t, 1, len(newKeyIds))
require.Equal(t, keyIds, newKeyIds)
logEntry, err = getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
require.NotNil(t, logEntry)
require.Equal(t, logEntry.MigrationVersion, latestMigrationVersion)
// Ensure the chains are correct on the intermediate. By using the
// issuerId saved above, this ensures we didn't change any issuerIds,
// we merely updated the existing issuers.
intIssuer, err := sc.fetchIssuerById(intIssuerId)
require.NoError(t, err)
require.NotNil(t, intIssuer)
require.Equal(t, 2, len(intIssuer.CAChain))
require.Equal(t, migIntCA, intIssuer.CAChain[0])
require.Equal(t, migRootCA, intIssuer.CAChain[1])
rootIssuer, err := sc.fetchIssuerById(rootIssuerId)
require.NoError(t, err)
require.NotNil(t, rootIssuer)
require.Equal(t, 1, len(rootIssuer.CAChain))
require.Equal(t, migRootCA, rootIssuer.CAChain[0])
}
func TestExpectedOpsWork_PreMigration(t *testing.T) {
t.Parallel()
ctx := context.Background()
b, s := CreateBackendWithStorage(t)
// 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.")
bundle := genCertBundle(t, b, s)
json, err := logical.StorageEntryJSON(legacyCertBundlePath, bundle)
require.NoError(t, err)
err = s.Put(ctx, json)
require.NoError(t, err)
// generate role
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/allow-all",
Storage: s,
Data: map[string]interface{}{
"allow_any_name": "true",
"no_store": "false",
},
MountPoint: "pki/",
})
require.NoError(t, err, "error from creating role")
require.NotNil(t, resp, "got nil response object from creating role")
// List roles
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ListOperation,
Path: "roles",
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "error from listing roles")
require.NotNil(t, resp, "got nil response object from listing roles")
require.False(t, resp.IsError(), "got error response from listing roles: %#v", resp)
require.Contains(t, resp.Data["keys"], "allow-all", "failed to list our roles")
// Read roles
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "roles/allow-all",
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "error from reading role")
require.NotNil(t, resp, "got nil response object from reading role")
require.False(t, resp.IsError(), "got error response from reading role: %#v", resp)
require.NotEmpty(t, resp.Data, "data map should not have been empty of reading role")
// Issue a cert from our legacy bundle.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "issue/allow-all",
Storage: s,
Data: map[string]interface{}{
"common_name": "test.com",
"ttl": "60s",
},
MountPoint: "pki/",
})
require.NoError(t, err, "error issue on allow-all")
require.NotNil(t, resp, "got nil response object from issue allow-all")
require.False(t, resp.IsError(), "got error response from issue on allow-all: %#v", resp)
serialNum := resp.Data["serial_number"].(string)
require.NotEmpty(t, serialNum)
// Make sure we can list
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ListOperation,
Path: "certs",
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "error listing certs")
require.NotNil(t, resp, "got nil response object from listing certs")
require.False(t, resp.IsError(), "got error response from listing certs: %#v", resp)
require.Contains(t, resp.Data["keys"], serialNum, "failed to list our cert")
// Revoke the cert now.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "revoke",
Storage: s,
Data: map[string]interface{}{
"serial_number": serialNum,
},
MountPoint: "pki/",
})
require.NoError(t, err, "error revoking cert")
require.NotNil(t, resp, "got nil response object from revoke cert")
require.False(t, resp.IsError(), "got error response from revoke cert: %#v", resp)
// Check our CRL includes the revoked cert.
resp = requestCrlFromBackend(t, s, b)
crl := parseCrlPemBytes(t, resp.Data["http_raw_body"].([]byte))
requireSerialNumberInCRL(t, crl, serialNum)
// Set CRL config
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/crl",
Storage: s,
Data: map[string]interface{}{
"expiry": "72h",
"disable": "false",
},
MountPoint: "pki/",
})
require.NoError(t, err, "error setting CRL config")
require.NotNil(t, resp, "got nil response setting CRL config")
// Set URL config
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/urls",
Storage: s,
Data: map[string]interface{}{
"ocsp_servers": []string{"https://localhost:8080"},
},
MountPoint: "pki/",
})
requireSuccessNonNilResponse(t, resp, err)
// Make sure we can fetch the old values...
for _, path := range []string{"ca/pem", "ca_chain", "cert/" + serialNum, "cert/ca", "cert/crl", "cert/ca_chain", "config/crl", "config/urls"} {
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: path,
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "error reading cert %s", path)
require.NotNil(t, resp, "got nil response object from reading cert %s", path)
require.False(t, resp.IsError(), "got error response from reading cert %s: %#v", path, resp)
}
// Sign CSR
_, csr := generateTestCsr(t, certutil.ECPrivateKey, 224)
for _, path := range []string{"sign/allow-all", "root/sign-intermediate", "sign-verbatim"} {
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: path,
Storage: s,
Data: map[string]interface{}{
"csr": csr,
},
MountPoint: "pki/",
})
require.NoError(t, err, "error signing csr from path %s", path)
require.NotNil(t, resp, "got nil response object from path %s", path)
require.NotEmpty(t, resp.Data, "data map response was empty from path %s", path)
}
// Sign self-issued
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/sign-self-issued",
Storage: s,
Data: map[string]interface{}{
"certificate": csr,
},
MountPoint: "pki/",
})
require.NoError(t, err, "error signing csr from path root/sign-self-issued")
require.NotNil(t, resp, "got nil response object from path root/sign-self-issued")
require.NotEmpty(t, resp.Data, "data map response was empty from path root/sign-self-issued")
// Delete Role
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "roles/allow-all",
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "error deleting role")
require.Nil(t, resp, "got non-nil response object from deleting role")
// Delete Root
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "root",
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "error deleting root")
require.NotNil(t, resp, "got nil response object from deleting root")
require.NotEmpty(t, resp.Warnings, "expected warnings set on delete root")
///////////////////////////////
// Legacy calls we expect to fail when in migration mode
///////////////////////////////
requireFailInMigration(t, b, s, logical.UpdateOperation, "config/ca")
requireFailInMigration(t, b, s, logical.UpdateOperation, "intermediate/generate/internal")
requireFailInMigration(t, b, s, logical.UpdateOperation, "intermediate/set-signed")
requireFailInMigration(t, b, s, logical.UpdateOperation, "root/generate/internal")
///////////////////////////////
// New apis should be unavailable
///////////////////////////////
requireFailInMigration(t, b, s, logical.ListOperation, "issuers")
requireFailInMigration(t, b, s, logical.UpdateOperation, "issuers/generate/root/internal")
requireFailInMigration(t, b, s, logical.UpdateOperation, "issuers/generate/intermediate/internal")
requireFailInMigration(t, b, s, logical.UpdateOperation, "issuers/import/cert")
requireFailInMigration(t, b, s, logical.ReadOperation, "issuer/default/json")
requireFailInMigration(t, b, s, logical.ReadOperation, "issuer/default/crl/pem")
requireFailInMigration(t, b, s, logical.UpdateOperation, "issuer/test-role")
// The following calls work as they are shared handlers with existing paths.
// requireFailInMigration(t, b, s, logical.UpdateOperation, "issuer/default/issue/test-role")
// requireFailInMigration(t, b, s, logical.UpdateOperation, "issuer/default/sign/test-role")
// requireFailInMigration(t, b, s, logical.UpdateOperation, "issuer/default/sign-verbatim")
// requireFailInMigration(t, b, s, logical.UpdateOperation, "issuer/default/sign-self-issued")
requireFailInMigration(t, b, s, logical.UpdateOperation, "root/replace")
requireFailInMigration(t, b, s, logical.UpdateOperation, "root/rotate/internal")
requireFailInMigration(t, b, s, logical.UpdateOperation, "intermediate/cross-sign")
requireFailInMigration(t, b, s, logical.UpdateOperation, "config/issuers")
requireFailInMigration(t, b, s, logical.ReadOperation, "config/issuers")
requireFailInMigration(t, b, s, logical.ListOperation, "keys")
requireFailInMigration(t, b, s, logical.UpdateOperation, "keys/generate/internal")
requireFailInMigration(t, b, s, logical.UpdateOperation, "keys/import")
requireFailInMigration(t, b, s, logical.ReadOperation, "key/default")
requireFailInMigration(t, b, s, logical.UpdateOperation, "config/keys")
requireFailInMigration(t, b, s, logical.ReadOperation, "config/keys")
}
func TestBackupBundle(t *testing.T) {
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 an empty request and tidy configuration for us.
req := &logical.Request{
Storage: s,
MountPoint: "pki/",
}
cfg := &tidyConfig{
BackupBundle: true,
IssuerSafetyBuffer: 120 * time.Second,
}
// Migration should do nothing if we're on an empty mount.
err := b.doTidyMoveCABundle(ctx, req, b.Logger(), cfg)
require.NoError(t, err)
requireFileNotExists(t, sc, legacyCertBundlePath)
requireFileNotExists(t, sc, legacyCertBundleBackupPath)
issuerIds, err := sc.listIssuers()
require.NoError(t, err)
require.Empty(t, issuerIds)
keyIds, err := sc.listKeys()
require.NoError(t, err)
require.Empty(t, keyIds)
// 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)
// Doing another tidy should maintain the status quo since we've
// still not done our migration.
err = b.doTidyMoveCABundle(ctx, req, b.Logger(), cfg)
require.NoError(t, err)
requireFileExists(t, sc, legacyCertBundlePath, legacyContents)
requireFileNotExists(t, sc, legacyCertBundleBackupPath)
issuerIds, err = sc.listIssuers()
require.NoError(t, err)
require.Empty(t, issuerIds)
keyIds, err = sc.listKeys()
require.NoError(t, err)
require.Empty(t, keyIds)
// 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)
// Doing another tidy should maintain the status quo since we've
// done our migration too recently relative to the safety buffer.
err = b.doTidyMoveCABundle(ctx, req, b.Logger(), cfg)
require.NoError(t, err)
requireFileExists(t, sc, legacyCertBundlePath, legacyContents)
requireFileNotExists(t, sc, legacyCertBundleBackupPath)
issuerIds, err = sc.listIssuers()
require.NoError(t, err)
require.NotEmpty(t, issuerIds)
keyIds, err = sc.listKeys()
require.NoError(t, err)
require.NotEmpty(t, keyIds)
// Shortening our buffer should ensure the migration occurs, removing
// the legacy bundle but creating the backup one.
time.Sleep(2 * time.Second)
cfg.IssuerSafetyBuffer = 1 * time.Second
err = b.doTidyMoveCABundle(ctx, req, b.Logger(), cfg)
require.NoError(t, err)
requireFileNotExists(t, sc, legacyCertBundlePath)
requireFileExists(t, sc, legacyCertBundleBackupPath, 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)
// A new initialization should do nothing.
err = b.initialize(ctx, initReq)
require.NoError(t, err)
requireFileNotExists(t, sc, legacyCertBundlePath)
requireFileExists(t, sc, legacyCertBundleBackupPath, legacyContents)
issuerIds, err = sc.listIssuers()
require.NoError(t, err)
require.NotEmpty(t, issuerIds)
require.Equal(t, len(issuerIds), 1)
keyIds, err = sc.listKeys()
require.NoError(t, err)
require.NotEmpty(t, keyIds)
require.Equal(t, len(keyIds), 1)
// Restoring the legacy bundles with new issuers should redo the
// migration.
newBundle := genCertBundle(t, b, s)
json, err = logical.StorageEntryJSON(legacyCertBundlePath, newBundle)
require.NoError(t, err)
err = s.Put(ctx, json)
require.NoError(t, err)
newLegacyContents := requireFileExists(t, sc, legacyCertBundlePath, nil)
// -> reinit
err = b.initialize(ctx, initReq)
require.NoError(t, err)
requireFileExists(t, sc, legacyCertBundlePath, newLegacyContents)
requireFileExists(t, sc, legacyCertBundleBackupPath, legacyContents)
issuerIds, err = sc.listIssuers()
require.NoError(t, err)
require.NotEmpty(t, issuerIds)
require.Equal(t, len(issuerIds), 2)
keyIds, err = sc.listKeys()
require.NoError(t, err)
require.NotEmpty(t, keyIds)
require.Equal(t, len(keyIds), 2)
// -> when we tidy again, we'll overwrite the old backup with the new
// one.
time.Sleep(2 * time.Second)
err = b.doTidyMoveCABundle(ctx, req, b.Logger(), cfg)
require.NoError(t, err)
requireFileNotExists(t, sc, legacyCertBundlePath)
requireFileExists(t, sc, legacyCertBundleBackupPath, newLegacyContents)
issuerIds, err = sc.listIssuers()
require.NoError(t, err)
require.NotEmpty(t, issuerIds)
keyIds, err = sc.listKeys()
require.NoError(t, err)
require.NotEmpty(t, keyIds)
// Finally, restoring the legacy bundle and re-migrating should redo
// the migration.
err = s.Put(ctx, json)
require.NoError(t, err)
requireFileExists(t, sc, legacyCertBundlePath, newLegacyContents)
requireFileExists(t, sc, legacyCertBundleBackupPath, newLegacyContents)
// -> overwrite the version and re-migrate
logEntry, err := getLegacyBundleMigrationLog(ctx, s)
require.NoError(t, err)
logEntry.MigrationVersion = 0
err = setLegacyBundleMigrationLog(ctx, s, logEntry)
require.NoError(t, err)
err = b.initialize(ctx, initReq)
require.NoError(t, err)
requireFileExists(t, sc, legacyCertBundlePath, newLegacyContents)
requireFileExists(t, sc, legacyCertBundleBackupPath, newLegacyContents)
issuerIds, err = sc.listIssuers()
require.NoError(t, err)
require.NotEmpty(t, issuerIds)
require.Equal(t, len(issuerIds), 2)
keyIds, err = sc.listKeys()
require.NoError(t, err)
require.NotEmpty(t, keyIds)
require.Equal(t, len(keyIds), 2)
// -> Re-tidy should remove the legacy one.
time.Sleep(2 * time.Second)
err = b.doTidyMoveCABundle(ctx, req, b.Logger(), cfg)
require.NoError(t, err)
requireFileNotExists(t, sc, legacyCertBundlePath)
requireFileExists(t, sc, legacyCertBundleBackupPath, newLegacyContents)
issuerIds, err = sc.listIssuers()
require.NoError(t, err)
require.NotEmpty(t, issuerIds)
keyIds, err = sc.listKeys()
require.NoError(t, err)
require.NotEmpty(t, keyIds)
}
// 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) {
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: operation,
Path: path,
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "error from op:%s path:%s", operation, path)
require.NotNil(t, resp, "got nil response from op:%s path:%s", operation, path)
require.True(t, resp.IsError(), "error flag was not set from op:%s path:%s resp: %#v", operation, path, resp)
require.Contains(t, resp.Error().Error(), "migration has completed",
"error message did not contain migration test for op:%s path:%s resp: %#v", operation, path, resp)
}
func requireFileNotExists(t *testing.T, sc *storageContext, path string) {
t.Helper()
entry, err := sc.Storage.Get(sc.Context, path)
require.NoError(t, err)
if entry != nil {
require.Empty(t, entry.Value)
} else {
require.Empty(t, entry)
}
}
func requireFileExists(t *testing.T, sc *storageContext, path string, contents []byte) []byte {
t.Helper()
entry, err := sc.Storage.Get(sc.Context, path)
require.NoError(t, err)
require.NotNil(t, entry)
require.NotEmpty(t, entry.Value)
if contents != nil {
require.Equal(t, entry.Value, contents)
}
return entry.Value
}
// Keys to simulate an intermediate CA mount with also-imported root (parent).
const (
migIntPrivKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqu88Jcct/EyT8gDF+jdWuAwFplvanQ7KXAO5at58G6Y39UUz
fwnMS3P3VRBUoV5BDX+13wI2ldskbTKITsl6IXBPXUz0sKrdEKzXRVY4D6P2JR7W
YO1IUytfTgR+3F4sotFNQB++3ivT66AYLW7lOkoa+5lxsPM/oJ82DOlD2uGtDVTU
gQy1zugMBgPDlj+8tB562J9MTIdcKe9JpYrN0eO+aHzhbfvaSpScU4aZBgkS0kDv
8G4FxVfrBSDeD/JjCWaC48rLdgei1YrY0NFuw/8p/nPfA9vf2AtHMsWZRSwukQfq
I5HhQu+0OHQy3NWqXaBPzJNu3HnpKLykPHW7sQIDAQABAoIBAHNJy/2G66MhWx98
Ggt7S4fyw9TCWx5XHXEWKfbEfFyBrXhF5kemqh2x5319+DamRaX/HwF8kqhcF6N2
06ygAzmOcFjzUI3fkB5xFPh1AHa8FYZP2DOjloZR2IPcUFv9QInINRwszSU31kUz
w1rRUtYPqUdM5Pt99Mo219O5eMSlGtPKXm09uDAR8ZPuUx4jwGw90pSgeRB1Bg7X
Dt3YXx3X+OOs3Hbir1VDLSqCuy825l6Kn79h3eB8LAi+FUwCBvnTqyOEWyH2XjgP
z+tbz7lwnhGeKtxUl6Jb3m3SHtXpylot/4fwPisRV/9vaEDhVjKTmySH1WM+TRNR
CQLCJekCgYEA3b67DBhAYsFFdUd/4xh4QhHBanOcepV1CwaRln+UUjw1618ZEsTS
DKb9IS72C+ukUusGhQqxjFJlhOdXeMXpEbnEUY3PlREevWwm3bVAxtoAVRcmkQyK
PM4Oj9ODi2z8Cds0NvEXdX69uVutcbvm/JRZr/dsERWcLsfwdV/QqYcCgYEAxVce
d4ylsqORLm0/gcLnEyB9zhEPwmiJe1Yj5sH7LhGZ6JtLCqbOJO4jXmIzCrkbGyuf
BA/U7klc6jSprkBMgYhgOIuaULuFJvtKzJUzoATGFqX4r8WJm2ZycXgooAwZq6SZ
ySXOuQe9V7hlpI0fJfNhw+/HIjivL1jrnjBoXwcCgYEAtTv6LLx1g0Frv5scj0Ok
pntUlei/8ADPlJ9dxp+nXj8P4rvrBkgPVX/2S3TSbJO/znWA8qP20TVW+/UIrRE0
mOQ37F/3VWKUuUT3zyUhOGVc+C7fupWBNolDpZG+ZepBZNzgJDeQcNuRvTmM3PQy
qiWl2AhlLuF2sVWA1q3lIWkCgYEAnuHWgNA3dE1nDWceE351hxvIzklEU/TQhAHF
o/uYHO5E6VdmoqvMG0W0KkCL8d046rZDMAUDHdrpOROvbcENF9lSBxS26LshqFH4
ViDmULanOgLk57f2Y6ynBZ6Frt4vKNe8jYuoFacale67vzFz251JoHSD8pSKz2cb
ROCal68CgYA51hKqvki4r5rmS7W/Yvc3x3Wc0wpDEHTgLMoH+EV7AffJ8dy0/+po
AHK0nnRU63++1JmhQczBR0yTI6PUyeegEBk/d5CgFlY7UJQMTFPsMsiuM0Xw5nAv
KMPykK01D28UAkUxhwF7CqFrwwEv9GislgjewbdF5Za176+EuMEwIw==
-----END RSA PRIVATE KEY-----
`
migIntCA = `-----BEGIN CERTIFICATE-----
MIIDHTCCAgWgAwIBAgIUfxlNBmrI7jsgH2Sdle1nVTqn5YQwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAxMHUm9vdCBYMTAeFw0yMjExMDIxMjI2MjhaFw0yMjEyMDQx
MjI2NThaMBoxGDAWBgNVBAMTD0ludGVybWVkaWF0ZSBSMTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKrvPCXHLfxMk/IAxfo3VrgMBaZb2p0OylwDuWre
fBumN/VFM38JzEtz91UQVKFeQQ1/td8CNpXbJG0yiE7JeiFwT11M9LCq3RCs10VW
OA+j9iUe1mDtSFMrX04EftxeLKLRTUAfvt4r0+ugGC1u5TpKGvuZcbDzP6CfNgzp
Q9rhrQ1U1IEMtc7oDAYDw5Y/vLQeetifTEyHXCnvSaWKzdHjvmh84W372kqUnFOG
mQYJEtJA7/BuBcVX6wUg3g/yYwlmguPKy3YHotWK2NDRbsP/Kf5z3wPb39gLRzLF
mUUsLpEH6iOR4ULvtDh0MtzVql2gT8yTbtx56Si8pDx1u7ECAwEAAaNjMGEwDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFusWj3piAiY
CR7tszR6uNYSMLe2MB8GA1UdIwQYMBaAFMNRNkLozstIhNhXCefi+WnaQApbMA0G
CSqGSIb3DQEBCwUAA4IBAQCmH852E/pDGBhf2VI1JAPZy9VYaRkKoqn4+5R1Gnoq
b90zhdCGueIm/usC1wAa0OOn7+xdQXFNfeI8UUB9w10q0QnM/A/G2v8UkdlLPPQP
zPjIYLalOOIOHf8hU2O5lwj0IA4JwjwDQ4xj69eX/N+x2LEI7SHyVVUZWAx0Y67a
QdyubpIJZlW/PI7kMwGyTx3tdkZxk1nTNtf/0nKvNuXKKcVzBCEMfvXyx4LFEM+U
nc2vdWN7PAoXcjUbxD3ZNGinr7mSBpQg82+nur/8yuSwu6iHomnfGxjUsEHic2GC
ja9siTbR+ONvVb4xUjugN/XmMSSaZnxig2vM9xcV8OMG
-----END CERTIFICATE-----
`
migRootCA = `-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAwIBAgIURDTnXp8u78jWMe770Jj6Ac1paxkwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAxMHUm9vdCBYMTAeFw0yMjExMDIxMjI0NTVaFw0yMjEyMDQx
MjI1MjRaMBIxEDAOBgNVBAMTB1Jvb3QgWDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQC/+dh/o1qKTOua/OkHRMIvHiyBxjjoqrLqFSBYhjYKs+alA0qS
lLVzNqIKU8jm3fT73orx7yk/6acWaEYv/6owMaUn51xwS3gQhTHdFR/fLJwXnu2O
PZNqAs6tjAM3Q08aqR0qfxnjDvcgO7TOWSyOvVT2cTRK+uKYzxJEY52BDMUbp+iC
WJdXca9UwKRzi2wFqGliDycYsBBt/tr8tHSbTSZ5Qx6UpFrKpjZn+sT5KhKUlsdd
BYFmRegc0wXq4/kRjum0oEUigUMlHADIEhRasyXPEKa19sGP8nAZfo/hNOusGhj7
z7UPA0Cbe2uclpYPxsKgvcqQmgKugqKLL305AgMBAAGjYzBhMA4GA1UdDwEB/wQE
AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTDUTZC6M7LSITYVwnn4vlp
2kAKWzAfBgNVHSMEGDAWgBTDUTZC6M7LSITYVwnn4vlp2kAKWzANBgkqhkiG9w0B
AQsFAAOCAQEAu7qdM1Li6V6iDCPpLg5zZReRtcxhUdwb5Xn4sDa8GJCy35f1voew
n0TQgM3Uph5x/djCR/Sj91MyAJ1/Q1PQQTyKGyUjSHvkcOBg628IAnLthn8Ua1fL
oQC/F/mlT1Yv+/W8eNPtD453/P0z8E0xMT5K3kpEDW/6K9RdHZlDJMW/z3UJ+4LN
6ONjIBmgffmLz9sVMpgCFyL7+w3W01bGP7w5AfKj2duoVG/Ekf2yUwmm6r9NgTQ1
oke0ShbZuMocwO8anq7k0R42FoluH3ipv9Qzzhsy+KdK5/fW5oqy1tKFaZsc67Q6
0UmD9DiDpCtn2Wod3nwxn0zW5HvDAWuDwg==
-----END CERTIFICATE-----
`
)