open-vault/builtin/logical/pki/storage_migrations_test.go
Steven Clark da7fd8f639
Migrate existing PKI mounts that only contains a key (#16813)
* Migrate existing PKI mounts that only contains a key

- We missed testing a use-case of the migration that someone has a PKI
  mount point that generated a CSR but never called set-signed back on
  that mount point so it only contains a key.

* Add cl
2022-08-22 10:11:21 -07:00

499 lines
18 KiB
Go

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, &issuerConfigEntry{}, issuersConfig)
// 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)
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, &issuerConfigEntry{DefaultIssuerId: issuerId}, issuersConfig)
// 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 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.Nil(t, resp, "got non-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.Nil(t, resp, "got non-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/",
})
require.NoError(t, err, "error setting URL config")
require.Nil(t, resp, "got non-nil response setting URL config")
// 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")
}
// 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)
}