From de5b231682cc6403e2c3cc433ebd5f22ba36dccb Mon Sep 17 00:00:00 2001 From: Raul Cajias <81768037+cajias@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:31:31 +0200 Subject: [PATCH] fix(terraform): reject wildcard accounts in bedrock_model_arns The variable already documented that wildcards in the account segment are not accepted, and the validation's own error_message said they are rejected, but the regex used [^:]* there, so arn:aws:bedrock:*:*:imported-model/* passed and was copied verbatim into the task role's policy. Require the account segment to be empty -- the canonical form for AWS-owned foundation models, arn:aws:bedrock:*::foundation-model/anthropic.* -- or a literal 12-digit account id, which is what every inference-profile, imported-model and provisioned-model ARN carries. Two runs pin both sides: a wildcard account is rejected, and a concrete account in a cross-region inference-profile ARN still passes, so the tightening cannot quietly cost the form operators actually paste. --- terraform/litellm/aws/README.md | 10 ++++-- .../examples/default/terraform.tfvars.example | 6 ++-- .../litellm/aws/tests/bedrock.tftest.hcl | 31 +++++++++++++++++++ terraform/litellm/aws/variables.tf | 4 +-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/terraform/litellm/aws/README.md b/terraform/litellm/aws/README.md index 56c51300203..2d8987e891e 100644 --- a/terraform/litellm/aws/README.md +++ b/terraform/litellm/aws/README.md @@ -220,9 +220,13 @@ foundation model in whichever region has capacity, so it needs both ARNs above: the profile itself, and the foundation model wildcarded across every region the profile can route to -Every entry must be a Bedrock ARN. A bare `"*"` is rejected at plan time, since -it would let the proxy's task role invoke every Bedrock resource in the account, -including other teams' provisioned throughput and private imported models +Every entry must be a Bedrock ARN naming a resource type, with the account +segment either empty (foundation models, which AWS owns) or a literal 12-digit +account id. A bare `"*"`, and any wildcard in the account or resource-type +segment, is rejected at plan time, since it would let the proxy's task role +invoke every Bedrock resource in the account, including other teams' +provisioned throughput and private imported models. Wildcards inside the +resource segment are what scope a family of models, and stay allowed Two adjacent flags cover the rest of the Bedrock surface. `enable_bedrock_mantle` grants `bedrock-mantle:CreateInference`, which the `bedrock_mantle/...` models diff --git a/terraform/litellm/aws/examples/default/terraform.tfvars.example b/terraform/litellm/aws/examples/default/terraform.tfvars.example index 0883b7bad70..5b149a3990a 100644 --- a/terraform/litellm/aws/examples/default/terraform.tfvars.example +++ b/terraform/litellm/aws/examples/default/terraform.tfvars.example @@ -139,8 +139,10 @@ env = "stage" # reference them in proxy_config as `bedrock/`. A cross-region # inference profile needs both the profile ARN and the foundation-model ARN. # Every entry must name a Bedrock resource type ("foundation-model/...", -# "inference-profile/...", "imported-model/..."); "*", "arn:aws:bedrock:*", -# and "arn:aws:bedrock:*:*:*" are rejected at plan time. +# "inference-profile/...", "imported-model/...") and carry either an empty +# account segment or a literal 12-digit account id; "*", "arn:aws:bedrock:*", +# "arn:aws:bedrock:*:*:*" and "arn:aws:bedrock:*:*:imported-model/*" are +# rejected at plan time. # bedrock_model_arns = [ # "arn:aws:bedrock:*:111122223333:inference-profile/us.anthropic.*", # "arn:aws:bedrock:*::foundation-model/anthropic.*", diff --git a/terraform/litellm/aws/tests/bedrock.tftest.hcl b/terraform/litellm/aws/tests/bedrock.tftest.hcl index 827628dac70..640ade6613c 100644 --- a/terraform/litellm/aws/tests/bedrock.tftest.hcl +++ b/terraform/litellm/aws/tests/bedrock.tftest.hcl @@ -180,6 +180,37 @@ run "an_all_segments_wildcard_bedrock_arn_is_rejected" { ] } +# A wildcard in the account segment reads like a scoped ARN but matches every +# account the identity can reach, which is why the variable documents the account +# as "empty or a concrete id". The resource-type slug clause above does not catch +# it, so it needs its own guard and its own case. +run "an_account_wildcard_bedrock_arn_is_rejected" { + command = plan + + variables { + bedrock_model_arns = ["arn:aws:bedrock:us-east-1:*:imported-model/*"] + } + + expect_failures = [ + var.bedrock_model_arns, + ] +} + +# The mirror of the case above: tightening the account segment must not cost the +# ARN form operators actually paste for a cross-region inference profile. +run "a_concrete_account_inference_profile_arn_is_accepted" { + command = plan + + variables { + bedrock_model_arns = ["arn:aws:bedrock:*:111122223333:inference-profile/us.anthropic.*"] + } + + assert { + condition = length(aws_iam_policy.bedrock_invoke) == 1 + error_message = "A 12-digit account in an inference-profile ARN must stay valid." + } +} + run "a_china_partition_bedrock_arn_is_accepted" { command = plan diff --git a/terraform/litellm/aws/variables.tf b/terraform/litellm/aws/variables.tf index 442685b13d5..39e87593411 100644 --- a/terraform/litellm/aws/variables.tf +++ b/terraform/litellm/aws/variables.tf @@ -591,8 +591,8 @@ variable "bedrock_model_arns" { default = [] validation { - condition = alltrue([for a in var.bedrock_model_arns : can(regex("^arn:aws[a-z-]*:bedrock:[^:]*:[^:]*:[a-z-]+/", a))]) - error_message = "Every bedrock_model_arns entry must be a Bedrock ARN with a real resource type, e.g. arn:aws:bedrock:*::foundation-model/anthropic.*. Bare wildcards on the account or resource-type segment (\"*\", \"arn:aws:bedrock:*\", \"arn:aws:bedrock:*:*:*\") are rejected, because they would let the task role invoke every Bedrock resource in the account, including other teams' provisioned throughput and private imported models." + condition = alltrue([for a in var.bedrock_model_arns : can(regex("^arn:aws[a-z-]*:bedrock:[^:]*:([0-9]{12})?:[a-z-]+/", a))]) + error_message = "Every bedrock_model_arns entry must be a Bedrock ARN with a real resource type, e.g. arn:aws:bedrock:*::foundation-model/anthropic.*. The account segment must be empty (foundation models) or a literal 12-digit account id; wildcards on the account or resource-type segment (\"*\", \"arn:aws:bedrock:*\", \"arn:aws:bedrock:*:*:*\", \"arn:aws:bedrock:*:*:imported-model/*\") are rejected, because they would let the task role invoke every Bedrock resource in the account, including other teams' provisioned throughput and private imported models." } }