Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions iac/environments/dev/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,35 @@ module "ai_search_registry_index" {
# AAD-only storage account here; the workload UAMI holds Storage Blob Data
# Owner on it via the role assignment below. No shared keys, no connection
# strings — managed-identity is the only auth path.

# Pipeline MI self-grant — required because the indexer storage account has
# `shared_access_key_enabled = false`, so the azurerm provider's post-create
# blob data-plane wait must use AAD (via `storage_use_azuread = true` on the
# provider). Without this grant, the AAD call from the provider 403s and
# `tofu apply` fails on the storage account resource. RG-scoped so the role
# assignment can be created BEFORE the storage account (the storage account
# resource block depends_on this grant + a propagation sleep below). Mirrors
# the `pipeline_kv_secrets_officer` pattern: per-env, RG-scoped, allowlisted
# in `scripts/lint-iac-inline-iam.sh` because the workload-identity module
# is parented on the workload UAMI and this is a pipeline grant.
resource "azurerm_role_assignment" "pipeline_storage_blob_data_owner" {
scope = azurerm_resource_group.this.id
role_definition_name = "Storage Blob Data Owner"
principal_id = data.azurerm_client_config.current.object_id
description = "Pipeline MI manages `azurerm_storage_account.indexer_webjobs` data-plane wait via AAD (shared keys disabled on the account)."
}

# Azure AD role assignments have eventual-consistency propagation. Without
# this sleep, the first `azurerm_storage_account` post-create data-plane
# wait races propagation of the pipeline grant and 403s — same shape as the
# KV `wait_for_kv_rbac_propagation` block above.
resource "time_sleep" "wait_for_storage_rbac_propagation" {
depends_on = [
azurerm_role_assignment.pipeline_storage_blob_data_owner,
]
create_duration = "60s"
}

resource "azurerm_storage_account" "indexer_webjobs" {
# Storage account names: globally unique, 3-24 lowercase alphanumerics.
# `stbtdev<suffix>` keeps us within the limit even at long suffixes.
Expand All @@ -721,6 +750,13 @@ resource "azurerm_storage_account" "indexer_webjobs" {
}

tags = local.shared_tags

# Ordering edge for the provider's post-create blob data-plane wait —
# the pipeline MI's Storage Blob Data Owner role must exist + propagate
# before the storage account is created.
depends_on = [
time_sleep.wait_for_storage_rbac_propagation,
]
}

# Storage Blob Data Owner for the workload UAMI is wired via the
Expand Down
10 changes: 10 additions & 0 deletions iac/environments/dev/providers.tf
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ terraform {
provider "azurerm" {
subscription_id = var.subscription_id

# Spec 006 indexer storage — keys are disabled on the new
# AzureWebJobsStorage account (`shared_access_key_enabled = false`).
# Without this flag, the provider's post-create data-plane wait for
# the Blob service uses key-based auth and 403s on
# `KeyBasedAuthenticationNotPermitted`, failing the apply. The flag
# tells azurerm to use AAD for data-plane operations on all storage
# accounts in this composition; key-based ops continue to work where
# they're still enabled.
storage_use_azuread = true

features {
resource_group {
prevent_deletion_if_contains_resources = true
Expand Down
15 changes: 14 additions & 1 deletion iac/platform-bootstrap/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ resource "azurerm_role_assignment" "pipeline_role_admin" {
# grants; this allowlist permits namespace-scope and resource-group-
# scope grants only via the existing scope-evaluation gates.
#
# Spec 006 indexer storage — AzureWebJobsStorage AAD access:
# b7e6dc6d-f1e8-4753-8033-0f276bb0955b Storage Blob Data Owner
# → granted to the workload identity on the indexer's
# AzureWebJobsStorage account so the Functions runtime can
# authenticate to its internal blob containers via AAD (the
# account has `shared_access_key_enabled = false`). The pipeline
# MI also needs this role on the same account so the azurerm
# provider's post-create blob data-plane wait succeeds with
# `storage_use_azuread = true`; that self-grant is wired inline
# in `iac/environments/dev/main.tf` (allowlist entry in
# `scripts/lint-iac-inline-iam.sh`).
#
# NOT in this list: Cosmos DB Built-in Data Contributor — Cosmos uses its
# OWN native RBAC surface (`azurerm_cosmosdb_sql_role_assignment`), not
# Azure RBAC (`azurerm_role_assignment`), so this condition does not govern
Expand All @@ -249,7 +261,8 @@ resource "azurerm_role_assignment" "pipeline_role_admin" {
4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0,
8ebe5a00-799e-43f5-93ac-243d3dce84a7,
3913510d-42f4-4e42-8a64-420c390055eb,
acdd72a7-3385-48ef-bd42-f606fba81ae7
acdd72a7-3385-48ef-bd42-f606fba81ae7,
b7e6dc6d-f1e8-4753-8033-0f276bb0955b
}
)
CONDITION
Expand Down
8 changes: 8 additions & 0 deletions scripts/lint-iac-inline-iam.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ ALLOWLIST=(
# Standing operator access for `kv_operator_object_ids`. Per-env operator
# set — not a workload grant, not a fit for the workload-identity module.
"azurerm_role_assignment.operator_kv_secrets_officer"

# Spec 006 indexer storage — pipeline MI's data-plane grant on the
# indexer's AzureWebJobsStorage account. Required because the account
# has `shared_access_key_enabled = false`, so the azurerm provider's
# post-create blob data-plane wait must use AAD. Same shape as the KV
# pipeline grant above; the workload-identity module is parented on
# the workload UAMI and this is a PIPELINE grant — can't be folded in.
"azurerm_role_assignment.pipeline_storage_blob_data_owner"
)

# `iac/environments/<env>/main.tf` only — submodules + helper files are
Expand Down
Loading