2023-03-20 11:02:17 -07:00
package credential
2021-09-30 17:05:27 -07:00
import (
2021-10-05 16:36:53 -07:00
"errors"
"fmt"
2021-09-30 17:05:27 -07:00
"testing"
"time"
2021-10-05 16:36:53 -07:00
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/go-secure-stdlib/awsutil"
2021-09-30 17:05:27 -07:00
"github.com/stretchr/testify/require"
2023-03-20 11:02:17 -07:00
"google.golang.org/protobuf/types/known/structpb"
2021-09-30 17:05:27 -07:00
)
2023-03-20 11:02:17 -07:00
func TestNewAwsCredentialPersistedState ( t * testing . T ) {
2021-10-06 12:47:05 -07:00
staticTime := time . Now ()
cases := [] struct {
name string
2023-03-20 11:02:17 -07:00
opts [] AwsCredentialPersistedStateOption
expected * AwsCredentialPersistedState
2021-10-06 12:47:05 -07:00
expectedErr string
}{
{
name : "error" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
func ( s * AwsCredentialPersistedState ) error {
2021-10-06 12:47:05 -07:00
return errors . New ( testOptionErr )
},
},
expectedErr : testOptionErr ,
},
{
name : "access key id" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithAccessKeyId ( "foobar" ),
2021-10-06 12:47:05 -07:00
},
2023-03-20 11:02:17 -07:00
expected : & AwsCredentialPersistedState {
2021-10-06 12:47:05 -07:00
AccessKeyId : "foobar" ,
},
},
{
name : "secret access key" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithSecretAccessKey ( "bazqux" ),
2021-10-06 12:47:05 -07:00
},
2023-03-20 11:02:17 -07:00
expected : & AwsCredentialPersistedState {
2021-10-06 12:47:05 -07:00
SecretAccessKey : "bazqux" ,
},
},
{
name : "rotation time" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithCredsLastRotatedTime ( staticTime ),
2021-10-06 12:47:05 -07:00
},
2023-03-20 11:02:17 -07:00
expected : & AwsCredentialPersistedState {
2021-10-06 12:47:05 -07:00
CredsLastRotatedTime : staticTime ,
},
},
2022-09-02 14:35:34 -04:00
{
name : "region key" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithRegion ( "foobar" ),
2022-09-02 14:35:34 -04:00
},
2023-03-20 11:02:17 -07:00
expected : & AwsCredentialPersistedState {
2022-09-02 14:35:34 -04:00
region : "foobar" ,
},
},
2021-10-06 12:47:05 -07:00
{
name : "double set access key id" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithAccessKeyId ( "foobar" ),
WithAccessKeyId ( "onetwo" ),
2021-10-06 12:47:05 -07:00
},
expectedErr : "access key id already set" ,
},
{
2022-09-02 14:35:34 -04:00
name : "double set secret access key" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithSecretAccessKey ( "bazqux" ),
WithSecretAccessKey ( "threefour" ),
2021-10-06 12:47:05 -07:00
},
expectedErr : "secret access key already set" ,
},
{
2022-09-02 14:35:34 -04:00
name : "double set rotation time" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithCredsLastRotatedTime ( staticTime ),
WithCredsLastRotatedTime ( time . Now ()),
2021-10-06 12:47:05 -07:00
},
expectedErr : "last rotation time already set" ,
},
2022-09-02 14:35:34 -04:00
{
name : "double set region" ,
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithRegion ( "foobar" ),
WithRegion ( "barfoo" ),
2022-09-02 14:35:34 -04:00
},
expectedErr : "region already set" ,
},
2021-10-06 12:47:05 -07:00
}
for _ , tc := range cases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
require := require . New ( t )
2023-03-20 11:02:17 -07:00
actual , err := NewAwsCredentialPersistedState ( tc . opts ... )
2021-10-06 12:47:05 -07:00
if tc . expectedErr != "" {
require . EqualError ( err , tc . expectedErr )
return
}
require . NoError ( err )
require . Equal ( tc . expected , actual )
})
}
}
2023-03-20 11:02:17 -07:00
func TestAwsCredentialPersistedStateFromProto ( t * testing . T ) {
2021-09-30 17:05:27 -07:00
cases := [] struct {
name string
2023-03-20 11:02:17 -07:00
in map [ string ] any
opts [] AwsCredentialPersistedStateOption
expected * AwsCredentialPersistedState
2021-09-30 17:05:27 -07:00
expectedErr string
}{
{
2023-03-20 11:02:17 -07:00
name : "no secrets" ,
in : nil ,
2021-09-30 17:05:27 -07:00
expectedErr : "missing persisted secrets" ,
},
{
2023-03-20 11:02:17 -07:00
name : "missing access key ID" ,
in : map [ string ] any {},
2021-09-30 17:05:27 -07:00
expectedErr : "persisted state integrity error: missing required value \"access_key_id\"" ,
},
{
name : "missing secret access key" ,
2023-03-20 11:02:17 -07:00
in : map [ string ] any {
ConstAccessKeyId : "foobar" ,
2021-09-30 17:05:27 -07:00
},
expectedErr : "persisted state integrity error: missing required value \"secret_access_key\"" ,
},
{
name : "bad last rotated time" ,
2023-03-20 11:02:17 -07:00
in : map [ string ] any {
ConstAccessKeyId : "foobar" ,
ConstSecretAccessKey : "bazqux" ,
ConstCredsLastRotatedTime : "notatime" ,
2021-09-30 17:05:27 -07:00
},
expectedErr : "persisted state integrity error: could not parse time in value \"creds_last_rotated_time\": parsing time \"notatime\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"notatime\" as \"2006\"" ,
},
2021-10-06 12:47:05 -07:00
{
name : "option error" ,
2023-03-20 11:02:17 -07:00
in : map [ string ] any {
ConstAccessKeyId : "foobar" ,
ConstSecretAccessKey : "bazqux" ,
ConstCredsLastRotatedTime : "2006-01-02T15:04:05+07:00" ,
},
opts : [] AwsCredentialPersistedStateOption {
func ( s * AwsCredentialPersistedState ) error {
2021-10-06 12:47:05 -07:00
return errors . New ( testOptionErr )
},
},
expectedErr : testOptionErr ,
},
2021-09-30 17:05:27 -07:00
{
name : "good with non-zero timestamp" ,
2023-03-20 11:02:17 -07:00
in : map [ string ] any {
ConstAccessKeyId : "foobar" ,
ConstSecretAccessKey : "bazqux" ,
ConstCredsLastRotatedTime : "2006-01-02T15:04:05+07:00" ,
},
expected : & AwsCredentialPersistedState {
2021-09-30 17:05:27 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
CredsLastRotatedTime : func () time . Time {
t , err := time . Parse ( time . RFC3339 , "2006-01-02T15:04:05+07:00" )
if err != nil {
panic ( err )
}
return t
}(),
},
},
{
name : "good with zero timestamp" ,
2023-03-20 11:02:17 -07:00
in : map [ string ] any {
ConstAccessKeyId : "foobar" ,
ConstSecretAccessKey : "bazqux" ,
ConstCredsLastRotatedTime : ( time . Time {}). Format ( time . RFC3339Nano ),
2021-09-30 17:05:27 -07:00
},
2023-03-20 11:02:17 -07:00
expected : & AwsCredentialPersistedState {
2021-10-06 12:47:05 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
CredsLastRotatedTime : time . Time {},
},
},
2021-10-05 16:36:53 -07:00
{
2023-03-20 11:02:17 -07:00
name : "good (ignoring non-test options)" ,
in : map [ string ] any {
ConstAccessKeyId : "foobar" ,
ConstSecretAccessKey : "bazqux" ,
ConstCredsLastRotatedTime : ( time . Time {}). Format ( time . RFC3339Nano ),
2021-10-05 16:36:53 -07:00
},
2023-03-20 11:02:17 -07:00
opts : [] AwsCredentialPersistedStateOption {
WithAccessKeyId ( "ignored" ),
WithRegion ( "us-west-2" ),
2021-10-05 16:36:53 -07:00
},
2023-03-20 11:02:17 -07:00
expected : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
CredsLastRotatedTime : time . Time {},
2023-03-20 11:02:17 -07:00
region : "us-west-2" ,
2021-10-05 16:36:53 -07:00
},
},
}
for _ , tc := range cases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
require := require . New ( t )
2023-03-20 11:02:17 -07:00
var (
input * structpb . Struct
err error
)
if tc . in != nil {
input , err = structpb . NewStruct ( tc . in )
require . NoError ( err )
}
actual , err := AwsCredentialPersistedStateFromProto ( input , tc . opts ... )
2021-10-05 16:36:53 -07:00
if tc . expectedErr != "" {
require . EqualError ( err , tc . expectedErr )
return
}
require . NoError ( err )
2023-03-20 11:02:17 -07:00
require . Equal ( tc . expected . AccessKeyId , actual . AccessKeyId )
require . Equal ( tc . expected . SecretAccessKey , actual . SecretAccessKey )
require . Equal ( tc . expected . region , actual . region )
2021-10-05 16:36:53 -07:00
})
}
}
func TestAwsCatalogPersistedStateValidateCreds ( t * testing . T ) {
cases := [] struct {
name string
2023-03-20 11:02:17 -07:00
in * AwsCredentialPersistedState
2021-10-05 16:36:53 -07:00
expectedErr string
}{
{
name : "could not load credentials" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
testOpts : [] awsutil . Option { awsutil . MockOptionErr ( errors . New ( testOptionErr ))},
},
expectedErr : fmt . Sprintf ( "error loading credentials: error reading options in NewCredentialsConfig: %s" , testOptionErr ),
},
{
name : "validation error" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
testOpts : [] awsutil . Option { awsutil . WithSTSAPIFunc ( awsutil . NewMockSTS ( awsutil . WithGetCallerIdentityError ( errors . New ( testGetCallerIdentityErr ))))},
},
expectedErr : fmt . Sprintf ( "error validating credentials: %s" , testGetCallerIdentityErr ),
},
{
name : "good" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
testOpts : [] awsutil . Option { awsutil . WithSTSAPIFunc ( awsutil . NewMockSTS ())},
},
},
}
for _ , tc := range cases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
require := require . New ( t )
err := tc . in . ValidateCreds ()
if tc . expectedErr != "" {
require . EqualError ( err , tc . expectedErr )
return
}
require . NoError ( err )
})
}
}
func TestAwsCatalogPersistedStateRotateCreds ( t * testing . T ) {
cases := [] struct {
name string
2023-03-20 11:02:17 -07:00
in * AwsCredentialPersistedState
2021-10-05 16:36:53 -07:00
expectedAccessKeyId string
expectedSecretAccessKey string
expectedNonZeroRotatedTime bool
expectedErr string
}{
{
name : "could not load credentials" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
testOpts : [] awsutil . Option { awsutil . MockOptionErr ( errors . New ( testOptionErr ))},
},
expectedErr : fmt . Sprintf ( "error loading credentials: error reading options in NewCredentialsConfig: %s" , testOptionErr ),
},
{
name : "rotation error" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
testOpts : [] awsutil . Option {
awsutil . WithIAMAPIFunc (
awsutil . NewMockIAM (
awsutil . WithGetUserError ( errors . New ( testGetUserErr )),
),
),
},
},
expectedErr : fmt . Sprintf ( "error rotating credentials: error calling CreateAccessKey: error calling aws.GetUser: %s" , testGetUserErr ),
},
{
name : "good" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
testOpts : [] awsutil . Option {
awsutil . WithSTSAPIFunc (
awsutil . NewMockSTS (),
),
awsutil . WithIAMAPIFunc (
awsutil . NewMockIAM (
awsutil . WithGetUserOutput (
& iam . GetUserOutput {
User : & iam . User {
Arn : aws . String ( "arn:aws:iam::123456789012:user/JohnDoe" ),
UserId : aws . String ( "AIDAJQABLZS4A3QDU576Q" ),
UserName : aws . String ( "JohnDoe" ),
},
},
),
awsutil . WithCreateAccessKeyOutput (
& iam . CreateAccessKeyOutput {
AccessKey : & iam . AccessKey {
AccessKeyId : aws . String ( "one" ),
SecretAccessKey : aws . String ( "two" ),
UserName : aws . String ( "JohnDoe" ),
},
},
),
),
),
},
},
expectedAccessKeyId : "one" ,
expectedSecretAccessKey : "two" ,
expectedNonZeroRotatedTime : true ,
},
}
for _ , tc := range cases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
require := require . New ( t )
err := tc . in . RotateCreds ()
if tc . expectedErr != "" {
require . EqualError ( err , tc . expectedErr )
return
}
require . NoError ( err )
require . Equal ( tc . expectedAccessKeyId , tc . in . AccessKeyId )
require . Equal ( tc . expectedSecretAccessKey , tc . in . SecretAccessKey )
require . Equal ( tc . expectedNonZeroRotatedTime , ! tc . in . CredsLastRotatedTime . IsZero ())
})
}
}
func TestAwsCatalogPersistedStateReplaceCreds ( t * testing . T ) {
// NOTE: Not safe to run this test in parallel
state := new ( testMockIAMState )
cases := [] struct {
name string
2023-03-20 11:02:17 -07:00
in * AwsCredentialPersistedState
2021-10-05 16:36:53 -07:00
accessKeyId string
secretAccessKey string
expectedAccessKeyId string
expectedSecretAccessKey string
expectedDeleteCalled bool
expectedErr string
}{
{
name : "missing access key id" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {},
2021-10-05 16:36:53 -07:00
expectedErr : "access key id cannot be empty" ,
},
{
name : "missing access key id" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {},
2021-10-05 16:36:53 -07:00
accessKeyId : "one" ,
expectedErr : "secret access key cannot be empty" ,
},
{
name : "identical access key id" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
},
accessKeyId : "foobar" ,
secretAccessKey : "bazqux" ,
expectedErr : "attempting to replace access key with the same one" ,
},
{
name : "deletion error" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
CredsLastRotatedTime : time . Now (),
testOpts : [] awsutil . Option {
awsutil . WithIAMAPIFunc (
awsutil . NewMockIAM (
awsutil . WithDeleteAccessKeyError ( errors . New ( testDeleteAccessKeyErr )),
),
),
},
},
accessKeyId : "one" ,
secretAccessKey : "two" ,
2021-10-06 12:47:05 -07:00
expectedErr : fmt . Sprintf ( "error deleting old access key: %s" , testDeleteAccessKeyErr ),
2021-10-05 16:36:53 -07:00
},
{
name : "good with delete of old rotated" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
CredsLastRotatedTime : time . Now (),
testOpts : [] awsutil . Option {
awsutil . WithIAMAPIFunc (
newTestMockIAM ( state ),
),
},
},
accessKeyId : "one" ,
secretAccessKey : "two" ,
expectedAccessKeyId : "one" ,
expectedSecretAccessKey : "two" ,
expectedDeleteCalled : true ,
},
{
name : "good without delete of old rotated" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
testOpts : [] awsutil . Option {
awsutil . WithIAMAPIFunc (
newTestMockIAM ( state ),
),
},
},
accessKeyId : "one" ,
secretAccessKey : "two" ,
expectedAccessKeyId : "one" ,
expectedSecretAccessKey : "two" ,
expectedDeleteCalled : false ,
},
}
for _ , tc := range cases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
require := require . New ( t )
state . Reset ()
err := tc . in . ReplaceCreds ( tc . accessKeyId , tc . secretAccessKey )
if tc . expectedErr != "" {
require . EqualError ( err , tc . expectedErr )
return
}
require . NoError ( err )
require . Equal ( tc . expectedAccessKeyId , tc . in . AccessKeyId )
require . Equal ( tc . expectedSecretAccessKey , tc . in . SecretAccessKey )
require . Zero ( tc . in . CredsLastRotatedTime )
require . Equal ( tc . expectedDeleteCalled , state . DeleteAccessKeyCalled )
})
}
}
func TestAwsCatalogPersistedStateDeleteCreds ( t * testing . T ) {
cases := [] struct {
name string
2023-03-20 11:02:17 -07:00
in * AwsCredentialPersistedState
2021-10-05 16:36:53 -07:00
expectedErr string
}{
{
name : "could not load credentials" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
testOpts : [] awsutil . Option { awsutil . MockOptionErr ( errors . New ( testOptionErr ))},
},
expectedErr : fmt . Sprintf ( "error loading credentials: error reading options in NewCredentialsConfig: %s" , testOptionErr ),
},
{
name : "deletion error" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
testOpts : [] awsutil . Option {
awsutil . WithIAMAPIFunc (
awsutil . NewMockIAM (
awsutil . WithDeleteAccessKeyError ( errors . New ( testDeleteAccessKeyErr )),
),
),
},
},
2021-10-06 12:47:05 -07:00
expectedErr : fmt . Sprintf ( "error deleting old access key: %s" , testDeleteAccessKeyErr ),
2021-10-05 16:36:53 -07:00
},
{
name : "deletion error, but OK because key was just gone" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
testOpts : [] awsutil . Option {
awsutil . WithIAMAPIFunc (
awsutil . NewMockIAM (
awsutil . WithDeleteAccessKeyError (
awserr . New ( iam . ErrCodeNoSuchEntityException , "" , nil ),
),
),
),
},
},
},
{
name : "good" ,
2023-03-20 11:02:17 -07:00
in : & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
AccessKeyId : "foobar" ,
SecretAccessKey : "bazqux" ,
testOpts : [] awsutil . Option {
awsutil . WithIAMAPIFunc (
awsutil . NewMockIAM (),
),
},
},
},
}
for _ , tc := range cases {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
require := require . New ( t )
err := tc . in . DeleteCreds ()
if tc . expectedErr != "" {
require . EqualError ( err , tc . expectedErr )
return
}
require . NoError ( err )
require . Empty ( tc . in . AccessKeyId )
require . Empty ( tc . in . SecretAccessKey )
require . Zero ( tc . in . CredsLastRotatedTime )
})
}
}
func TestAwsCatalogPersistedStateGetSessionErr ( t * testing . T ) {
require := require . New ( t )
2023-03-20 11:02:17 -07:00
state := & AwsCredentialPersistedState {
2021-10-05 16:36:53 -07:00
testOpts : [] awsutil . Option { awsutil . MockOptionErr ( errors . New ( testOptionErr ))},
}
_ , err := state . GetSession ()
require . EqualError ( err , fmt . Sprintf ( "error reading options in NewCredentialsConfig: %s" , testOptionErr ))
}