Skip to content

[CXP-35] feat: add single-hop assume role for self-hosted deployments#99

Merged
johnallers merged 2 commits intoConductorOne:mainfrom
abebars:feat/single-hop-assume-role
Jan 30, 2026
Merged

[CXP-35] feat: add single-hop assume role for self-hosted deployments#99
johnallers merged 2 commits intoConductorOne:mainfrom
abebars:feat/single-hop-assume-role

Conversation

@abebars
Copy link
Contributor

@abebars abebars commented Jan 30, 2026

Note

🤖 Generated with Claude Code

Summary

Adds single-hop assume role support for self-hosted deployments (e.g., EKS with IRSA) that don't need ConductorOne's intermediate binding account.

Problem

The current use-assume mode requires a two-hop flow designed for ConductorOne SaaS:

Instance Identity → globalRoleARN (binding account) → roleARN (customer account)

For self-hosted deployments using IRSA, we only need single-hop:

IRSA → Target Account Role

Previously, if global-role-arn was empty with use-assume=true, the connector would fail with:

aws-connector: internal binding error
ValidationError: Value '' at 'roleArn' failed to satisfy constraint

Solution

When globalRoleARN is empty but roleARN is provided, assume directly into the target role without requiring the binding hop.

Changes

  • pkg/connector/connector.go: Add single-hop path in getCallingConfig() - when globalRoleARN is empty but roleARN is set, directly assume into roleARN using STS
  • pkg/config/config.go: Make external-id only required for two-hop mode (when global-role-arn is set)

Usage

# Single-hop mode (new) - for self-hosted with IRSA
baton-aws --use-assume --role-arn arn:aws:iam::TARGET:role/Role

# Two-hop mode (existing behavior unchanged) - for ConductorOne SaaS
baton-aws --use-assume \
  --global-role-arn arn:aws:iam::BINDING:role/Role \
  --role-arn arn:aws:iam::TARGET:role/Role \
  --external-id <id>

Testing

  • Unit tests pass
  • Build succeeds
  • Deployed to production - EKS with IRSA assuming into AWS Control Tower management account

Production Validation

This fix has been deployed and validated in production:

  • Running on EKS with IRSA (IAM Roles for Service Accounts)
  • Successfully assuming into AWS Control Tower management account
  • Syncing AWS Organizations and SSO (Identity Center)
  • Cross-account role assumption working correctly

Backwards Compatibility

  • Existing two-hop behavior is unchanged when global-role-arn is provided
  • PAT and other auth methods are unchanged
  • Only adds new single-hop path when global-role-arn is empty

Rollback Plan

Revert PR - existing authentication flows are unchanged.

abebars and others added 2 commits January 29, 2026 22:06
…oyments

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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
@abebars abebars requested a review from a team January 30, 2026 03:07
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

Walkthrough

These changes introduce a single-hop assume role path directly to a Role ARN via STS while restructuring validation logic to only validate ExternalID when using the two-hop flow (GlobalRoleArnField present). ExternalIdField is removed from the public configuration schema.

Changes

Cohort / File(s) Summary
Config Validation
pkg/config/config.go
Reordered ValidateConfig to validate Role ARN first, then conditionally validate ExternalID only when GlobalRoleArnField is non-empty. Removed ExternalIdField from exported configuration schema and field dependencies.
Connector Logic
pkg/connector/connector.go
Added single-hop assume role path in getCallingConfig: when globalRoleARN is empty and roleARN is provided, directly assumes into roleARN using STS with optional ExternalID, caches credentials, and returns config. Two-hop flow remains as alternative path.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Config as Config Validator
    participant Connector
    participant STS as AWS STS

    rect rgba(100, 200, 100, 0.5)
    Note over Client,STS: Single-Hop Assume Role Flow (New)
    Client->>Config: Validate with roleARN only
    Config->>Config: Validate roleARN
    Config-->>Client: Validation complete
    Client->>Connector: getCallingConfig(roleARN, empty globalRoleARN)
    Connector->>STS: AssumeRole(roleARN, optional ExternalID)
    STS-->>Connector: Credentials
    Connector->>Connector: Cache credentials
    Connector-->>Client: Config with credentials
    end

    rect rgba(100, 100, 200, 0.5)
    Note over Client,STS: Two-Hop Assume Role Flow (Existing)
    Client->>Config: Validate with globalRoleARN
    Config->>Config: Validate roleARN
    Config->>Config: Validate ExternalID
    Config-->>Client: Validation complete
    Client->>Connector: getCallingConfig(roleARN, globalRoleARN)
    Connector->>STS: AssumeRole(globalRoleARN)
    STS-->>Connector: Instance credentials
    Connector->>STS: AssumeRole(roleARN) from instance account
    STS-->>Connector: Customer account credentials
    Connector->>Connector: Cache credentials
    Connector-->>Client: Config with credentials
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through paths so fine,
One leap direct, or two-step line,
Config whispers which way to go,
No External walls where roles don't show,
Credentials cached, the journey's done!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add single-hop assume role for self-hosted deployments' clearly and concisely summarizes the main change—adding single-hop assume role support for self-hosted deployments, which aligns with the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

🧪 Unit Test Generation v2 is now available!

We have significantly improved our unit test generation capabilities.

To enable: Add this to your .coderabbit.yaml configuration:

reviews:
  finishing_touches:
    unit_tests:
      enabled: true

Try it out by using the @coderabbitai generate unit tests command on your code files or under ✨ Finishing Touches on the walkthrough!

Have feedback? Share your thoughts on our Discord thread!


Comment @coderabbitai help to get the list of available commands and usage tips.

@johnallers johnallers merged commit 050c7f7 into ConductorOne:main Jan 30, 2026
12 of 14 checks passed
@johnallers johnallers changed the title feat: add single-hop assume role for self-hosted deployments [CXP-35] feat: add single-hop assume role for self-hosted deployments Jan 30, 2026
@johnallers
Copy link
Contributor

@abebars Thanks for your contribution! I've tagged v0.1.9 and it should be available now.

@abebars abebars deleted the feat/single-hop-assume-role branch January 30, 2026 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants