Skip to content

Commit 376a646

Browse files
authored
Fix Azure deploy Terraform state authorization permanently (#55)
* fix(ci): harden terraform state auth and retry behavior * fix(ci): restore state network baseline and scope RBAC * fix(ci): enforce secure state-network baseline
1 parent 20143e5 commit 376a646

1 file changed

Lines changed: 56 additions & 25 deletions

File tree

.github/workflows/azd-deploy.yml

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -327,17 +327,26 @@ jobs:
327327
--name "$TFSTATE_STORAGE_ACCOUNT" \
328328
--query publicNetworkAccess -o tsv)"
329329
330+
current_default_action="$(az storage account show \
331+
--resource-group "$TFSTATE_RESOURCE_GROUP" \
332+
--name "$TFSTATE_STORAGE_ACCOUNT" \
333+
--query networkRuleSet.defaultAction -o tsv)"
334+
330335
if [ "$current_access" = "Disabled" ]; then
331-
echo "Enabling public network access on Terraform state storage account for CI connectivity."
332-
az storage account update \
333-
--resource-group "$TFSTATE_RESOURCE_GROUP" \
334-
--name "$TFSTATE_STORAGE_ACCOUNT" \
335-
--public-network-access Enabled \
336-
--only-show-errors >/dev/null
336+
echo "Terraform state storage account has publicNetworkAccess=Disabled."
337+
echo "Permanent fix required: keep secure baseline and provide CI reachability (for example via approved runner egress/network path)."
338+
exit 1
339+
fi
340+
341+
if [ "$current_default_action" = "Deny" ]; then
342+
echo "Terraform state storage account has networkRuleSet.defaultAction=Deny."
343+
echo "Permanent fix required: keep secure baseline and provide CI reachability (for example via approved runner egress/network path)."
344+
exit 1
337345
fi
338346
339347
- name: Ensure Terraform state RBAC for OIDC principal
340348
env:
349+
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
341350
TFSTATE_RESOURCE_GROUP: ${{ secrets.TERRAFORM_STATE_RESOURCE_GROUP }}
342351
TFSTATE_STORAGE_ACCOUNT: ${{ secrets.TERRAFORM_STATE_STORAGE_ACCOUNT }}
343352
TFSTATE_CONTAINER: ${{ secrets.TERRAFORM_STATE_CONTAINER }}
@@ -349,11 +358,17 @@ jobs:
349358
--name "$TFSTATE_STORAGE_ACCOUNT" \
350359
--query id -o tsv)"
351360
352-
ACCESS_TOKEN="$(az account get-access-token \
353-
--resource https://management.azure.com/ \
354-
--query accessToken -o tsv)"
361+
CONTAINER_SCOPE="${STORAGE_SCOPE}/blobServices/default/containers/${TFSTATE_CONTAINER}"
355362
356-
PRINCIPAL_OBJECT_ID="$(ACCESS_TOKEN="$ACCESS_TOKEN" python -c "import base64,json,os; t=os.environ.get('ACCESS_TOKEN',''); p=t.split('.'); payload=(p[1] + '=' * (-len(p[1]) % 4)) if len(p) > 1 else ''; print(json.loads(base64.urlsafe_b64decode(payload.encode('utf-8')).decode('utf-8')).get('oid','') if payload else '')")"
363+
PRINCIPAL_OBJECT_ID="$(az ad sp show --id "$AZURE_CLIENT_ID" --query id -o tsv 2>/dev/null || true)"
364+
365+
if [ -z "$PRINCIPAL_OBJECT_ID" ]; then
366+
ACCESS_TOKEN="$(az account get-access-token \
367+
--resource https://management.azure.com/ \
368+
--query accessToken -o tsv)"
369+
370+
PRINCIPAL_OBJECT_ID="$(ACCESS_TOKEN="$ACCESS_TOKEN" python -c "import base64,json,os; t=os.environ.get('ACCESS_TOKEN',''); p=t.split('.'); payload=(p[1] + '=' * (-len(p[1]) % 4)) if len(p) > 1 else ''; print(json.loads(base64.urlsafe_b64decode(payload.encode('utf-8')).decode('utf-8')).get('oid','') if payload else '')")"
371+
fi
357372
358373
if [ -z "$PRINCIPAL_OBJECT_ID" ]; then
359374
echo "Unable to resolve OIDC principal object id from access token."
@@ -364,25 +379,18 @@ jobs:
364379
--assignee-object-id "$PRINCIPAL_OBJECT_ID" \
365380
--assignee-principal-type ServicePrincipal \
366381
--role "Storage Blob Data Contributor" \
367-
--scope "$STORAGE_SCOPE" \
368-
--only-show-errors >/dev/null || true
369-
370-
az role assignment create \
371-
--assignee-object-id "$PRINCIPAL_OBJECT_ID" \
372-
--assignee-principal-type ServicePrincipal \
373-
--role "Storage Blob Data Owner" \
374-
--scope "$STORAGE_SCOPE" \
382+
--scope "$CONTAINER_SCOPE" \
375383
--only-show-errors >/dev/null || true
376384
377-
ASSIGNMENT_COUNT="$(az role assignment list \
385+
CONTAINER_ASSIGNMENT_COUNT="$(az role assignment list \
378386
--assignee-object-id "$PRINCIPAL_OBJECT_ID" \
379-
--scope "$STORAGE_SCOPE" \
387+
--scope "$CONTAINER_SCOPE" \
380388
--role "Storage Blob Data Contributor" \
381389
--query 'length(@)' -o tsv)"
382390
383-
if [ "$ASSIGNMENT_COUNT" = "0" ]; then
384-
echo "OIDC principal is missing 'Storage Blob Data Contributor' on Terraform state storage account."
385-
echo "Grant role on scope: $STORAGE_SCOPE"
391+
if [ "$CONTAINER_ASSIGNMENT_COUNT" = "0" ]; then
392+
echo "OIDC principal is missing 'Storage Blob Data Contributor' on Terraform state container scope."
393+
echo "Grant role on scope: $CONTAINER_SCOPE"
386394
exit 1
387395
fi
388396
@@ -391,7 +399,12 @@ jobs:
391399
max_attempts=20
392400
attempt=1
393401
while [ "$attempt" -le "$max_attempts" ]; do
394-
if az storage blob list \
402+
if az storage container show \
403+
--account-name "$TFSTATE_STORAGE_ACCOUNT" \
404+
--name "$TFSTATE_CONTAINER" \
405+
--auth-mode login \
406+
--only-show-errors >/dev/null 2>&1 && \
407+
az storage blob list \
395408
--account-name "$TFSTATE_STORAGE_ACCOUNT" \
396409
--container-name "$TFSTATE_CONTAINER" \
397410
--auth-mode login \
@@ -568,6 +581,24 @@ jobs:
568581
while [ "$attempt" -le "$max_attempts" ]; do
569582
echo "Provision attempt $attempt/$max_attempts"
570583
584+
if ! az storage blob list \
585+
--account-name "$RS_STORAGE_ACCOUNT" \
586+
--container-name "$RS_CONTAINER_NAME" \
587+
--auth-mode login \
588+
--num-results 1 \
589+
--only-show-errors >/dev/null 2>&1; then
590+
if [ "$attempt" -eq "$max_attempts" ]; then
591+
echo "Terraform state data-plane access is still unavailable after $max_attempts attempts."
592+
exit 1
593+
fi
594+
595+
sleep_seconds=$((attempt * 60))
596+
echo "State data-plane access not ready yet. Waiting ${sleep_seconds}s before retry."
597+
sleep "$sleep_seconds"
598+
attempt=$((attempt + 1))
599+
continue
600+
fi
601+
571602
set +e
572603
provision_output="$(azd provision --no-prompt --debug 2>&1)"
573604
provision_exit=$?
@@ -585,7 +616,7 @@ jobs:
585616
exit 1
586617
fi
587618
588-
if ! echo "$provision_output" | grep -Eq 'RequestConflict|Failed to create/update resource|ContainerAppOperationInProgress|Operation expired'; then
619+
if ! echo "$provision_output" | grep -Eq 'RequestConflict|Failed to create/update resource|ContainerAppOperationInProgress|Operation expired|AuthorizationFailure|Failed to get existing workspaces|listing blobs|terraform init failed'; then
589620
echo "Provision failed with a non-transient error; not retrying further."
590621
exit "$provision_exit"
591622
fi

0 commit comments

Comments
 (0)