← All work

Identity · Cross-cloud federation · Amazon Bedrock

Cross-cloud workload identity for Amazon Bedrock

An internal AI chat platform on Kubernetes in Azure needed Amazon Bedrock as a second inference provider. The design goal was zero static credentials in production, with the provider’s data-retention controls locked down. I owned the AWS side and was the client-facing technical contact for it: architecture, cross-cloud identity, documentation, and handover.

Role
Owner of the AWS side; client-facing technical contact
Team
Six
Duration
About eight weeks of active engineering
Status
Delivered with a documented handover
0
Quota-increase tickets: applied quotas already met the stated throughput target
measured
4
Material IAM defects caught in pre-deployment review; none reached production
measured
1
Alarm defect caught before handover: as designed, it could never have fired
measured
0 min
Production downtime from the live IAM redesign, confirmed through CloudTrail and live user testing
measured

Problem

The platform needed a second inference provider for capacity headroom, wired into the same chat front end. Two things made that harder than it sounds. The workload ran on Kubernetes in one cloud and had to authenticate to a managed AI service in another. And nobody wanted a static credential sitting in production to make that work.

What I built

  • An IAM OIDC identity provider registered against the cluster’s issuer.
  • An inference role whose trust policy requires the exact service-account subject and the STS audience. The common shortcut, a missing or wildcarded subject, lets any workload in the cluster assume the role.
  • Permissions scoped to one model provider’s family, across the three regions its cross-region inference profiles route to.
  • A rebuilt human identity layer: two purpose-separated managed policies and a permissions boundary, attached at group level, with self-service MFA enrollment, so the team could manage its own access within the boundary instead of filing a request for every change.
  • The whole build and every verification ran from the CLI, so each step was reproducible and diffable.
Federation flow: a Kubernetes workload presents its projected token to AWS STS, which verifies it against the IAM OIDC provider and checks the inference role's trust policy for issuer, exact subject, and audience, then returns short-lived credentials used to invoke Amazon Bedrock, where only the intended model family is allowed.Kubernetes workloadservice accountprojected tokeniss · sub · audAWS STSAssumeRoleWithWebIdentityexchanges the tokenIAM OIDC providertrusts cluster issuerInference roletrust policy requires✓ isscluster issuer✓ subexact namespace:sa✓ audsts.amazonaws.comAmazon Bedrockpermissions policyintended familyallowedother providersimplicitDenymutation actionsimplicitDenyprojected tokentemp credentialsverify signaturetrust policyInvokeModel · SigV4 · temp credentials
The workload’s projected service-account token is exchanged at STS for short-lived credentials. The role’s trust policy requires the exact service-account subject and the STS audience. Its permissions allow one model family across three regions and nothing else. Identifiers are omitted.

Before anything went live, the IAM policy simulator had to agree with the permissions policy:

aws iam simulate-principal-policyidentifiers removed
RequestDecision
Invoke · intended model family · each of three regions✓ allowed
Invoke · any other model provider✗ implicitDeny
Any mutation action✗ implicitDeny

Capacity check

I pulled applied quotas from the Service Quotas API instead of quoting published defaults. They already met the team’s stated throughput target, with the top-tier model class several times over it. That answered the capacity question the same day, with no quota-increase requests.

Connectivity design

The usual starting point for cross-cloud connectivity is a private tunnel. I priced two private-network designs, including routing, DNS, and monthly run cost, and recommended in writing that federated identity over the public endpoint was the better fit. A tunnel would have been permanent infrastructure for the organization to run. The priced options stayed in the decision record, so the choice can be revisited with real numbers.

Key decision

Static access key, or workload identity federation?

  • Long-term static key
    Fastest to set up. Leaves a long-lived credential and a new machine identity in production, which is what the design was meant to avoid.
  • Short-term credential
    No standing secret, but it inherits the generating identity’s permissions and doesn’t solve how the workload gets one.
  • ChosenWorkload identity federation
    No static secret in production. Depends on the platform team completing their side of the configuration, so the finish date wasn’t fully in my control.

Decision

Federation as the destination, with a static credential as a deliberate bridge: a 28-day hard expiry written into policy, and the permanent path built and verified before that date.

What broke

  • Alarm missing the model-ID dimension

    The service publishes invocation metrics only with a model-ID dimension. An alarm without that dimension matches no data, so it can never fire, and nothing about it looks broken at a glance. I caught it by confirming that real datapoints came back from the metrics API instead of trusting an alert-channel test.

    FixAlarms rebuilt to cover every model ID, each verified against returned datapoints before handover.

  • Nonexistent IAM actions in a draft policy

    The conversational API’s calls aren’t IAM actions of their own; they authorize as the invoke actions. A policy that references a nonexistent action raises no error. It simply never matches.

    FixSimulate every policy before deploying it.

  • Defects found in policy review

    Among them: privilege-management actions scoped to a wildcard resource, a trust-policy mutation on the allow side, and a permissions boundary wide enough that a repointed trust would have exposed object and secret reads.

    FixPre-deployment review of every draft policy. None of the four reached production.

  • Self-service path blocked by a deny

    The self-service credential path failed because the policy denied identity creation by design, and the deny was broader than it needed to be.

    FixFixed the same day by narrowing the deny to block identity creation only when the permissions boundary isn’t attached.

  • Issuer URL mismatch

    A trailing slash in the issuer URL broke the trust relationship, which requires an exact string match.

    FixFederation strings are copied from the source, never retyped.

  • Model terms that conflicted with data retention

    After handover, one newer model’s terms conflicted with the data-retention requirements the platform was built on. Enabling it was one command, but it would have reversed a policy decision.

    FixEscalated to the executive sponsor and the identity lead instead of executing it.

Handover

The handover document separated what was built and verified from what was in use, and defined the check that proves the federated path is carrying traffic. It was written for the team that would run the platform next.

Stack

  • IAM (OIDC provider, permissions boundaries, policy simulator)
  • STS AssumeRoleWithWebIdentity
  • Amazon Bedrock (cross-region inference profiles, data-retention controls)
  • CloudTrail
  • CloudWatch
  • SNS
  • AWS Budgets
  • Service Quotas
  • AKS workload identity
  • Entra ID
  • AWS CLI v2
  • boto3

Organizations, people, dates, and identifying environment details are omitted or generalized. Each figure carries a tag for the kind of evidence behind it.