Files
threshold-plugin-aws/internal/credential/attributes_test.go
T
Damian DebkowskiandHugo fcfe267a3f refactor: switch from awsutil to awsutilv2 (#34)
* fix(storage): Storage Assume Role Support

This commit introduces fixes to enable operators to use AWS dynamic
credentials on the storage side of this plugin, namely validation,
improved credential lifecycle management to allow for storage buckets to
be updated from static to dynamic credentials and vice versa, and a new
way to determine credential types.

It also introduces new tests for dynamic credential usage for the
various functions.

* fix(storage): Static rotated creds being deleted on plugin err

On Storage Bucket update, when we determine if the credentials are to be
updated to new ones, we call `credState.ReplaceCreds`.

This call, if the old credentials were managed by us (rotated at create
time), will delete the old credentials and set the new ones onto
credState.

We called this function without validating if the incoming credentials
are valid. This could get the bucket in a state where where, while we'd
return an update error if the incoming credentials weren't working with
S3 (err in the dry run process that happens at the end of the update),
we'd also silently delete the old credentials the bucket used, rendering
the bucket completely broken.

---------

Co-authored-by: Hugo <hugoamvieira@users.noreply.github.com>
2023-09-22 12:26:08 -07:00

289 lines
8.2 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package credential
import (
"testing"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
)
func TestGetCredentialAttributes(t *testing.T) {
cases := []struct {
name string
in map[string]any
expected *CredentialAttributes
expectedErrContains string
}{
{
name: "missing region",
in: map[string]any{},
expectedErrContains: "missing required value \"region\"",
},
{
name: "invalid aws region value",
in: map[string]any{
ConstRegion: "dne",
},
expectedErrContains: "not a valid region",
},
{
name: "bad value for disable_credential_rotation",
in: map[string]any{
ConstRegion: "us-west-2",
ConstDisableCredentialRotation: "sure",
},
expectedErrContains: "unexpected type for value \"disable_credential_rotation\": want bool, got string",
},
{
name: "default",
in: map[string]any{
ConstRegion: "us-west-2",
},
expected: &CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
},
{
name: "with disable_credential_rotation",
in: map[string]any{
ConstRegion: "us-west-2",
ConstDisableCredentialRotation: true,
},
expected: &CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: true,
},
},
{
name: "with assume role",
in: map[string]any{
ConstRegion: "us-west-2",
ConstRoleArn: "arn:aws:iam::123456789012:role/S3Access",
ConstRoleExternalId: "1234567890",
ConstRoleSessionName: "test-session",
ConstRoleTags: map[string]interface{}{
"foo": "bar",
},
},
expected: &CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890",
RoleSessionName: "test-session",
RoleTags: map[string]string{
"foo": "bar",
},
},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
input, err := structpb.NewStruct(tc.in)
require.NoError(err)
actual, err := GetCredentialAttributes(input)
if tc.expectedErrContains != "" {
require.Error(err)
require.Contains(err.Error(), tc.expectedErrContains)
require.Equal(status.Code(err), codes.InvalidArgument)
return
}
require.NoError(err)
require.EqualValues(tc.expected.Region, actual.Region)
require.EqualValues(tc.expected.DisableCredentialRotation, actual.DisableCredentialRotation)
})
}
}
func TestGetCredentialsConfig(t *testing.T) {
cases := []struct {
name string
secrets *structpb.Struct
required bool
attrs *CredentialAttributes
expected *awsutil.CredentialsConfig
expectedErrContains string
}{
{
name: "no credentials",
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expected: &awsutil.CredentialsConfig{
Region: "us-west-2",
},
},
{
name: "with static credentials",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
},
},
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expected: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
Region: "us-west-2",
},
},
{
name: "missing access key",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
},
},
attrs: &CredentialAttributes{
Region: "us-west-2",
},
required: true,
expectedErrContains: "secrets.access_key_id: missing required value",
},
{
name: "missing secret key",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
},
},
required: true,
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expectedErrContains: "secrets.secret_access_key: missing required value",
},
{
name: "unknown fields",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
"foo": structpb.NewBoolValue(true),
"bar": structpb.NewBoolValue(true),
},
},
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expectedErrContains: "secrets.bar: unrecognized field, secrets.foo: unrecognized field",
},
{
name: "valid ignore creds_last_rotated_time",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
},
},
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expected: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
Region: "us-west-2",
},
},
{
name: "with assume role",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
attrs: &CredentialAttributes{
Region: "us-west-2",
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890",
RoleSessionName: "test-session",
RoleTags: map[string]string{
"foo": "bar",
},
DisableCredentialRotation: true,
},
expected: &awsutil.CredentialsConfig{
Region: "us-west-2",
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890",
RoleSessionName: "test-session",
RoleTags: map[string]string{
"foo": "bar",
},
},
},
{
name: "with static credential & assume role",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
},
},
attrs: &CredentialAttributes{
Region: "us-west-2",
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890",
RoleSessionName: "test-session",
RoleTags: map[string]string{
"foo": "bar",
},
},
expectedErrContains: "attributes.role_arn: conflicts with access_key_id and secret_access_key values, secrets.access_key_id: conflicts with role_arn value, secrets.secret_access_key: conflicts with role_arn value",
},
{
name: "with dynamic credentials and no disable credential rotation",
secrets: &structpb.Struct{Fields: map[string]*structpb.Value{}},
attrs: &CredentialAttributes{
Region: "us-west-2",
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890",
RoleSessionName: "test-session",
RoleTags: map[string]string{
"foo": "bar",
},
},
expectedErrContains: "attributes.disable_credential_rotation: disable_credential_rotation attribute is required when providing a role_arn",
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
actual, err := GetCredentialsConfig(tc.secrets, tc.attrs, tc.required)
if tc.expectedErrContains != "" {
require.Error(err)
require.Contains(err.Error(), tc.expectedErrContains)
require.Equal(status.Code(err), codes.InvalidArgument)
return
}
require.NoError(err)
require.Equal(tc.expected.AccessKey, actual.AccessKey)
require.Equal(tc.expected.SecretKey, actual.SecretKey)
require.Equal(tc.expected.Region, actual.Region)
require.Equal(tc.expected.RoleARN, actual.RoleARN)
require.Equal(tc.expected.RoleExternalId, actual.RoleExternalId)
require.Equal(tc.expected.RoleSessionName, actual.RoleSessionName)
require.Equal(tc.expected.RoleTags, actual.RoleTags)
})
}
}