test: validate ipv6 compatibility (#50)

* test: validate ipv6 compatability

* test: update test to include checking against SBC state
This commit is contained in:
Damian Debkowski authored and GitHub committed 2024-09-25 12:04:00 -07:00
1 parent 5f96422fbf
commit 7caff84c06
5 files changed
+239 -25

No files matched your search

+112 -7
View File
@@ -17,12 +17,16 @@ import (
"github.com/hashicorp/boundary-plugin-aws/plugin/service/storage" "github.com/hashicorp/boundary-plugin-aws/plugin/service/storage"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
) )
// NOTE: this test does not vaildate against dynamic credentials because
// this credential type can only be tested on an EC2 instance.
func TestStoragePlugin(t *testing.T) { func TestStoragePlugin(t *testing.T) {
region := os.Getenv("AWS_REGION") region := os.Getenv("AWS_REGION")
if region == "" { if region == "" {
@@ -75,6 +79,12 @@ func TestStoragePlugin(t *testing.T) {
iamSecretAccessKeyMissingPutObject, err := tf.GetOutputString("iam_secret_access_key_missing_put_obj") iamSecretAccessKeyMissingPutObject, err := tf.GetOutputString("iam_secret_access_key_missing_put_obj")
require.NoError(err) require.NoError(err)
iamAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_access_key_missing_delete_obj")
require.NoError(err)
iamSecretAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_secret_access_key_missing_delete_obj")
require.NoError(err)
// Start the workflow now. Set up the storage bucket. Note that this // Start the workflow now. Set up the storage bucket. Note that this
// will cause the state to go out of drift above in the sense that // will cause the state to go out of drift above in the sense that
// the access key ID/secret access key will no longer be valid. We // the access key ID/secret access key will no longer be valid. We
@@ -121,7 +131,7 @@ func TestStoragePlugin(t *testing.T) {
testPluginOnDeleteStorageBucket(ctx, t, p, bucketName, region, keyid, secret, true) testPluginOnDeleteStorageBucket(ctx, t, p, bucketName, region, keyid, secret, true)
// ******************** // ********************
// * Object Methods: PutObject, GetObject, HeadObject // * Object Methods: PutObject, GetObject, HeadObject, DeleteObjects
// ******************** // ********************
// //
// Reassign the keyid and secret first. // Reassign the keyid and secret first.
@@ -132,11 +142,59 @@ func TestStoragePlugin(t *testing.T) {
// * Validate Permissions // * Validate Permissions
// ******************** // ********************
// //
var expectedSBCState *pb.StorageBucketCredentialState
// Validate error is returned for missing get object permission // Validate error is returned for missing get object permission
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingGetObject, iamSecretAccessKeyMissingGetObject) expectedSBCState = &pb.StorageBucketCredentialState{
State: &pb.Permissions{
Write: &pb.Permission{
State: pb.StateType_STATE_TYPE_OK,
},
Read: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
},
Delete: &pb.Permission{
State: pb.StateType_STATE_TYPE_OK,
},
},
}
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingGetObject, iamSecretAccessKeyMissingGetObject, expectedSBCState)
// Validate error is returned for missing put object permission // Validate error is returned for missing put object permission
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingPutObject, iamSecretAccessKeyMissingPutObject) expectedSBCState = &pb.StorageBucketCredentialState{
State: &pb.Permissions{
Write: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
},
Read: &pb.Permission{
// Since we cannot write an object to S3,
// we cannot test against the ability to read from S3.
State: pb.StateType_STATE_TYPE_UNKNOWN,
},
Delete: &pb.Permission{
// We can still try deleting an object that does
// not exist in the S3 bucket to validate access.
State: pb.StateType_STATE_TYPE_OK,
},
},
}
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingPutObject, iamSecretAccessKeyMissingPutObject, expectedSBCState)
// Validate error is returned for missing delete object permission
expectedSBCState = &pb.StorageBucketCredentialState{
State: &pb.Permissions{
Write: &pb.Permission{
State: pb.StateType_STATE_TYPE_OK,
},
Read: &pb.Permission{
State: pb.StateType_STATE_TYPE_OK,
},
Delete: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
},
},
}
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingDeleteObject, iamSecretAccessKeyMissingDeleteObject, expectedSBCState)
} }
func testPluginOnCreateStorageBucket(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) { func testPluginOnCreateStorageBucket(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) {
@@ -289,10 +347,12 @@ func testPluginOnDeleteStorageBucket(ctx context.Context, t *testing.T, p *stora
} }
} }
func testPluginValidatePermissions(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string) { func testPluginValidatePermissions(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string, expectedState *pb.StorageBucketCredentialState) {
t.Helper() t.Helper()
t.Logf("testing ValidatePermissions") t.Logf("testing ValidatePermissions")
require := require.New(t) require, assert := require.New(t), assert.New(t)
require.NotNil(expectedState)
reqAttrs, err := structpb.NewStruct(map[string]any{ reqAttrs, err := structpb.NewStruct(map[string]any{
credential.ConstRegion: region, credential.ConstRegion: region,
@@ -315,6 +375,51 @@ func testPluginValidatePermissions(ctx context.Context, t *testing.T, p *storage
_, err = p.ValidatePermissions(ctx, req) _, err = p.ValidatePermissions(ctx, req)
require.Error(err) require.Error(err)
var actualSBCState *pb.StorageBucketCredentialState
if st, ok := status.FromError(err); ok {
for _, detail := range st.Details() {
if statusDetail, ok := detail.(*pb.StorageBucketCredentialState); ok {
actualSBCState = statusDetail
break
}
}
}
require.NotNil(actualSBCState)
require.NotNil(actualSBCState.GetState())
expectedReadState := expectedState.GetState().GetRead()
if expectedReadState != nil {
actualReadState := actualSBCState.GetState().GetRead()
require.NotNil(actualReadState)
assert.Equal(expectedReadState.GetState(), actualReadState.GetState())
assert.NotEmpty(actualReadState.GetCheckedAt())
if expectedReadState.GetState() == pb.StateType_STATE_TYPE_ERROR {
assert.NotEmpty(actualReadState.GetErrorDetails())
}
}
expectedWriteState := expectedState.GetState().GetWrite()
if expectedWriteState != nil {
actualWriteState := actualSBCState.GetState().GetWrite()
require.NotNil(actualWriteState)
assert.Equal(expectedWriteState.GetState(), actualWriteState.GetState())
assert.NotEmpty(actualWriteState.GetCheckedAt())
if expectedWriteState.GetState() == pb.StateType_STATE_TYPE_ERROR {
assert.NotEmpty(actualWriteState.GetErrorDetails())
}
}
expectedDeleteState := expectedState.GetState().GetDelete()
if expectedDeleteState != nil {
actualDeleteState := actualSBCState.GetState().GetDelete()
require.NotNil(actualDeleteState)
assert.Equal(expectedDeleteState.GetState(), actualDeleteState.GetState())
assert.NotEmpty(actualDeleteState.GetCheckedAt())
if expectedDeleteState.GetState() == pb.StateType_STATE_TYPE_ERROR {
assert.NotEmpty(actualDeleteState.GetErrorDetails())
}
}
} }
func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string) { func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string) {
@@ -369,7 +474,7 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora
// Need to create a GRPC server to test GetObject // Need to create a GRPC server to test GetObject
t.Logf("testing GetObject") t.Logf("testing GetObject")
lis, err := net.Listen("tcp", "localhost:2030") lis, err := net.Listen("tcp", "[::1]:2030")
require.NoError(err) require.NoError(err)
grpcServer := grpc.NewServer() grpcServer := grpc.NewServer()
pb.RegisterStoragePluginServiceServer(grpcServer, &storage.StoragePlugin{}) pb.RegisterStoragePluginServiceServer(grpcServer, &storage.StoragePlugin{})
@@ -377,7 +482,7 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora
grpcServer.Serve(lis) grpcServer.Serve(lis)
}() }()
conn, err := grpc.Dial("localhost:2030", grpc.WithTransportCredentials(insecure.NewCredentials())) conn, err := grpc.Dial("[::1]:2030", grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(err) require.NoError(err)
defer conn.Close() defer conn.Close()
client := pb.NewStoragePluginServiceClient(conn) client := pb.NewStoragePluginServiceClient(conn)
+97 -12
View File
@@ -32,7 +32,9 @@ resource "aws_iam_policy" "valid" {
"Action": [ "Action": [
"s3:PutObject", "s3:PutObject",
"s3:GetObject", "s3:GetObject",
"s3:GetObjectAttributes" "s3:DeleteObject",
"s3:GetObjectAttributes",
"s3:ListBucket"
], ],
"Effect": "Allow", "Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*" "Resource": "${aws_s3_bucket.test.arn}*"
@@ -79,7 +81,9 @@ resource "aws_iam_policy" "missing_put_obj" {
{ {
"Action": [ "Action": [
"s3:GetObject", "s3:GetObject",
"s3:GetObjectAttributes" "s3:GetObjectAttributes",
"s3:ListBucket",
"s3:DeleteObject"
], ],
"Effect": "Allow", "Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*" "Resource": "${aws_s3_bucket.test.arn}*"
@@ -125,7 +129,7 @@ resource "aws_iam_policy" "missing_get_obj" {
{ {
"Action": [ "Action": [
"s3:PutObject", "s3:PutObject",
"s3:GetObjectAttributes" "s3:DeleteObject"
], ],
"Effect": "Allow", "Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*" "Resource": "${aws_s3_bucket.test.arn}*"
@@ -141,18 +145,69 @@ resource "aws_iam_role_policy_attachment" "missing_get_obj" {
policy_arn = aws_iam_policy.missing_get_obj.arn policy_arn = aws_iam_policy.missing_get_obj.arn
} }
resource "aws_iam_role" "missing_delete_obj" {
name = "${random_id.prefix.dec}-missing-delete-obj"
tags = local.tags
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
AWS: data.aws_caller_identity.current.user_id
}
},
]
})
}
resource "aws_iam_policy" "missing_delete_obj" {
name = "${random_id.prefix.dec}-missing-delete-obj"
tags = local.tags
policy = <<EOT
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:GetObjectAttributes",
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*"
}
]
}
EOT
}
resource "aws_iam_role_policy_attachment" "missing_delete_obj" {
role = aws_iam_role.missing_delete_obj.name
policy_arn = aws_iam_policy.missing_delete_obj.arn
}
# Static Credential Testing # Static Credential Testing
resource "random_id" "user_name" { resource "random_id" "user_name" {
count = var.iam_user_count count = var.iam_user_count
prefix = random_id.prefix.dec prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "test" { resource "aws_iam_user" "test" {
count = var.iam_user_count count = var.iam_user_count
name = random_id.user_name[count.index].dec name = random_id.user_name[count.index].dec
force_destroy = true force_destroy = true
tags = local.tags permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
tags = {
"boundary-demo" = local.hashicorp_email
}
} }
resource "aws_iam_access_key" "test" { resource "aws_iam_access_key" "test" {
@@ -195,17 +250,44 @@ resource "aws_iam_user_policy_attachment" "test_credentials" {
policy_arn = aws_iam_policy.credentials[count.index].arn policy_arn = aws_iam_policy.credentials[count.index].arn
} }
# Edge case for missing DeleteObject permission
resource "random_id" "missing_delete_obj" {
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4
}
resource "aws_iam_user" "missing_delete_obj" {
name = random_id.missing_delete_obj.dec
force_destroy = true
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
tags = {
"boundary-demo" = local.hashicorp_email
}
}
resource "aws_iam_access_key" "missing_delete_obj" {
user = aws_iam_user.missing_delete_obj.name
}
resource "aws_iam_user_policy_attachment" "missing_delete_obj" {
user = aws_iam_user.missing_delete_obj.name
policy_arn = aws_iam_policy.missing_delete_obj.arn
}
# Edge case for missing GetObject permission # Edge case for missing GetObject permission
resource "random_id" "missing_get_obj" { resource "random_id" "missing_get_obj" {
prefix = random_id.prefix.dec prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "missing_get_obj" { resource "aws_iam_user" "missing_get_obj" {
name = random_id.missing_get_obj.dec name = random_id.missing_get_obj.dec
force_destroy = true force_destroy = true
tags = local.tags permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
tags = {
"boundary-demo" = local.hashicorp_email
}
} }
resource "aws_iam_access_key" "missing_get_obj" { resource "aws_iam_access_key" "missing_get_obj" {
@@ -220,14 +302,17 @@ resource "aws_iam_user_policy_attachment" "missing_get_obj" {
# Edge case for missing PutObject permission # Edge case for missing PutObject permission
resource "random_id" "missing_put_obj" { resource "random_id" "missing_put_obj" {
prefix = random_id.prefix.dec prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "missing_put_obj" { resource "aws_iam_user" "missing_put_obj" {
name = random_id.missing_put_obj.dec name = random_id.missing_put_obj.dec
force_destroy = true force_destroy = true
tags = local.tags permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
tags = {
"boundary-demo" = local.hashicorp_email
}
} }
resource "aws_iam_access_key" "missing_put_obj" { resource "aws_iam_access_key" "missing_put_obj" {
+15 -1
View File
@@ -13,10 +13,14 @@ output "missing_put_obj_policy_arn" {
value = aws_iam_role.missing_put_obj.arn value = aws_iam_role.missing_put_obj.arn
} }
output "missing_put_get_policy_arn" { output "missing_get_obj_policy_arn" {
value = aws_iam_role.missing_get_obj.arn value = aws_iam_role.missing_get_obj.arn
} }
output "missing_delete_obj_policy_arn" {
value = aws_iam_role.missing_delete_obj.arn
}
output "iam_user_names" { output "iam_user_names" {
value = aws_iam_user.test.*.name value = aws_iam_user.test.*.name
} }
@@ -53,4 +57,14 @@ output "iam_access_key_missing_get_obj" {
output "iam_secret_access_key_missing_get_obj" { output "iam_secret_access_key_missing_get_obj" {
value = aws_iam_access_key.missing_get_obj.secret value = aws_iam_access_key.missing_get_obj.secret
sensitive = true sensitive = true
}
output "iam_access_key_missing_delete_obj" {
value = aws_iam_access_key.missing_delete_obj.id
sensitive = true
}
output "iam_secret_access_key_missing_delete_obj" {
value = aws_iam_access_key.missing_delete_obj.secret
sensitive = true
} }
+9 -1
View File
@@ -4,6 +4,14 @@
resource "aws_s3_bucket" "test" { resource "aws_s3_bucket" "test" {
bucket = random_id.prefix.dec bucket = random_id.prefix.dec
force_destroy = true force_destroy = true
acl = "private"
tags = local.tags tags = local.tags
}
resource "aws_s3_bucket_public_access_block" "secure_access" {
bucket = aws_s3_bucket.test.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
} }
+6 -4
View File
@@ -4,7 +4,7 @@
data "aws_caller_identity" "current" {} data "aws_caller_identity" "current" {}
resource "random_id" "prefix" { resource "random_id" "prefix" {
prefix = "storage-e2e-test" prefix = "aws-plugin-e2e-test"
byte_length = 4 byte_length = 4
} }
@@ -13,8 +13,10 @@ variable "iam_user_count" {
} }
locals { locals {
hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1]
tags = { tags = {
user_id = split(":", data.aws_caller_identity.current.user_id)[1] User = split(":", data.aws_caller_identity.current.user_id)[1]
repo = "boundary-plugin-aws" Repo = "boundary-plugin-aws"
Environment = "Demo"
} }
} }