Cross-Account Bedrock Access: Platform Teams Without Shared Keys

Cross-Account Bedrock Access: Platform Teams Without Shared Keys

Product accounts should not share a Bedrock API key on Slack. Centralize model access in a platform account: SCPs, IAM role assumption, application inference profiles, and cost tags per team. Consumers get short-lived credentials and quotas — never long-lived keys.

⚡ TL;DR: Platform account owns Bedrock + Guardrails + Prompt Management. Product accounts assume BedrockConsumer via STS with external ID; tag every call team, service, env. Use application inference profiles for per-team routing and spend. Pair with LLM Cost Controls and AI Coding in VPC.

Account layout

org
├── platform-ai (Bedrock, Guardrails, KMS, CloudTrail lake)
├── product-payments (assume role → invoke)
└── product-growth (assume role → invoke)
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::111111111111:root" },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": { "sts:ExternalId": "payments-prod-extid" },
      "IpAddress": { "aws:SourceIp": "10.0.0.0/8" }
    }
  }]
}
{
  "Effect": "Allow",
  "Action": [
    "bedrock:InvokeModel",
    "bedrock:InvokeModelWithResponseStream",
    "bedrock:Converse",
    "bedrock:ConverseStream"
  ],
  "Resource": [
    "arn:aws:bedrock:*::foundation-model/anthropic.*",
    "arn:aws:bedrock:*:999999999999:inference-profile/payments-*"
  ],
  "Condition": {
    "StringEquals": {
      "aws:RequestTag/team": "payments",
      "aws:RequestTag/env": ["prod", "staging"]
    }
  }
}

Consumer SDK pattern

import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";
import { fromTemporaryCredentials } from "@aws-sdk/credential-providers";

export function bedrockForTeam(team: string) {
  return new BedrockRuntimeClient({
    region: "us-east-1",
    credentials: fromTemporaryCredentials({
      params: {
        RoleArn: process.env.BEDROCK_CONSUMER_ROLE_ARN!,
        RoleSessionName: `bedrock-${team}`,
        ExternalId: process.env.BEDROCK_EXTERNAL_ID!,
        Tags: [
          { Key: "team", Value: team },
          { Key: "env", Value: process.env.APP_ENV! },
        ],
      },
    }),
  });
}

❌ Hardcoding access keys in Parameter Store “shared with eng”.

SCPs that keep product accounts honest

Deny bedrock:* in product accounts except through VPC endpoints to the platform pattern you choose — or deny local Bedrock entirely and force cross-account invoke. Deny iam:CreateAccessKey for humans in prod OUs.

Attribution and quotas

Mechanism Purpose
Application inference profiles Per-team model routing + metering
Cost allocation tags Showback/chargeback
CloudWatch metrics by team Burn alerts
SCPs Prevent shadow Bedrock in rogue accounts

See spend alerts in LLM Cost Controls.

Closing checklist

✅ Dos
– ✅ Central Bedrock in platform account
– ✅ STS assume-role + external ID + session tags
– ✅ Inference profiles per team
– ✅ Guardrails applied centrally
– ✅ CloudTrail + spend dashboards by tag

❌ Don’ts
– ❌ Don’t share long-lived access keys across teams
– ❌ Don’t allow untagged InvokeModel
– ❌ Don’t let product accounts create their own foundation-model wildcards unchecked
– ❌ Don’t skip ExternalId on the trust policy

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

2 Comments

Leave a Reply