Files
threshold-plugin-aws/plugin/plugin_test.go
T

349 lines
13 KiB
Go
Raw Normal View History

2021-09-22 17:00:37 -07:00
package plugin
import (
"context"
"os"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws/awserr"
pluginTesting "github.com/hashicorp/boundary-host-plugin-aws/testing"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)
func TestPlugin(t *testing.T) {
region := os.Getenv("AWS_REGION")
if region == "" {
t.Skip("set AWS_REGION to use this test")
}
if envAccessKeyId := os.Getenv("AWS_ACCESS_KEY_ID"); envAccessKeyId == "" {
t.Skip("set AWS_ACCESS_KEY_ID to use this test")
}
if envSecretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY"); envSecretAccessKey == "" {
t.Skip("set AWS_SECRET_ACCESS_KEY to use this test")
}
require := require.New(t)
tf, err := pluginTesting.NewTestTerraformer("testdata/basic")
require.NoError(err)
require.NotNil(tf)
err = tf.Deploy()
require.NoError(err)
defer func() {
if err := tf.Destroy(); err != nil {
t.Logf("WARNING: could not run Terraform destroy: %s", err)
}
}()
iamUserName, err := tf.GetOutputString("iam_user_name")
require.NoError(err)
require.NotZero(iamUserName)
iamUserArn, err := tf.GetOutputString("iam_user_arn")
require.NoError(err)
require.NotZero(iamUserArn)
iamAccessKeyId, err := tf.GetOutputString("iam_access_key_id")
require.NoError(err)
require.NotZero(iamAccessKeyId)
iamSecretAccessKey, err := tf.GetOutputString("iam_secret_access_key")
require.NoError(err)
require.NotZero(iamSecretAccessKey)
ec2InstanceIds, err := tf.GetOutputSlice("instance_ids")
require.NoError(err)
require.Len(ec2InstanceIds, 5)
ec2InstanceAddrs, err := tf.GetOutputMap("instance_addrs")
require.NoError(err)
require.Len(ec2InstanceAddrs, 5)
ec2InstanceTags, err := tf.GetOutputMap("instance_tags")
require.NoError(err)
require.Len(ec2InstanceTags, 5)
// Start the workflow now. Set up the host catalog. Note that this
// 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
// will assert this through the returned state.
p := new(AwsPlugin)
ctx := context.Background()
// ********************
// * OnCreateCatalog
// ********************
//
// Test rotation first.
iamAccessKeyId, iamSecretAccessKey = testPluginOnCreateCatalog(ctx, t, p, region, iamAccessKeyId, iamSecretAccessKey, true)
// Test non-rotation next.
iamAccessKeyId, iamSecretAccessKey = testPluginOnCreateCatalog(ctx, t, p, region, iamAccessKeyId, iamSecretAccessKey, false)
// ********************
// * OnUpdateCatalog
// ********************
//
// Test no-op non-rotation.
iamAccessKeyId, iamSecretAccessKey = testPluginOnCreateCatalog(ctx, t, p, region, iamAccessKeyId, iamSecretAccessKey, "", "", false, false)
// Switch to rotation.
iamAccessKeyId, iamSecretAccessKey = testPluginOnCreateCatalog(ctx, t, p, region, iamAccessKeyId, iamSecretAccessKey, "", "", false, true)
// Test no-op with rotation.
iamAccessKeyId, iamSecretAccessKey = testPluginOnCreateCatalog(ctx, t, p, region, iamAccessKeyId, iamSecretAccessKey, "", "", true, true)
// Switch credentials to other user.
}
func testPluginOnCreateCatalog(ctx context.Context, t *testing.T, p *AwsPlugin, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) {
t.Helper()
require := require.New(t)
reqAttrs, err := structpb.NewStruct(map[string]interface{}{
constRegion: region,
constDisableCredentialRotation: !rotate,
})
require.NoError(err)
reqSecrets, err := structpb.NewStruct(map[string]interface{}{
constAccessKeyId: accessKeyId,
constSecretAccessKey: secretAccessKey,
})
require.NoError(err)
request := &pb.OnCreateCatalogRequest{
Catalog: &hostcatalogs.HostCatalog{
Attributes: reqAttrs,
Secrets: reqSecrets,
},
}
response, err := p.OnCreateCatalog(ctx, request)
require.NoError(err)
require.NotNil(response)
persisted := response.GetPersisted()
require.NotNil(persisted)
persistedData := persisted.GetData()
require.NotNil(persistedData)
persistedDataMap := persistedData.AsMap()
require.NotNil(persistedDataMap)
persistedAccessKeyId, ok := persistedDataMap[constAccessKeyId]
require.True(ok)
require.NotZero(persistedAccessKeyId)
if rotate {
require.NotEqual(accessKeyId, persistedAccessKeyId)
} else {
require.Equal(accessKeyId, persistedAccessKeyId)
}
persistedSecretAccessKey, ok := persistedDataMap[constSecretAccessKey]
require.True(ok)
require.NotZero(persistedSecretAccessKey)
if rotate {
require.NotEqual(secretAccessKey, persistedSecretAccessKey)
} else {
require.Equal(secretAccessKey, persistedSecretAccessKey)
}
persistedCredsLastRotatedTime, ok := persistedDataMap[constCredsLastRotatedTime]
require.True(ok)
if rotate {
require.NotZero(persistedCredsLastRotatedTime)
} else {
require.Zero(persistedCredsLastRotatedTime)
}
return persistedAccessKeyId.(string), persistedSecretAccessKey.(string)
}
func testPluginOnUpdateCatalog(
ctx context.Context, t *testing.T, p *AwsPlugin,
region, currentAccessKeyId, currentSecretAccessKey, newAccessKeyId, newSecretAccessKey string,
rotated, rotate bool,
) (string, string) {
t.Helper()
require := require.New(t)
// Take a timestamp of the current time to get a point in time to
// reference, ensuring that we are updating credential rotation
// timestamps.
currentLastRotationTime = time.Now()
reqCurrentAttrs, err := structpb.NewStruct(map[string]interface{}{
constRegion: region,
constDisableCredentialRotation: !rotated,
})
reqNewAttrs, err := structpb.NewStruct(map[string]interface{}{
constRegion: region,
constDisableCredentialRotation: !rotate,
})
require.NoError(err)
var reqSecrets *structpb.Struct
if newAccessKeyId != "" && newSecretAccessKey != "" {
reqSecrets, err = structpb.NewStruct(map[string]interface{}{
constAccessKeyId: newAccessKeyId,
constSecretAccessKey: newSecretAccessKey,
})
}
reqPersistedData, err := structpb.NewStruct(map[string]interface{}{
constAccessKeyId: currentAccessKeyId,
constSecretAccessKey: currentSecretAccessKey,
constCredsLastRotatedTime: func() time.Time {
if rotated {
return currentLastRotationTime
}
return time.Time{}
}(),
})
require.NoError(err)
onUpdateCatalogRequest := &pb.OnUpdateCatalogRequest{
CurrentCatalog: &hostcatalogs.HostCatalog{
Attributes: reqCurrentAttrs,
},
NewCatalog: &hostcatalogs.HostCatalog{
Attributes: reqNewAttrs,
Secrets: reqSecrets,
},
Persisted: &pb.HostCatalogPersisted{
Data: reqPersistedData,
},
}
response, err := p.OnCreateCatalog(ctx, request)
require.NoError(err)
require.NotNil(response)
persisted := response.GetPersisted()
require.NotNil(persisted)
persistedData := persisted.GetData()
require.NotNil(persistedData)
persistedDataMap := persistedData.AsMap()
require.NotNil(persistedDataMap)
// Complex checks based on the scenarios.
persistedAccessKeyId, ok := persistedDataMap[constAccessKeyId]
require.True(ok)
require.NotZero(persistedAccessKeyId)
persistedSecretAccessKey, ok := persistedDataMap[constSecretAccessKey]
require.True(ok)
require.NotZero(persistedSecretAccessKey)
persistedCredsLastRotatedTime, ok := persistedDataMap[constCredsLastRotatedTime]
require.True(ok)
// Our test scenarios are complex due the multi-dimensional nature
// of criteria, so we lay them out in a switch below.
switch {
case newAccessKeyId != "" && rotated && rotate:
// The new access key ID was provided, we had previously rotated
// the credentials before, and the new credential set is to be
// rotated as well. In this case, the old credentials should have
// been deleted, and the new credentials should have been rotated,
// hence, should not match the new credentials initially
// provided. Rotation time should be non-zero and updated.
require.CredentialsInvalid(t, currentAccessKeyId, currentSecretAccessKey)
require.NotEqual(persistedAccessKeyId, newAccessKeyId)
require.NotEqual(persistedSecretAccessKey, newSecretAccessKey)
require.NotZero(persistedCredsLastRotatedTime)
require.True(persistedCredsLastRotatedTime.After(currentCredsLastRotatedTime))
case newAccessKeyId != "" && rotated && !rotate:
// The new access key ID was provided, we had previously rotated
// the credentials before, and the new credential is *not*
// rotated. In this case, the old credentials should have
// been deleted, But the new credentials should have not been
// rotated, and hence should be the same. Rotation time should be
// zero.
require.CredentialsInvalid(t, currentAccessKeyId, currentSecretAccessKey)
require.Equal(persistedAccessKeyId, newAccessKeyId)
require.Equal(persistedSecretAccessKey, newSecretAccessKey)
require.Zero(persistedCredsLastRotatedTime)
case newAccessKeyId != "" && !rotated && rotate:
// The new access key ID was provided, we *have not* previously
// rotated the credentials, and the new credential set is to be
// rotated. In this case, the old credentials should have been
// left alone, and the new credentials should have been rotated,
// hence, should not match the new credentials initially
// provided. Rotation time should be non-zero, but updated.
require.CredentialsValid(t, currentAccessKeyId, currentSecretAccessKey)
require.NotEqual(persistedAccessKeyId, newAccessKeyId)
require.NotEqual(persistedSecretAccessKey, newSecretAccessKey)
require.NotZero(persistedCredsLastRotatedTime)
require.True(persistedCredsLastRotatedTime.After(currentCredsLastRotatedTime))
case newAccessKeyId != "" && !rotated && !rotate:
// The new access key ID was provided, but we have not rotated
// the credentials previously and we still don't plan on rotating
// them. In this case, the old credentials should still be valid,
// and the persisted ones should match the new ones provided.
// Rotation time should be zero.
require.CredentialsValid(t, currentAccessKeyId, currentSecretAccessKey)
require.Equal(persistedAccessKeyId, newAccessKeyId)
require.Equal(persistedSecretAccessKey, newSecretAccessKey)
require.Zero(persistedCredsLastRotatedTime)
case newAccessKeyId == "" && rotated && rotate:
// No new credentials have been provided, but we have previously
// rotated and are still rotating credentials. This is a no-op.
// Existing credentials should still be valid and match the ones
// persisted to state. Rotation time should be identical since
// no new rotation occurred.
require.CredentialsValid(t, currentAccessKeyId, currentSecretAccessKey)
require.Equal(persistedAccessKeyId, currentAccessKeyId)
require.Equal(persistedSecretAccessKey, currentSecretAccessKey)
require.NotZero(persistedCredsLastRotatedTime)
require.Equal(currentCredsLastRotatedTime, persistedCredsLastRotatedTime)
case newAccessKeyId == "" && rotated && !rotate:
// No new credentials have been provided, and we have previously
// rotated the credentials. This is actually an error.
//
// TODO: validate this scenario through unit testing. For now if
// we for some reason try to test this with this function, return
// an error.
require.FailNow("testing rotated-to-not-rotated scenario not implemented by this helper")
case newAccessKeyId == "" && !rotated && rotate:
// No new credentials have been provided, and while we did not
// rotate before, we want to switch to rotation. In this case,
// the existing persisted credentials should have been rotated,
// with a new non-zero timestamp.
require.CredentialsInvalid(t, currentAccessKeyId, currentSecretAccessKey)
require.NotEqual(persistedAccessKeyId, currentAccessKeyId)
require.NotEqual(persistedSecretAccessKey, currentSecretAccessKey)
require.NotZero(persistedCredsLastRotatedTime)
case newAccessKeyId == "" && !rotated && !rotate:
// No new credentials have been provided and we have not, nor do
// not, plan on rotating the credentials. This is a no-op.
// Existing credentials should still be valid and match the ones
// persisted to state. Rotation time should remain at zero.
require.CredentialsValid(t, currentAccessKeyId, currentSecretAccessKey)
require.Equal(persistedAccessKeyId, currentAccessKeyId)
require.Equal(persistedSecretAccessKey, currentSecretAccessKey)
require.Zero(persistedCredsLastRotatedTime)
default:
// Scenario was reached that was not covered by this function.
require.FailNow("unknown test scenario")
}
return persistedAccessKeyId.(string), persistedSecretAccessKey.(string)
}
func requireCredentailsInvalid(t *testing.T, accessKeyId, secretAccessKey string) {
t.Helper()
require := require.New(t)
err := c.GetCallerIdentity()
require.NotNil(err)
awsErr, ok := err.(awserr.Error)
require.True(ok)
require.Equal("InvalidClientTokenId", awsErr.Code())
}
func requireCredentailsvValid(t *testing.T, accessKeyId, secretAccessKey string) {
t.Helper()
require := require.New(t)
err := c.GetCallerIdentity()
require.NoError(err)
}