4dbbd3e1f8
This decreases the total time to run the test suite significantly. From the last PR, we were at 151s: > [cipherboy@xps15 pki]$ go test -count=1 github.com/hashicorp/vault/builtin/logical/pki > ok github.com/hashicorp/vault/builtin/logical/pki 151.182s Now we're around 60s: > [cipherboy@xps15 pki]$ go test -count=1 github.com/hashicorp/vault/builtin/logical/pki > ok github.com/hashicorp/vault/builtin/logical/pki 61.838s Notably, Go will correctly handle parallelizing tests across both packages and within a package, so this shouldn't really impact test runners (if they're already saturated). The only gotcha in this approach is that the call to t.Run(...) becomes effectively async; this means we either need to not mark the test as parallel or shadow any loop variables inside the scope of the loop to allow the t.Run to have the correct copy. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
417 lines
16 KiB
Go
417 lines
16 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_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)
|
|
}
|