Skip to content

Configure AWS Bedrock Access

Kindo reaches AWS Bedrock through LiteLLM. On EKS, the recommended credential is an IAM role that LiteLLM assumes through IAM Roles for Service Accounts (IRSA), so no long-lived AWS keys are stored in Kindo. This page covers the four pieces that role needs:

  1. A trust policy that lets the LiteLLM service account assume the role.
  2. A permissions policy that allows invoking the models you register.
  3. Model enablement for third-party models sold through AWS Marketplace.
  4. Wiring the role into the install so the LiteLLM service account carries it.

If you register Bedrock models with an AWS access key and secret instead, skip the trust policy and wiring steps. The IAM user behind those keys still needs the permissions and model enablement described here.

Throughout this page, <account-id> is your AWS account ID, <region> is the region LiteLLM calls Bedrock in (the AWS region set on each Bedrock model you register), <cluster-region> is the region of your EKS cluster, and <oidc-id> is the ID of the cluster’s OIDC provider. <region> and <cluster-region> are often the same, but they do not have to be.

LiteLLM runs as the service account litellm in the litellm namespace. The role’s trust policy must allow exactly that service account to assume it through the cluster’s OIDC provider:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<account-id>:oidc-provider/oidc.eks.<cluster-region>.amazonaws.com/id/<oidc-id>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<cluster-region>.amazonaws.com/id/<oidc-id>:aud": "sts.amazonaws.com",
"oidc.eks.<cluster-region>.amazonaws.com/id/<oidc-id>:sub": "system:serviceaccount:litellm:litellm"
}
}
}
]
}

Find <oidc-id> with aws eks describe-cluster --region <cluster-region> --name <cluster> --query cluster.identity.oidc.issuer; it is the last path segment of the issuer URL. The cluster’s OIDC provider must already be registered in IAM.

LiteLLM uses two Bedrock actions, and the role needs both:

  • bedrock:InvokeModel — non-streaming calls, including embeddings.
  • bedrock:InvokeModelWithResponseStream — streaming calls. Chat streams its responses, so a role without this action fails every chat turn even when InvokeModel works.

How cross-region inference profiles are authorized

Section titled “How cross-region inference profiles are authorized”

A model ID that starts with a geography prefix such as us., eu., apac., au., or jp. is a cross-region inference profile. LiteLLM calls the profile in <region>, and Bedrock serves each request from one of several regions behind it. The destination regions depend on the profile and on the region you call it from: us. profiles called from us-east-1, us-east-2, or us-west-2 commonly route across those three, while the same profile called from another region can include more.

IAM checks two resources on every such request:

  • The inference profile in <region>: arn:aws:bedrock:<region>:<account-id>:inference-profile/<profile-id>.
  • The foundation model in whichever region serves the request: arn:aws:bedrock:<serving-region>::foundation-model/<model-id>. Foundation-model ARNs have no account ID.

Grant the foundation model in every region the profile routes to. A policy that omits one of them fails intermittently, whenever Bedrock happens to route a request there. Build the list from the profile itself rather than assuming it:

Terminal window
aws bedrock get-inference-profile --region <region> \
--inference-profile-identifier <profile-id> \
--query 'models[].modelArn'

Profiles that start with global. route to any commercial region and need three grants: the profile ARN in <region>, the foundation model in <region>, and the region-less foundation-model ARN arn:aws:bedrock:::foundation-model/<model-id>. Add the region-less ARN alongside the <region> grant, not in place of it; a policy with only the region-less ARN returns AccessDeniedException. The rest of this page covers geographic profiles; for the exact global. policy, follow AWS’s global cross-region inference documentation.

Some models can only be called through an inference profile. aws bedrock get-foundation-model --model-identifier <model-id> --query modelDetails.inferenceTypesSupported returns INFERENCE_PROFILE for those, and ON_DEMAND for models you can also call directly in a region. To find the profile for a model, list the profiles available in <region>:

Terminal window
aws bedrock list-inference-profiles --region <region> --type-equals SYSTEM_DEFINED \
--query 'inferenceProfileSummaries[].inferenceProfileId'

This policy allows one chat model through its US inference profile and one embedding model called directly in <region>. It assumes the profile routes to the three regions listed; replace them with the output of get-inference-profile for your profile. The bedrock:InferenceProfileArn condition limits the foundation-model grant to requests that arrive through that profile, so the role cannot call the chat model directly or through any other profile.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ChatModelProfile",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": ["arn:aws:bedrock:<region>:<account-id>:inference-profile/us.anthropic.claude-sonnet-4-6"]
},
{
"Sid": "ChatModelRoutedRegions",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": [
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-6",
"arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-sonnet-4-6",
"arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-sonnet-4-6"
],
"Condition": {
"StringEquals": {
"bedrock:InferenceProfileArn": ["arn:aws:bedrock:<region>:<account-id>:inference-profile/us.anthropic.claude-sonnet-4-6"]
}
}
},
{
"Sid": "EmbeddingModel",
"Effect": "Allow",
"Action": "bedrock:InvokeModel",
"Resource": "arn:aws:bedrock:<region>::foundation-model/amazon.titan-embed-text-v2:0"
}
]
}

To add another model, add its profile ARN to the first statement’s Resource list, its per-region foundation-model ARNs to the second statement, and its profile ARN to the condition’s list. With several models in one statement pair, any listed profile can reach any listed foundation model; for a strict one-to-one mapping, give each model its own pair of statements. Every model you register in Kindo needs a grant — including the embedding model behind the reserved embedding-generator serving name, which is easy to leave out of a policy written for chat models.

A broader policy works too, at the cost of letting the role invoke any Bedrock model the account has enabled:

{
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": [
"arn:aws:bedrock:*::foundation-model/*",
"arn:aws:bedrock:*:<account-id>:inference-profile/*"
]
}

In commercial AWS regions, Bedrock enables every serverless model for the account by default; there is no per-model access page to visit. AWS GovCloud (US) works differently; see GovCloud below. Models from third-party providers sold through AWS Marketplace — Anthropic, OpenAI, Cohere, Writer, Stability, TwelveLabs, and others — still need a one-time Marketplace subscription per model, per account. Models from Amazon, Meta, Mistral AI, DeepSeek, and Qwen are not sold through Marketplace and need no subscription.

The first call to an unsubscribed model starts the subscription in the background, using the permissions of whoever made the call. Calls can succeed for up to about 15 minutes while it is pending. If the caller lacks Marketplace permissions the subscription fails, and later calls return AccessDeniedException naming aws-marketplace:ViewSubscriptions and aws-marketplace:Subscribe. This is why a newly registered model can work for a while and then stop.

Choose one of two ways to subscribe:

  • An administrator subscribes once (recommended). Someone with Marketplace permissions enables each model before it is registered in Kindo, either by opening the model in the Bedrock console’s model catalog or with the Bedrock API: list-foundation-model-agreement-offers returns the model’s offer token, and create-foundation-model-agreement accepts it. After that, the LiteLLM role needs no Marketplace permissions.
  • Let the LiteLLM role subscribe on first use. Add aws-marketplace:Subscribe, aws-marketplace:Unsubscribe, and aws-marketplace:ViewSubscriptions to the role. This also lets the workload subscribe the account to any other Marketplace model it is asked to call. To limit that, scope aws-marketplace:Subscribe with the aws-marketplace:ProductId condition key.

Either way, a subscription accepts the provider’s end-user license agreement on behalf of the account, and the account needs a valid payment method for Marketplace purchases.

Anthropic models also require a one-time use-case form per account (or per AWS Organization, from the management account). Submit it by opening any Anthropic model in the Bedrock console’s model catalog, or with the Bedrock put-use-case-for-model-access API.

Check a model’s state with:

Terminal window
aws bedrock get-foundation-model-availability --region <region> --model-id <model-id>

agreementAvailability.status is AVAILABLE once the model is subscribed and NOT_AVAILABLE until then. authorizationStatus is AUTHORIZED when IAM and organization policies allow access.

In AWS GovCloud (US), models are not enabled by default:

  • Enable each model on the Bedrock console’s Model access page in us-gov-west-1.
  • For third-party models, also enable the model in the commercial AWS account linked to the GovCloud account, by invoking it or subscribing to it there in us-east-1 or us-west-2.
  • Write every ARN on this page with the arn:aws-us-gov: partition instead of arn:aws:, including the OIDC provider, inference profile, and foundation-model ARNs.

Annotate the LiteLLM service account with the role ARN through a Helm override, then apply it. Run both commands from the directory that holds the install’s install-contract.yaml and environment-bindings.yaml; kindo config helm-override reads them to validate and apply the change.

Terminal window
kindo config helm-override set litellm \
'serviceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:aws:iam::<account-id>:role/<role-name>'
kindo config helm-override apply litellm

Escape the dots in the annotation key (eks\.amazonaws\.com) so the path is read as one key, and quote the whole argument so the shell keeps the backslashes. The override is stored in the cluster’s install configuration, so kindo upgrade keeps applying it.

EKS injects the role’s web-identity credentials only into pods that start after the annotation is in place, so restart LiteLLM, then confirm the annotation and the role its pods carry:

Terminal window
kubectl -n litellm rollout restart deployment/litellm
kubectl -n litellm rollout status deployment/litellm
kubectl -n litellm get serviceaccount litellm \
-o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
kubectl -n litellm exec deploy/litellm -- printenv AWS_ROLE_ARN

Both commands print the role ARN. If printenv prints nothing, the pod started before the annotation existed.

Then register Bedrock models with the AWS access key ID and secret access key left empty; LiteLLM falls back to the role. See Prepare AI Models for the rest of each model’s registration.

Bedrock’s error text names the failing check. Match it against this table:

Error containsCauseFix
is not authorized to perform: bedrock:InvokeModelWithResponseStreamThe role grants InvokeModel but not the streaming actionAdd bedrock:InvokeModelWithResponseStream to the statements for that model
not authorized to perform: bedrock:InvokeModel on resource: ...inference-profile/...The inference profile is not in the policyAdd the profile ARN in <region>
not authorized to perform: bedrock:InvokeModel on resource: ...foundation-model/...The foundation model is missing for the region that served the requestAdd the foundation-model ARN for every region the profile routes to
no identity-based policy allows for the embedding modelThe policy only covers chat modelsGrant the embedding model (for example amazon.titan-embed-text-v2:0)
not authorized to perform the required AWS Marketplace actionsThe model was never subscribed, and the caller could not subscribe itSubscribe it once as an administrator, or grant the role the Marketplace actions, then retry after a few minutes
Unable to locate credentialsThe service account has no role annotation, or the pods started before itApply the Helm override, then restart the LiteLLM Deployment
assumed-role/<name> names a role other than yours, such as the node roleLiteLLM is not using the service account’s roleCheck the annotation and AWS_ROLE_ARN as in step 4, then restart the LiteLLM Deployment