mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-01 02:46:16 -05:00
90b88e1cf9
In the light of extending our eKMS support it will be helpful to have a tighter use of the word "KMS". KMS should refer to the actual component that manages keys. The keyservice, also called KMS in the constellation code, does not manage keys itself. It talks to a KMS backend, which in turn does the actual key management.
226 lines
6.6 KiB
Go
226 lines
6.6 KiB
Go
//go:build integration
|
|
|
|
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/config"
|
|
"github.com/aws/aws-sdk-go-v2/service/kms"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
kmsconfig "github.com/edgelesssys/constellation/v2/keyservice/internal/config"
|
|
"github.com/edgelesssys/constellation/v2/keyservice/internal/storage"
|
|
awsInterface "github.com/edgelesssys/constellation/v2/keyservice/kms/aws"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var defaultPolicyWithDecryption = `{
|
|
"Version" : "2012-10-17",
|
|
"Id" : "updated-policy",
|
|
"Statement" : [ {
|
|
"Sid" : "Enable IAM User Permissions",
|
|
"Effect" : "Allow",
|
|
"Principal" : {
|
|
"AWS" : "*"
|
|
},
|
|
"Action" : [
|
|
"kms:CreateAlias",
|
|
"kms:CreateKey",
|
|
"kms:Decrypt",
|
|
"kms:DeleteAlias",
|
|
"kms:DescribeKey",
|
|
"kms:Encrypt",
|
|
"kms:GenerateDataKey",
|
|
"kms:GenerateDataKeyWithoutPlaintext",
|
|
"kms:GetKeyPolicy",
|
|
"kms:GetParametersForImport",
|
|
"kms:ImportKeyMaterial",
|
|
"kms:PutKeyPolicy",
|
|
"kms:ScheduleKeyDeletion"
|
|
],
|
|
"Resource" : "*"
|
|
} ]
|
|
}`
|
|
|
|
func TestAwsStorage(t *testing.T) {
|
|
if !*runAwsStorage {
|
|
t.Skip("Skipping AWS storage test")
|
|
}
|
|
assert := assert.New(t)
|
|
require := require.New(t)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
|
defer cancel()
|
|
bucketID := strings.ToLower(addSuffix("test-bucket"))
|
|
|
|
// create bucket
|
|
store, err := storage.NewAWSS3Storage(ctx, bucketID, func(*s3.Options) {})
|
|
require.NoError(err)
|
|
|
|
testDEK1 := []byte("test DEK")
|
|
testDEK2 := []byte("more test DEK")
|
|
|
|
// request unset value
|
|
_, err = store.Get(ctx, "test:input")
|
|
assert.Error(err)
|
|
|
|
// test Put method
|
|
assert.NoError(store.Put(ctx, "volume01", testDEK1))
|
|
assert.NoError(store.Put(ctx, "volume02", testDEK2))
|
|
|
|
// make sure values have been set
|
|
val, err := store.Get(ctx, "volume01")
|
|
assert.NoError(err)
|
|
assert.Equal(testDEK1, val)
|
|
val, err = store.Get(ctx, "volume02")
|
|
assert.NoError(err)
|
|
assert.Equal(testDEK2, val)
|
|
|
|
_, err = store.Get(ctx, "invalid:key")
|
|
assert.Error(err)
|
|
assert.ErrorIs(err, storage.ErrDEKUnset)
|
|
|
|
cleanUpBucket(ctx, require, bucketID, func(*s3.Options) {})
|
|
}
|
|
|
|
func cleanUpBucket(ctx context.Context, require *require.Assertions, bucketID string, optFns ...func(*s3.Options)) {
|
|
cfg, err := config.LoadDefaultConfig(ctx)
|
|
require.NoError(err)
|
|
client := s3.NewFromConfig(cfg, optFns...)
|
|
|
|
// List all objects of the bucket
|
|
listObjectsInput := &s3.ListObjectsV2Input{
|
|
Bucket: &bucketID,
|
|
}
|
|
output, _ := client.ListObjectsV2(ctx, listObjectsInput)
|
|
var objects []string
|
|
var i int32
|
|
for i = 0; i < output.KeyCount; i++ {
|
|
objects = append(objects, *output.Contents[i].Key)
|
|
}
|
|
// Delete all objects of the bucket
|
|
require.NoError(cleanUpObjects(ctx, client, bucketID, objects))
|
|
|
|
// Delete the bucket
|
|
deleteBucketInput := &s3.DeleteBucketInput{
|
|
Bucket: &bucketID,
|
|
}
|
|
_, err = client.DeleteBucket(ctx, deleteBucketInput)
|
|
require.NoError(err)
|
|
}
|
|
|
|
func cleanUpObjects(ctx context.Context, client *s3.Client, bucketID string, objectsToDelete []string) error {
|
|
var objectsIdentifier []types.ObjectIdentifier
|
|
for _, object := range objectsToDelete {
|
|
objectsIdentifier = append(objectsIdentifier, types.ObjectIdentifier{Key: aws.String(object)})
|
|
}
|
|
deleteObjectsInput := &s3.DeleteObjectsInput{
|
|
Bucket: &bucketID,
|
|
Delete: &types.Delete{Objects: objectsIdentifier},
|
|
}
|
|
_, err := client.DeleteObjects(ctx, deleteObjectsInput)
|
|
return err
|
|
}
|
|
|
|
func TestAwsKms(t *testing.T) {
|
|
if !*runAwsKms {
|
|
t.Skip("Skipping AWS KMS test")
|
|
}
|
|
require := require.New(t)
|
|
assert := assert.New(t)
|
|
|
|
newKEKId1 := addSuffix("test-key-")
|
|
newKEKId2 := addSuffix("test-key-")
|
|
require.NotEqual(newKEKId1, newKEKId2)
|
|
var keyPolicyProducer createKeyPolicyFunc
|
|
|
|
client, err := awsInterface.New(context.Background(), &keyPolicyProducer, nil)
|
|
require.NoError(err)
|
|
|
|
privateKEK1 := []byte(strings.Repeat("1234", 8))
|
|
privateKEK2 := []byte(strings.Repeat("5678", 8))
|
|
privateKEK3 := make([]byte, 0)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
|
defer cancel()
|
|
|
|
// test setting first KEK
|
|
assert.NoError(client.CreateKEK(ctx, newKEKId1, privateKEK1))
|
|
|
|
// make sure that CreateKEK is idempotent
|
|
assert.NoError(client.CreateKEK(ctx, newKEKId1, privateKEK1))
|
|
|
|
// make sure you can not overwrite KEK with different key material
|
|
assert.NoError(client.CreateKEK(ctx, newKEKId1, privateKEK2))
|
|
|
|
// make sure that GetDEK is idempotent
|
|
volumeKey1, err := client.GetDEK(ctx, newKEKId1, "volume01", kmsconfig.SymmetricKeyLength)
|
|
require.NoError(err)
|
|
volumeKey1Copy, err := client.GetDEK(ctx, newKEKId1, "volume01", kmsconfig.SymmetricKeyLength)
|
|
require.NoError(err)
|
|
assert.Equal(volumeKey1, volumeKey1Copy)
|
|
|
|
// test setting a second DEK
|
|
volumeKey2, err := client.GetDEK(ctx, newKEKId1, "volume02", kmsconfig.SymmetricKeyLength)
|
|
require.NoError(err)
|
|
assert.NotEqual(volumeKey1, volumeKey2)
|
|
|
|
// make sure AWS KMS generates KEK when calling CreateKEK with empty key
|
|
assert.NoError(client.CreateKEK(ctx, newKEKId2, privateKEK3))
|
|
|
|
// make sure that CreateKEK is idempotent
|
|
assert.NoError(client.CreateKEK(ctx, newKEKId2, privateKEK3))
|
|
|
|
// test setting a DEK with AWS KMS generated KEK
|
|
volumeKey3, err := client.GetDEK(ctx, newKEKId2, "volume03", kmsconfig.SymmetricKeyLength)
|
|
require.NoError(err)
|
|
assert.NotEqual(volumeKey1, volumeKey3)
|
|
|
|
cleanUp(ctx, assert, require, newKEKId1)
|
|
cleanUp(ctx, assert, require, newKEKId2)
|
|
}
|
|
|
|
func cleanUp(ctx context.Context, assert *assert.Assertions, require *require.Assertions, alias string) {
|
|
cfg, err := config.LoadDefaultConfig(ctx)
|
|
require.NoError(err)
|
|
awsClient := kms.NewFromConfig(cfg)
|
|
require.NotNil(awsClient)
|
|
|
|
describeKeyInput := &kms.DescribeKeyInput{
|
|
KeyId: aws.String("alias/" + alias),
|
|
}
|
|
describeKeyOutput, err := awsClient.DescribeKey(ctx, describeKeyInput)
|
|
assert.NoError(err)
|
|
deleteAliasInput := &kms.DeleteAliasInput{
|
|
AliasName: aws.String("alias/" + alias),
|
|
}
|
|
_, err = awsClient.DeleteAlias(ctx, deleteAliasInput)
|
|
assert.NoError(err)
|
|
scheduleKeyDeletionInput := &kms.ScheduleKeyDeletionInput{
|
|
KeyId: describeKeyOutput.KeyMetadata.KeyId,
|
|
PendingWindowInDays: aws.Int32(7),
|
|
}
|
|
_, err = awsClient.ScheduleKeyDeletion(ctx, scheduleKeyDeletionInput)
|
|
assert.NoError(err)
|
|
}
|
|
|
|
type createKeyPolicyFunc struct{}
|
|
|
|
func (fn *createKeyPolicyFunc) CreateKeyPolicy(keyID string) (string, error) {
|
|
policy := defaultPolicyWithDecryption
|
|
policy = strings.Replace(policy, "<AWSAccountId>", "795746500882", 2)
|
|
return policy, nil
|
|
}
|