// 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----- ` )