open-vault/builtin/logical/pki/path_manage_keys_test.go
Alexander Scheel 4dbbd3e1f8
Make PKI tests run in parallel (#16514)
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>
2022-08-01 16:43:38 -04:00

428 lines
16 KiB
Go

package pki
import (
"context"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"testing"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
func TestPKI_PathManageKeys_GenerateInternalKeys(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
tests := []struct {
name string
keyType string
keyBits []int
wantLogicalErr bool
}{
{"all-defaults", "", []int{0}, false},
{"rsa", "rsa", []int{0, 2048, 3072, 4096}, false},
{"ec", "ec", []int{0, 224, 256, 384, 521}, false},
{"ed25519", "ed25519", []int{0}, false},
{"error-rsa", "rsa", []int{-1, 343444}, true},
{"error-ec", "ec", []int{-1, 3434324}, true},
{"error-bad-type", "dskjfkdsfjdkf", []int{0}, true},
}
for _, tt := range tests {
tt := tt
for _, keyBitParam := range tt.keyBits {
keyName := fmt.Sprintf("%s-%d", tt.name, keyBitParam)
t.Run(keyName, func(t *testing.T) {
data := make(map[string]interface{})
if tt.keyType != "" {
data["key_type"] = tt.keyType
}
if keyBitParam != 0 {
data["key_bits"] = keyBitParam
}
keyName = genUuid() + "-" + tt.keyType + "-key-name"
data["key_name"] = keyName
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/internal",
Storage: s,
Data: data,
MountPoint: "pki/",
})
require.NoError(t, err,
"Failed generating key with values key_type:%s key_bits:%d key_name:%s", tt.keyType, keyBitParam, keyName)
require.NotNil(t, resp,
"Got nil response generating key with values key_type:%s key_bits:%d key_name:%s", tt.keyType, keyBitParam, keyName)
if tt.wantLogicalErr {
require.True(t, resp.IsError(), "expected logical error but the request passed:\n%#v", resp)
} else {
require.False(t, resp.IsError(),
"Got logical error response when not expecting one, "+
"generating key with values key_type:%s key_bits:%d key_name:%s\n%s", tt.keyType, keyBitParam, keyName, resp.Error())
// Special case our all-defaults
if tt.keyType == "" {
tt.keyType = "rsa"
}
require.Equal(t, tt.keyType, resp.Data["key_type"], "key_type field contained an invalid type")
require.NotEmpty(t, resp.Data["key_id"], "returned an empty key_id field, should never happen")
require.Equal(t, keyName, resp.Data["key_name"], "key name was not processed correctly")
require.Nil(t, resp.Data["private_key"], "private_key field should not appear in internal generation type.")
}
})
}
}
}
func TestPKI_PathManageKeys_GenerateExportedKeys(t *testing.T) {
t.Parallel()
// We tested a lot of the logic above within the internal test, so just make sure we honor the exported contract
b, s := createBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/exported",
Storage: s,
Data: map[string]interface{}{
"key_type": "ec",
"key_bits": 224,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed generating exported key")
require.NotNil(t, resp, "Got nil response generating exported key")
require.Equal(t, "ec", resp.Data["key_type"], "key_type field contained an invalid type")
require.NotEmpty(t, resp.Data["key_id"], "returned an empty key_id field, should never happen")
require.Empty(t, resp.Data["key_name"], "key name should have been empty but was not")
require.NotEmpty(t, resp.Data["private_key"], "private_key field should not be empty in exported generation type.")
// Make sure we can decode our private key as expected
keyData := resp.Data["private_key"].(string)
block, rest := pem.Decode([]byte(keyData))
require.Empty(t, rest, "should not have had any trailing data")
require.NotEmpty(t, block, "failed decoding pem block")
key, err := x509.ParseECPrivateKey(block.Bytes)
require.NoError(t, err, "failed parsing pem block as ec private key")
require.Equal(t, elliptic.P224(), key.Curve, "got unexpected curve value in returned private key")
}
func TestPKI_PathManageKeys_ImportKeyBundle(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
bundle1, err := certutil.CreateKeyBundle("ec", 224, rand.Reader)
require.NoError(t, err, "failed generating an ec key bundle")
bundle2, err := certutil.CreateKeyBundle("rsa", 2048, rand.Reader)
require.NoError(t, err, "failed generating an rsa key bundle")
pem1, err := bundle1.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting ec key to pem")
pem2, err := bundle2.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting rsa key to pem")
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": pem1,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing ec key")
require.NotNil(t, resp, "Got nil response importing ec key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for ec import response was empty")
require.Equal(t, "my-ec-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.ECPrivateKey, resp.Data["key_type"])
keyId1 := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-rsa-key",
"pem_bundle": pem2,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing rsa key")
require.NotNil(t, resp, "Got nil response importing rsa key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for rsa import response was empty")
require.Equal(t, "my-rsa-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.RSAPrivateKey, resp.Data["key_type"])
keyId2 := resp.Data["key_id"].(keyID)
require.NotEqual(t, keyId1, keyId2)
// Attempt to reimport the same key with a different name.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-new-ec-key",
"pem_bundle": pem1,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing the same ec key")
require.NotNil(t, resp, "Got nil response importing the same ec key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for ec import response was empty")
// Note we should receive back the original name, not the new updated name.
require.Equal(t, "my-ec-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.ECPrivateKey, resp.Data["key_type"])
keyIdReimport := resp.Data["key_id"]
require.Equal(t, keyId1, keyIdReimport, "the re-imported key did not return the same key id")
// Make sure we can not reuse an existing name across different keys.
bundle3, err := certutil.CreateKeyBundle("ec", 224, rand.Reader)
require.NoError(t, err, "failed generating an ec key bundle")
pem3, err := bundle3.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting rsa key to pem")
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": pem3,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing the same ec key")
require.NotNil(t, resp, "Got nil response importing the same ec key")
require.True(t, resp.IsError(), "should have received an error response importing a key with a re-used name")
// Delete the key to make sure re-importing gets another ID
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId2.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting keyId 2")
require.Nil(t, resp, "Got non-nil response deleting the key: %#v", resp)
// Deleting a non-existent key should be okay...
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId2.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting keyId 2")
require.Nil(t, resp, "Got non-nil response deleting the key: %#v", resp)
// Let's reimport key 2 post-deletion to make sure we re-generate a new key id
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-rsa-key",
"pem_bundle": pem2,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing rsa key")
require.NotNil(t, resp, "Got nil response importing rsa key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for rsa import response was empty")
require.Equal(t, "my-rsa-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.RSAPrivateKey, resp.Data["key_type"])
keyId2Reimport := resp.Data["key_id"].(keyID)
require.NotEqual(t, keyId2, keyId2Reimport, "re-importing key 2 did not generate a new key id")
}
func TestPKI_PathManageKeys_DeleteDefaultKeyWarns(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/internal",
Storage: s,
Data: map[string]interface{}{"key_type": "ec"},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed generating key")
require.NotNil(t, resp, "Got nil response generating key")
require.False(t, resp.IsError(), "resp contained errors generating key: %#v", resp.Error())
keyId := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting default key")
require.NotNil(t, resp, "Got nil response deleting the default key")
require.False(t, resp.IsError(), "expected no errors deleting default key: %#v", resp.Error())
require.NotEmpty(t, resp.Warnings, "expected warnings to be populated on deleting default key")
}
func TestPKI_PathManageKeys_DeleteUsedKeyFails(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "issuers/generate/root/internal",
Storage: s,
Data: map[string]interface{}{"common_name": "test.com"},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed generating issuer")
require.NotNil(t, resp, "Got nil response generating issuer")
require.False(t, resp.IsError(), "resp contained errors generating issuer: %#v", resp.Error())
keyId := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting key used by an issuer")
require.NotNil(t, resp, "Got nil response deleting key used by an issuer")
require.True(t, resp.IsError(), "expected an error deleting key used by an issuer")
}
func TestPKI_PathManageKeys_UpdateKeyDetails(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/internal",
Storage: s,
Data: map[string]interface{}{"key_type": "ec"},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed generating key")
require.NotNil(t, resp, "Got nil response generating key")
require.False(t, resp.IsError(), "resp contained errors generating key: %#v", resp.Error())
keyId := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "key/" + keyId.String(),
Storage: s,
Data: map[string]interface{}{"key_name": "new-name"},
MountPoint: "pki/",
})
require.NoError(t, err, "failed updating key with new name")
require.NotNil(t, resp, "Got nil response updating key with new name")
require.False(t, resp.IsError(), "unexpected error updating key with new name: %#v", resp.Error())
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "key/" + keyId.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed reading key after name update")
require.NotNil(t, resp, "Got nil response reading key after name update")
require.False(t, resp.IsError(), "unexpected error reading key: %#v", resp.Error())
keyName := resp.Data["key_name"].(string)
require.Equal(t, "new-name", keyName, "failed to update key_name expected: new-name was: %s", keyName)
// Make sure we do not allow updates to invalid name values
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "key/" + keyId.String(),
Storage: s,
Data: map[string]interface{}{"key_name": "a-bad\\-name"},
MountPoint: "pki/",
})
require.NoError(t, err, "failed updating key with a bad name")
require.NotNil(t, resp, "Got nil response updating key with a bad name")
require.True(t, resp.IsError(), "expected an error updating key with a bad name, but did not get one.")
}
func TestPKI_PathManageKeys_ImportKeyBundleBadData(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": "this-is-not-a-pem-bundle",
},
MountPoint: "pki/",
})
require.NoError(t, err, "got a 500 error type response from a bad pem bundle")
require.NotNil(t, resp, "Got nil response importing a bad pem bundle")
require.True(t, resp.IsError(), "should have received an error response importing a bad pem bundle")
// Make sure we also bomb on a proper certificate
bundle := genCertBundle(t, b, s)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"pem_bundle": bundle.Certificate,
},
MountPoint: "pki/",
})
require.NoError(t, err, "got a 500 error type response from a certificate pem bundle")
require.NotNil(t, resp, "Got nil response importing a certificate bundle")
require.True(t, resp.IsError(), "should have received an error response importing a certificate pem bundle")
}
func TestPKI_PathManageKeys_ImportKeyRejectsMultipleKeys(t *testing.T) {
t.Parallel()
b, s := createBackendWithStorage(t)
bundle1, err := certutil.CreateKeyBundle("ec", 224, rand.Reader)
require.NoError(t, err, "failed generating an ec key bundle")
bundle2, err := certutil.CreateKeyBundle("rsa", 2048, rand.Reader)
require.NoError(t, err, "failed generating an rsa key bundle")
pem1, err := bundle1.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting ec key to pem")
pem2, err := bundle2.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting rsa key to pem")
importPem := pem1 + "\n" + pem2
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": importPem,
},
MountPoint: "pki/",
})
require.NoError(t, err, "got a 500 error type response from a bad pem bundle")
require.NotNil(t, resp, "Got nil response importing a bad pem bundle")
require.True(t, resp.IsError(), "should have received an error response importing a pem bundle with more than 1 key")
ctx := context.Background()
sc := b.makeStorageContext(ctx, s)
keys, _ := sc.listKeys()
for _, keyId := range keys {
id, _ := sc.fetchKeyById(keyId)
t.Logf("%s:%s", id.ID, id.Name)
}
}