Most mid-market teams still move data between clouds the same way they did a decade ago: mint a long-lived AWS access key, drop it in a secret store on the other cloud, and hope rotation actually happens. Cross-cloud identity federation replaces that standing credential with a short-lived AWS role assumption, issued only when a specific workload on the other cloud presents a signed identity token.
We have shipped this pattern in production for a GCP service account authenticating into AWS IAM. AWS leads the architecture. The other cloud is the identity source, not a second specialty we claim to operate at the same depth.
Why Standing Keys Fail the Audit and the Incident
A static AWS key in another cloud's secret manager fails two tests at once.
First, the audit: SOC 2 CC6.1 and CC6.2 ask whether access is authorized, attributable, and removed when it is no longer needed. A key that lives for months cannot answer "who used this, from where, and why" without a separate log reconstruction. Second, the incident: if that key leaks, the blast radius is the full IAM policy attached to the IAM user, for as long as nobody notices.
Federation inverts both. AWS STS issues credentials that expire in minutes. CloudTrail records the role session name you set. Revoking access is an IAM trust-policy change, not a hunt through every secret store.
This is the same credential-elimination idea as replacing AWS access keys with OIDC — applied across a cloud boundary instead of a CI system.
The Trust Boundary We Actually Build
The durable pattern:
- The foreign-cloud workload holds only its own identity (a GCP service account, in the case we run).
- That identity requests a signed OIDC-style token from its native token service.
- AWS IAM trusts a tightly scoped issuer, audience, and subject claim — not "any identity from that cloud."
- STS
AssumeRoleWithWebIdentityreturns temporary credentials for one role, one session. - The role's permission policy is the minimum the workload needs, not a copy of an old IAM user.
The trust policy is the control that most tutorials leave wide open. A production trust policy names the exact subject (the service account email or unique ID), the exact audience, and often a condition on sts:ExternalId or source-identity if the calling system supports it.
# terraform: scoped trust for a single foreign-cloud identity
data "aws_iam_policy_document" "cross_cloud_trust" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.gcp.arn]
}
condition {
test = "StringEquals"
variable = "accounts.google.com:sub"
values = [var.gcp_service_account_unique_id]
}
condition {
test = "StringEquals"
variable = "accounts.google.com:aud"
values = [var.expected_audience]
}
}
}
If a second workload on the other cloud needs AWS, it gets a second role with its own trust conditions. Sharing one federated role across workloads is how you recreate the standing-key blast radius without the key.
Cutover Without an Outage
Teams stall on federation because they treat it as a flag flip. The sequence we use:
- Inventory the IAM user or access key currently used from the other cloud. Map every API action it actually calls (CloudTrail, not the policy document — policies lie).
- Stand up the OIDC provider, the role, and a permission policy that matches observed actions plus a small buffer for retries and pagination.
- Dual-run the workload: prefer federation, fall back to the old key only on STS errors, with a metric on fallback count.
- Disable the access key (do not delete yet) once fallback has been zero for a soak window.
- Delete the IAM user after the next access review cycle confirms nothing else assumed it.
Rollback is the disabled key, not a rebuild. That is why we disable before delete.
For the broader migration shape, see our workload identity federation migration guide.
Blast-Radius Limits Most Teams Skip
Federation is not automatically least privilege. We add four limits in every engagement:
- Session duration at the role, typically 15–60 minutes, never 12 hours "because the job might run long." Long jobs refresh the token.
- Permission boundary on the role so a future policy edit cannot grant
iam:*ors3:*by accident. - CloudTrail session tags (
sts:SourceIdentityor a customenvironmenttag) so an incident responder can filter one workload's calls. - Deny of long-lived key creation in the target account via SCP, so nobody "temporarily" recreates the old pattern.
Alaska-based delivery does not change the IAM math. It does mean we design for teams that do not have a dedicated identity-platform squad — the federation wiring has to be operable by a five-person engineering org, not a 40-person IAM COE.
What This Is Not
This is not a claim that we architect GKE, Cloud Run, or Azure Government as a primary specialty. AWS is the system of record for the role, the logs, and the permissions. The other cloud is an identity issuer we already know how to trust. If a later engagement needs a different issuer, the same trust-policy pattern applies; we do not market "multi-cloud" as an equal skill.
Talk to us about your credential elimination pilot → rutagon.com/contact or call 907-841-8407 / contact@rutagon.com.
Provider Registration
The IAM OIDC identity provider resource is account-global. Thumbprints and issuer URLs must match the real token issuer. We pin them in Terraform and alert if someone adds a second provider "for testing" in prod.
Session tags from the foreign token, where supported, flow into CloudTrail. If the issuer will not send useful subjects, we do not broaden the trust to sub=*. We fix the issuer or we do not federate.
Networking
The workload still needs network path to STS. That is ordinary. What teams forget is clock sync. Token nbf/exp failures look like "AWS is down." NTP on the foreign-cloud side is a runbook item.
Cross-cloud identity federation into AWS without a second provider in prod
Cross-cloud identity federation into AWS means the IAM OIDC identity provider resource is pinned in Terraform: issuer URL and thumbprints match the real token issuer. A second “test” provider in the prod account is a standing trust. AWS documents AssumeRoleWithWebIdentity. We condition on sub and audience, not sub=*.
Clock skew on the foreign-cloud side looks like “AWS is down.” NTP is a runbook item. Session tags from the foreign token, where supported, should appear in CloudTrail. If the issuer will not send a useful subject, we do not broaden the trust — we fix the issuer or we do not federate.
This is not a VPN. It is not a standing GCP service-account JSON key in Secrets Manager. It is a role assumption with a blast-radius permission set and a monitored AssumeRoleWithWebIdentity query. Cutover dual-runs the old key with a kill date.
Frequently Asked Questions
What is cross-cloud identity federation in AWS?
It is a pattern where a workload running outside AWS proves its identity with a short-lived token from its own cloud, then AWS STS issues temporary IAM role credentials. No AWS access key is stored on the other cloud.
Does federation replace a secrets manager?
It replaces standing AWS keys stored in that secrets manager. You may still store non-AWS secrets. The point is that AWS credentials should be minted at assume-role time, not copied into another vault.
How do auditors sample this control?
They typically pull the IAM trust policy, an example CloudTrail AssumeRoleWithWebIdentity event, and proof that the old access key is inactive or deleted. A screenshot of a secret named AWS_ACCESS_KEY_ID still counts against you.
Can we federate more than one foreign-cloud identity into the same AWS account?
Yes. Each identity should assume a dedicated role with its own trust conditions and permission policy. One shared federated role is the failure mode we refuse to ship.
What breaks first during cutover?
Clock skew and audience mismatches. Token issuers are strict about aud and expiry. We always dual-run with a fallback metric before disabling the old key so a bad audience string does not take production down on a Friday.