Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM gcr.io/distroless/static-debian12:nonroot

COPY baton-aws /baton-aws

ENTRYPOINT ["/baton-aws"]
14 changes: 9 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,18 @@ func ValidateExternalId(input string) error {
// validateConfig is run after the configuration is loaded, and should return an error if it isn't valid.
func ValidateConfig(ctx context.Context, awsc *Aws) error {
if awsc.GetBool(UseAssumeField.FieldName) {
err := ValidateExternalId(awsc.GetString(ExternalIdField.FieldName))
err := connector.IsValidRoleARN(awsc.GetString(RoleArnField.FieldName))
if err != nil {
return err
}
err = connector.IsValidRoleARN(awsc.GetString(RoleArnField.FieldName))
if err != nil {
return err
// Only validate external-id for two-hop mode (when global-role-arn is set)
// Single-hop mode (IRSA → target role) doesn't require external-id
globalRoleArn := awsc.GetString(GlobalRoleArnField.FieldName)
if globalRoleArn != "" {
err = ValidateExternalId(awsc.GetString(ExternalIdField.FieldName))
if err != nil {
return err
}
Comment on lines +118 to +129
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate global-role-arn when provided.

Right now an invalid global-role-arn can pass validation and only fail later during STS calls. Consider validating it alongside role-arn before external-id checks.

🔧 Proposed fix
 		err := connector.IsValidRoleARN(awsc.GetString(RoleArnField.FieldName))
 		if err != nil {
 			return err
 		}
 		// Only validate external-id for two-hop mode (when global-role-arn is set)
 		// Single-hop mode (IRSA → target role) doesn't require external-id
 		globalRoleArn := awsc.GetString(GlobalRoleArnField.FieldName)
 		if globalRoleArn != "" {
-			err = ValidateExternalId(awsc.GetString(ExternalIdField.FieldName))
-			if err != nil {
-				return err
-			}
+			if err := connector.IsValidRoleARN(globalRoleArn); err != nil {
+				return err
+			}
+			if err := ValidateExternalId(awsc.GetString(ExternalIdField.FieldName)); err != nil {
+				return err
+			}
 		}
🤖 Prompt for AI Agents
In `@pkg/config/config.go` around lines 118 - 129, The code currently validates
RoleArn via connector.IsValidRoleARN but doesn't validate GlobalRoleArn; update
the validation flow in the same block to call connector.IsValidRoleARN on
awsc.GetString(GlobalRoleArnField.FieldName) (similar to RoleArnField) before
running ValidateExternalId, and return the error if that call fails; keep
ValidateExternalId(awsc.GetString(ExternalIdField.FieldName)) only for the
two-hop case when globalRoleArn != "".

}
}
return nil
Expand Down Expand Up @@ -153,7 +158,6 @@ var Config = field.NewConfiguration(
UseAssumeField,
},
[]field.SchemaField{
ExternalIdField,
RoleArnField,
},
)),
Expand Down
30 changes: 29 additions & 1 deletion pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,35 @@ func (o *AWS) getCallingConfig(ctx context.Context, region string) (awsSdk.Confi
return o.baseConfig, nil
}
l := ctxzap.Extract(ctx)
// ok, if we are an instance, we do the assumeRole twice, first time from our Instance role, INTO the binding account

// Single-hop mode: when globalRoleARN is empty, assume directly into roleARN
// This supports self-hosted deployments (e.g., EKS with IRSA) that don't need
// an intermediate binding account.
if o.globalRoleARN == "" && o.roleARN != "" {
l.Debug("aws-connector: using single-hop assume role mode",
zap.String("role_arn", o.roleARN),
)
stsSvc := sts.NewFromConfig(o.baseConfig)
callingCreds := awsSdk.NewCredentialsCache(stscreds.NewAssumeRoleProvider(stsSvc, o.roleARN, func(aro *stscreds.AssumeRoleOptions) {
if o.externalID != "" {
aro.ExternalID = awsSdk.String(o.externalID)
}
}))

_, err := callingCreds.Retrieve(ctx)
if err != nil {
return awsSdk.Config{}, fmt.Errorf("aws-connector: failed to assume role into '%s': %w", o.roleARN, err)
}

return awsSdk.Config{
HTTPClient: o.baseClient,
Region: region,
DefaultsMode: awsSdk.DefaultsModeInRegion,
Credentials: callingCreds,
}, nil
}

// Two-hop mode: if we are an instance, we do the assumeRole twice, first time from our Instance role, INTO the binding account
// and from there, into the customer account.
stsSvc := sts.NewFromConfig(o.baseConfig)
bindingCreds := awsSdk.NewCredentialsCache(stscreds.NewAssumeRoleProvider(stsSvc, o.globalRoleARN, func(aro *stscreds.AssumeRoleOptions) {
Expand Down
Loading