open-vault/builtin/logical/transit/path_restore_test.go

261 lines
6.9 KiB
Go

package transit
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/vault/helper/testhelpers"
"github.com/hashicorp/vault/sdk/logical"
)
func TestTransit_Restore(t *testing.T) {
// Test setup:
// - Create a key
// - Configure it to be exportable, allowing deletion, and backups
// - Capture backup
// - Delete key
// - Run test cases
//
// Each test case should start with no key present. If the 'Seed' parameter is
// in the struct, we'll start by restoring it (without force) to run that test
// as if the key already existed
keyType := "aes256-gcm96"
b, s := createBackendWithStorage(t)
keyName := testhelpers.RandomWithPrefix("my-key")
// Create a key
keyReq := &logical.Request{
Path: "keys/" + keyName,
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"type": keyType,
"exportable": true,
},
}
resp, err := b.HandleRequest(context.Background(), keyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
// Configure the key to allow its deletion and backup
configReq := &logical.Request{
Path: fmt.Sprintf("keys/%s/config", keyName),
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"deletion_allowed": true,
"allow_plaintext_backup": true,
},
}
resp, err = b.HandleRequest(context.Background(), configReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
// Take a backup of the key
backupReq := &logical.Request{
Path: "backup/" + keyName,
Operation: logical.ReadOperation,
Storage: s,
}
resp, err = b.HandleRequest(context.Background(), backupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
backupKey := resp.Data["backup"].(string)
if backupKey == "" {
t.Fatal("failed to get a backup")
}
// Delete the key to start test cases with clean slate
keyReq.Operation = logical.DeleteOperation
resp, err = b.HandleRequest(context.Background(), keyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
// helper func to get a pointer value for a boolean
boolPtr := func(b bool) *bool {
return &b
}
keyExitsError := fmt.Errorf("key %q already exists", keyName)
testCases := []struct {
Name string
// Seed dermines if we start the test by restoring the initial backup we
// took, to test a restore operation based on the key existing or not
Seed bool
// Force is a pointer to differentiate between default false and given false
Force *bool
// The error we expect, if any
ExpectedErr error
// RestoreName is used to restore the key to a differnt name
RestoreName string
}{
{
// key does not already exist
Name: "Default restore",
},
{
// key already exists
Name: "Restore-without-force",
Seed: true,
ExpectedErr: keyExitsError,
},
{
// key already exists, use force to force a restore
Name: "Restore-with-force",
Seed: true,
Force: boolPtr(true),
},
{
// using force shouldn't matter if the key doesn't exist
Name: "Restore-with-force-no-seed",
Force: boolPtr(true),
},
{
// key already exists, restore to new name
Name: "Restore-new-name",
Seed: true,
RestoreName: "new-key",
},
{
// key already exists, restore to bad path, should error
Name: "Restore-new-name-bad-path",
Seed: true,
RestoreName: "sub/path/new-key",
ExpectedErr: ErrInvalidKeyName,
},
{
// using force shouldn't matter if the restore key name is different
Name: "Restore-with-force-seed-new-name",
Seed: true,
Force: boolPtr(true),
RestoreName: "other-key",
},
{
// using force shouldn't matter if the restore key name is different
Name: "Restore-with-out-force-seed-new-name",
Seed: true,
Force: boolPtr(false),
RestoreName: "other-key",
},
{
// using force shouldn't matter if the key doesn't exist
Name: "Restore-force-false",
Force: boolPtr(false),
},
{
// using false force should still error
Name: "Restore-force-false",
Seed: true,
Force: boolPtr(false),
ExpectedErr: keyExitsError,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
var resp *logical.Response
var err error
if tc.Seed {
// restore our key to test a pre-existing key
seedRestoreReq := &logical.Request{
Path: "restore",
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"backup": backupKey,
},
}
resp, err := b.HandleRequest(context.Background(), seedRestoreReq)
if resp != nil && resp.IsError() {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
if err != nil && tc.ExpectedErr == nil {
t.Fatalf("did not expect an error in SeedKey restore: %s", err)
}
}
restorePath := "restore"
if tc.RestoreName != "" {
restorePath = fmt.Sprintf("%s/%s", restorePath, tc.RestoreName)
}
restoreReq := &logical.Request{
Path: restorePath,
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"backup": backupKey,
},
}
if tc.Force != nil {
restoreReq.Data["force"] = *tc.Force
}
resp, err = b.HandleRequest(context.Background(), restoreReq)
if resp != nil && resp.IsError() {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
if err == nil && tc.ExpectedErr != nil {
t.Fatalf("expected an error, but got none")
}
if err != nil && tc.ExpectedErr == nil {
t.Fatalf("unexpected error:%s", err)
}
if err != nil && tc.ExpectedErr != nil {
if err.Error() != tc.ExpectedErr.Error() {
t.Fatalf("expected error: (%s), got: (%s)", tc.ExpectedErr.Error(), err.Error())
}
}
readKeyName := keyName
if tc.RestoreName != "" {
readKeyName = tc.RestoreName
}
// read the key and make sure it's there
readReq := &logical.Request{
Path: "keys/" + readKeyName,
Operation: logical.ReadOperation,
Storage: s,
}
resp, _ = b.HandleRequest(context.Background(), readReq)
if resp != nil && resp.IsError() {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
if tc.ExpectedErr == nil && resp == nil {
t.Fatal("expected to find a key, but got none")
}
// cleanup / delete key after each run
keyReq.Operation = logical.DeleteOperation
resp, err = b.HandleRequest(context.Background(), keyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
// cleanup / delete restore key after each run, if it was created
if tc.RestoreName != "" && tc.ExpectedErr == nil {
readReq.Operation = logical.DeleteOperation
resp, err = b.HandleRequest(context.Background(), readReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v\nerr: %v", resp, err)
}
}
})
}
}