Fix role arn fallthrough bug
This commit is contained in:
1 parent
77484a3fc9
commit
120ba8e8e4
3 files changed
+83
No files matched your search
@@ -112,10 +112,35 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) (
|
||||
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
||||
// to check that the credentials are valid. This method returns a status error
|
||||
// with PluginError details.
|
||||
//
|
||||
// When RoleARN is set and no static key is present (ambient role path),
|
||||
// credentials are eagerly retrieved before calling GetCallerIdentity. This
|
||||
// forces sts:AssumeRole to be called immediately so that any role errors
|
||||
// (e.g. no such role, access denied) surface here rather than being deferred
|
||||
// to the first real AWS API call.
|
||||
func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.BadRequestStatus("missing credentials config")
|
||||
}
|
||||
|
||||
// For the ambient role path (RoleARN set, no static key), eagerly resolve
|
||||
// the credential chain so that sts:AssumeRole is called now. Without this,
|
||||
// the AssumeRoleProvider inside the chain is lazy and GetCallerIdentity
|
||||
// would succeed even with an invalid role ARN (it uses the mock/ambient
|
||||
// identity directly rather than the assumed one).
|
||||
cc := s.CredentialsConfig
|
||||
if cc.RoleARN != "" && cc.AccessKey == "" {
|
||||
cfg, err := s.GenerateCredentialChain(ctx)
|
||||
if err != nil {
|
||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||
return st.Err()
|
||||
}
|
||||
if _, err := cfg.Credentials.Retrieve(ctx); err != nil {
|
||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||
return st.Err()
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||
return st.Err()
|
||||
|
||||
@@ -90,6 +90,15 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
||||
}
|
||||
}
|
||||
|
||||
// Validate assumed-role credentials at catalog-creation time so that an
|
||||
// invalid role_arn is rejected immediately rather than silently falling
|
||||
// back to ambient credentials.
|
||||
if credential.GetCredentialType(credState.CredentialsConfig) == credential.DynamicAWS {
|
||||
if err := credState.ValidateCreds(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
catalogState, err := newAwsCatalogPersistedState(
|
||||
append([]awsCatalogPersistedStateOption{
|
||||
withCredentials(credState),
|
||||
@@ -185,6 +194,15 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
|
||||
}
|
||||
|
||||
// Validate assumed-role credentials when the role_arn changes so that
|
||||
// an invalid ARN is caught before replacing the existing credential state.
|
||||
if credential.GetCredentialType(newCredState.CredentialsConfig) == credential.DynamicAWS {
|
||||
if err := newCredState.ValidateCreds(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newCatalogState, err := newAwsCatalogPersistedState(
|
||||
append([]awsCatalogPersistedStateOption{
|
||||
withCredentials(newCredState),
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||
@@ -158,6 +160,20 @@ func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||
)),
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||
Credentials: &stsTypes.Credentials{
|
||||
AccessKeyId: aws.String("ASIAfoobar"),
|
||||
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||
SessionToken: aws.String("sessiontokenfoobar"),
|
||||
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||
},
|
||||
}),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
expRsp: &pb.OnCreateCatalogResponse{Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}}},
|
||||
},
|
||||
}
|
||||
@@ -315,6 +331,20 @@ func TestPluginOnUpdateCatalogSuccess(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||
)),
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||
Credentials: &stsTypes.Credentials{
|
||||
AccessKeyId: aws.String("ASIAfoobar"),
|
||||
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||
SessionToken: aws.String("sessiontokenfoobar"),
|
||||
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||
},
|
||||
}),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
expRsp: &pb.OnUpdateCatalogResponse{
|
||||
Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}},
|
||||
},
|
||||
@@ -421,6 +451,16 @@ func TestPluginOnUpdateCatalogSuccess(t *testing.T) {
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(awsutil.NewMockIAM()),
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||
Credentials: &stsTypes.Credentials{
|
||||
AccessKeyId: aws.String("ASIAfoobar"),
|
||||
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||
SessionToken: aws.String("sessiontokenfoobar"),
|
||||
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||
},
|
||||
}),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
expRsp: &pb.OnUpdateCatalogResponse{
|
||||
|
||||
Reference in new issue
Block a user