From 1ca0ca6ab2284e50669e8746f452dfe50435057d Mon Sep 17 00:00:00 2001 From: Ahmed Bebars Date: Thu, 29 Jan 2026 22:06:41 -0500 Subject: [PATCH 1/2] feat(config): add single-hop assume role support for self-hosted deployments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When use-assume is set with role-arn but WITHOUT global-role-arn, the connector now performs single-hop assume role (IRSA -> target role). This supports self-hosted deployments (e.g., EKS with IRSA) that don't need an intermediate binding account. Changes: - external-id is only required for two-hop mode (when global-role-arn is set) - Dockerfile uses correct binary name (baton-aws) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Dockerfile | 5 +++++ pkg/config/config.go | 14 +++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..bdb8fc2d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM gcr.io/distroless/static-debian12:nonroot + +COPY baton-aws /baton-aws + +ENTRYPOINT ["/baton-aws"] diff --git a/pkg/config/config.go b/pkg/config/config.go index 9edf3f4a..f68f0d70 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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 + } } } return nil @@ -153,7 +158,6 @@ var Config = field.NewConfiguration( UseAssumeField, }, []field.SchemaField{ - ExternalIdField, RoleArnField, }, )), From ae919385f3e67e9d6bce2a95befd61821753157c Mon Sep 17 00:00:00 2001 From: Ahmed Bebars Date: Thu, 29 Jan 2026 22:07:17 -0500 Subject: [PATCH 2/2] feat(connector): add single-hop assume role for self-hosted deployments When globalRoleARN is empty but roleARN is set, assume directly into the target role without an intermediate binding account hop. This enables self-hosted deployments (e.g., EKS with IRSA) to use assume role without needing ConductorOne's binding account flow. Co-Authored-By: Claude --- pkg/connector/connector.go | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 1a5e750d..9188ebf5 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -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) {