Skip to content

feat: add identity.assumeRoleChainRef for a dynamic assume-role chain - #13

Merged
kaessert merged 4 commits into
mainfrom
hive/assume-role-chain-ref
Oct 22, 2026
Merged

kaessert merged 4 commits into
mainfrom
hive/assume-role-chain-ref

Conversation

@kaessert

Copy link
Copy Markdown
Contributor

What

Adds identity.assumeRoleChainRef, so the STS assume-role chain can be resolved at runtime from a spec. / status. / context. path, the same way regionRef, filtersRef and parametersRef already work.

identity:
  source: PodIdentity
  assumeRoleChainRef: context.[apiextensions.crossplane.io/environment].awsQuery.roleChain

Why

identity.assumeRoleChain is static step input. When one Composition is deployed to many control planes and each control plane must query through a different role (e.g. one AWS account or business unit per control plane), the only option today is to bake the chain into the function pod's base identity (e.g. the Pod Identity association), which gives every composition on that control plane the same role. The ref lets platform-controlled config (an EnvironmentConfig) pick the role per control plane while the Composition stays identical.

Behaviour

  • Path resolves → the list replaces the static assumeRoleChain; path absent → static chain is kept.
  • Entries use the AssumeRole field names (roleARN required; externalID, roleSessionName, tags, transitiveTagKeys). They are decoded as strictly as the Input itself (exact names, unknown fields rejected), so a typo like externalId is fatal instead of silently accepted.
  • Fatal, naming the ref and entry index: unrecognised prefix, non-list, empty list (would otherwise silently fall back to the base identity), non-object entry, missing roleARN, wrong field type.
  • buildAWSConfig is unchanged; resolution happens next to the other resolve*Ref calls.
  • README: field documented, plus a note to prefer a context. path fed from platform config over an XR spec. path, since the function's base identity is shared by every composition on the control plane.

Testing

  • Unit: TestRunFunctionResolvesAssumeRoleChainRef — override from context, spec and status (all five fields), absent path keeps static, seven malformed cases are fatal and never reach the AWS query.
  • crossplane render against a local STS stub: two-hop chains from context and spec produce the expected AssumeRole calls in order with all parameters, hop 2 signed with hop 1's credentials.
  • crossplane render against real AWS (temporary IAM roles, since deleted), 7/7:
    • 2-hop chain from context and from spec, with a required ExternalId on hop 1 and a transitive session tag that a tag-conditioned policy on hop 2 requires → query succeeds as hop 2
    • same chain without transitiveTagKeys → UnauthorizedOperation as hop 2 (proves the tag is what made the positive case work)
    • wrong ExternalId → AccessDenied on hop 1
    • cross-account hop
    • ref path absent → static chain used
    • empty list → fatal before any AWS call
  • Gate: go test ./..., golangci-lint v2.8.0 (0 issues), go generate + go mod tidy produce no diff.

Resolve the STS assume-role chain at runtime from a status./context./spec.
path, alongside regionRef/filtersRef/parametersRef, so one Composition can
target a different role per control plane (e.g. from an EnvironmentConfig).
The resolved list replaces the static assumeRoleChain; an absent path keeps
it. A malformed value is fatal and names the ref and the entry index.

- input: Identity.AssumeRoleChainRef (+ generated deepcopy and CRD).
- fn.go: resolveAssumeRoleChainRef + toAssumeRoleChain, called next to the
  other resolve*Ref calls; buildAWSConfig is unchanged.
- tests: context-sourced chain overrides static, absent keeps static,
  malformed values are fatal.
- docs: README input reference, dynamic queries, and a note on the shared
  base identity.

Signed-off-by: Tobias Kässer <tobias.kasser@upbound.io>
Decode each resolved entry with the same strict JSON options the SDK uses
for the static Input (exact field names, unknown fields rejected), so a typo
like externalId fails instead of being accepted. Reject an empty list, which
would otherwise drop the static chain and run the query as the base
identity. Cover the spec. and status. sources in the override test.

Signed-off-by: Tobias Kässer <tobias.kasser@upbound.io>

@jakubramut jakubramut left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two findings:

  1. an unresolved ref silently runs as the base identity - added as a separate comment
  2. the PR touches nothing under example/ while last PRs #11 and #12 did.

Comment thread fn.go
…tic chain

Previously, if assumeRoleChainRef's path didn't resolve and no static
assumeRoleChain was set, the query silently ran as the function's base
identity and reported success. A static chain is still used as the
explicit fallback; without one the step is now fatal.

Signed-off-by: Tobias Kässer <tobias.kasser@upbound.io>
Adds example/composition-assume-role-chain-ref.yaml (chain taken from the
environment context, as an EnvironmentConfig would supply it), a
`make render-assume-role ROLE_ARN=...` target kept out of `make render`
like render-irsa, and docs in example/README.md, following #11 and #12.

Signed-off-by: Tobias Kässer <tobias.kasser@upbound.io>
@kaessert

Copy link
Copy Markdown
Contributor Author

Thanks @jakubramut, both points addressed:

  1. Unresolved ref without a static chain is now fatal: f958926 (details in the inline reply).
  2. Examples, following feat: add new query types - DescribeRouteTables/DescribeSubnets/DescribeSecurityGroupRules #11/feat: adds a DescribeSecurityGroups queryType #12: 64d753a adds example/composition-assume-role-chain-ref.yaml (chain read from the environment context, as an EnvironmentConfig would supply it), a make render-assume-role ROLE_ARN=... target (kept out of make render like render-irsa, since it needs a role your creds can assume), and a section in example/README.md.

Verified with a real-AWS render: the cross-account role is assumed via the context value, and without the context value the step is fatal.

@jakubramut
jakubramut self-requested a review October 22, 2026 09:29

@jakubramut jakubramut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@kaessert
kaessert merged commit 7faa254 into main Oct 22, 2026
5 checks passed
@kaessert
kaessert deleted the hive/assume-role-chain-ref branch October 22, 2026 09:31
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.

2 participants