fix(iac,indexer): provision AzureWebJobsStorage for the dev indexer#83
Merged
Conversation
The dev indexer container reported 'azure.functions.webjobs.storage:
Unhealthy — Unable to create client for AzureWebJobsStorage' every
30s because there was no storage account at all in rg-bt-dev and the
Functions runtime expects AzureWebJobsStorage at startup. Container
Apps itself reported the revision Healthy (the Cosmos change-feed
trigger uses Cosmos's own lease container for state, not Azure
Storage), so functionally the indexer was still working — but the
log noise was constant and the runtime health surface stayed red.
Fix:
- New azurerm_storage_account.indexer_webjobs in the dev composition
(Standard_LRS, StorageV2, shared_access_key_enabled = false,
public_network_access_enabled gated on the existing
data_services_public_access_enabled variable so it tracks the same
toggle as Cosmos / Search / KV).
- Workload UAMI granted Storage Blob Data Owner on the new account —
covers the runtime's blob-container-create needs.
- functions-container-app module gains a new variable
azure_webjobs_storage_account_name and injects three env vars on
the container:
AzureWebJobsStorage__accountName = <storage account name>
AzureWebJobsStorage__credential = managedidentity
AzureWebJobsStorage__clientId = <workload UAMI client id>
- Indexer module call depends_on the role assignment so the data-plane
role propagates via AAD before the Functions runtime first connects.
No shared keys, no connection strings — matches the project's
managed-identity-only stance.
Test/prod compositions don't yet wire the indexer module, so no
changes needed there. When they do, they'll pass the same variable
shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ge account
CI's checkov scan tripped four rules on azurerm_storage_account.indexer_webjobs:
CKV_AZURE_190 Ensure that Storage blobs restrict public access
CKV2_AZURE_47 Ensure storage account is configured without blob
anonymous access
CKV2_AZURE_33 Ensure storage account is configured with private
endpoint
CKV2_AZURE_1 Ensure storage for critical data are encrypted with
Customer Managed Key
Fix:
- Two of them (190 + 47) are real and cheap to satisfy — set
`allow_nested_items_to_be_public = false` on the account so anonymous
blob access is blocked at the account level. The Functions runtime
never needs anonymous reads.
- CKV2_AZURE_33 (private endpoint) is allowlisted with justification:
the Container Apps Environment hosting the indexer has
`vnetConfig: null`, so even if a private endpoint existed the indexer
couldn't reach it. Same architectural posture as Cosmos / AI Search
in dev. Allowlist entry calls out the dependency on a future CAE
vnet integration so the rule gets re-evaluated when that happens.
- CKV2_AZURE_1 (CMK) is allowlisted with justification: the account
holds only Functions runtime internal state — no operator data, no
audit log, no registry payload (those live in Cosmos). Azure-managed
keys are the documented posture for AzureWebJobsStorage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The project's 'no inline IAM in env compositions' lint (scripts/lint-iac-inline-iam.sh) rejected the inline azurerm_role_assignment.workload_uami_indexer_webjobs_blob_owner in iac/environments/dev/main.tf. The conventional path is to add the role to the workload-identity module's assigned_azure_rbac input map. Fix: - Add a new 'indexer-webjobs-blob-owner' entry to the existing assigned_azure_rbac map (Storage Blob Data Owner on the new storage account) — same pattern as acr-pull, kv-secrets-user, and monitoring-metrics-publisher. - Drop the inline azurerm_role_assignment resource block. - Indexer module depends_on the workload_identity module (covers the new role assignment + the existing ones). Behavior identical to the previous attempt; this is purely a project- convention cleanup so the lint passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OpenTofu plan —
|
| Rule | Status | Detail |
|---|---|---|
| BT-IAC-001 | PASS | BT-IAC-001: PASS |
| BT-IAC-002 | SKIP (env 'dev' is non-prod; rule is prod-only per Q2c) | BT-IAC-002: SKIP (env 'dev' is non-prod; rule is prod-only per Q2c) |
| BT-IAC-003 | PASS | BT-IAC-003: PASS |
| BT-IAC-004 | PASS | BT-IAC-004: PASS |
| BT-IAC-005 | PASS | BT-IAC-005: PASS |
| BT-IAC-006 | PASS | BT-IAC-006: PASS |
| BT-IAC-007 | PASS | BT-IAC-007: PASS |
Totals: 7 pass · 0 fail · 0 setup error(s)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Dev indexer container has been continuously reporting:
Root cause: there's no storage account in
rg-bt-devand the Functions runtime expectsAzureWebJobsStorageto be configured at startup. The indexer's only trigger is the Cosmos change-feed (which uses Cosmos's own lease container for state), so functionally it kept working — Container Apps itself reported the revisionHealthyand the listener IS running:…but the log floor was an
Unhealthyline every 30s, and the runtime health surface stayed red.The user's hypothesis ("could be a network access issue") was reasonable but ruled out:
az storage account list -g rg-bt-devreturned[]. The fix is to actually create the storage account.Changes
New resource in
iac/environments/dev/main.tf:functions-container-appmodule gains one new variableazure_webjobs_storage_account_nameand three new env vars on the indexer container:No shared keys, no connection strings — same managed-identity stance as Cosmos / KV / ACR / Search across the dev composition.
Indexer module call in dev gains
depends_onthe role assignment so the data-plane role propagates via AAD before the Functions runtime first connects.Files
iac/environments/dev/main.tf(+ storage + role + module var + depends_on)iac/modules/functions-container-app/variables.tf(+ new variable)iac/modules/functions-container-app/main.tf(+ 3 env vars)iac/modules/functions-container-app/README.md(terraform-docs regen)Test plan
tofu validateclean inenvironments/devterraform-docsregenerated for the moduleaz containerapp logs show -g rg-bt-dev --name ca-bt-dev-indexer --tail 30 --type console | grep healthyshows no morewebjobs.storage: UnhealthyRisks / out of scope
🤖 Generated with Claude Code