Skip to content

feat: secure Terraform plan hand-off with Blob Storage - #51

Open
Git-PrinceNagar wants to merge 2 commits into
Azure:mainfrom
Git-PrinceNagar:feat/4174-secure-plan-storage
Open

feat: secure Terraform plan hand-off with Blob Storage#51
Git-PrinceNagar wants to merge 2 commits into
Azure:mainfrom
Git-PrinceNagar:feat/4174-secure-plan-storage

Conversation

@Git-PrinceNagar

@Git-PrinceNagar Git-PrinceNagar commented Sep 2, 2026

Copy link
Copy Markdown

Description

Moves Terraform plan hand-off between Azure Pipelines plan and apply stages from downloadable pipeline artifacts to a dedicated container in the private Azure Storage account used for Terraform state.

Related to Azure/Azure-Landing-Zones#4174.

Companion PRs:

Security and compatibility

  • use_storage_account_for_plan = true enables the secure path by default.
  • show_plan_in_pipeline_logs = false hides full plan output by default, including failed-plan output redaction.
  • Each run addresses its exact runs/$(Build.BuildId)/tfplan blob using Microsoft Entra ID; no storage keys, blob listing, or mutable latest alias are used.
  • Plan files and plan JSON remain under $(Agent.TempDirectory) and are excluded from the published module artifact.
  • Upload and download fail closed. The active plan blob is deleted after successful apply; failed or abandoned plans remain available for lifecycle cleanup.
  • plan_storage_retention_days defaults to 7 and is configurable for longer approval windows.
  • use_storage_account_for_plan = false preserves the legacy artifact hand-off.
  • The avm-res-storage-storageaccount dependency remains pinned at 0.6.8.

Implementation

  • Adds per-environment plan containers, naming/collision validation, lifecycle retention, RBAC, and variable-group publication.
  • Adds isolated workspace/upload/download/delete/cleanup helpers and wires them into CI/CD templates.
  • Removes plan JSON publication from PR validation and fixes the pre-existing $(Build.ArtifactsStagingDirectory) artifact-path typo.
  • Filters disabled identities before storage role-assignment module indexing.
  • Adds a 66-assertion contract suite, CI workflow, and generated documentation.

Testing evidence

  • avm pre-commit: sync, convention, transform, format, and docs pass.
  • terraform init -backend=false and terraform validate pass; only existing provider deprecation warnings are emitted.
  • tests/contract/Test-PipelinePlanStorage.ps1: 66 passed, 0 failed.
  • The equivalent generated accelerator pipeline was live-tested in a real Azure DevOps organization: 230 bootstrap resources deployed and all 8 planned scenarios passed, including the secure path, artifact absence, log gating, legacy fallback, failure retention, concurrency, policy merge safety, and teardown.
  • CodeQL, the plan-storage contract workflow, and the fork-safe PR check pass. The managed AVM workflow is disabled for fork PRs by repository policy.

Known limitations

  • Mocked unit tests cannot load the pinned storage module because Terraform mock_provider rejects its ephemeral azapi_resource_action (Test framework: add ephemeral resource support to mock_provider hashicorp/terraform#38608). The 66-assertion contract suite, live accelerator validation, plan inspection, and Azure read-back provide compensating coverage.
  • Azure Storage management policy is account-scoped and singleton. Existing rules are merged, and live testing confirmed an unrelated custom rule survived unchanged, but Terraform cannot enforce an upgrade preflight against an externally managed policy at plan time.

Type of Change

  • Non-module change (e.g. CI/CD, documentation, etc.)
  • Azure Verified Module updates:
    • Bugfix containing backwards compatible bug fixes
      • Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description.
      • The bug was found by the module author, and no one has opened an issue to report it yet.
    • Feature update backwards compatible feature updates.
    • Breaking changes.
    • Update to documentation

Checklist

  • I'm sure there are no other open Pull Requests for the same update/change
  • My corresponding pipelines / checks run clean and green without any errors or warnings
  • I did run all pre-commit checks

The managed AVM workflow is disabled for fork PRs by repository policy; it can run through the upstream release-branch process if maintainers require it.

Prince Nagar (BEYONDSOFT CONSULTING INC) and others added 2 commits September 2, 2026 16:26
Ports the GitHub production repo's secure plan-storage design
(commits dd24dec/12ea394 on the sibling GitHub bootstrap repo) to
the Azure DevOps pipelines, closing Azure/Azure-Landing-Zones#4174
for the Azure DevOps bootstrap module.

Terraform:
- variables.cicd.tf: plan_storage_retention_days,
  show_plan_in_pipeline_logs, use_storage_account_for_plan.
- locals.cicd.tf: plan_storage_container_names plus backend
  collision/duplicate-name validation lists.
- main.azure.storage.tf: plan-container validation resource, merge
  plan containers into the storage account, add a lifecycle rule for
  plan blob retention. Also fixes a pre-existing bug where the state
  container's role_assignments did not filter disabled identities.
- main.azuredevops.variable.groups.tf: publish
  USE_STORAGE_ACCOUNT_FOR_PLAN, SHOW_PLAN_IN_PIPELINE_LOGS, and
  (conditionally) PLAN_STORAGE_CONTAINER_NAME per environment.
- variables.environments.tf: bundled-CD identity validation.

Pipelines:
- New helpers/terraform-plan-{workspace,upload,download,delete,
  cleanup}.yaml isolate the plan blob lifecycle.
- terraform-plan.yaml / terraform-apply.yaml keep the existing OIDC
  auth blocks and route the plan file through Agent.TempDirectory
  instead of the sources directory.
- cd-template.yaml / ci-template.yaml gate log/artifact exposure on
  the two feature flags, drop tfplan/tfplan.json from the published
  artifact, and fix a pre-existing Build.ArtifactsStagingDirectory
  typo (missing 's').

Docs/tests:
- _header.md documents the new secure plan hand-off behavior.
- tests/contract/Test-PipelinePlanStorage.ps1: 66-assertion static
  contract test adapted for Azure Pipelines YAML (raw-text regex
  rather than object-model navigation, since ADO's "each" template
  expression syntax isn't a standard YAML construct).
- .github/workflows/plan-storage-contract.yml wires the contract
  test into CI.
- README.md regenerated via avm pre-commit.

use_storage_account_for_plan defaults to true (secure by default);
show_plan_in_pipeline_logs defaults to false. Storage account module
pin (avm-res-storage-storageaccount 0.6.8) unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Explain that retention must exceed the expected plan-to-apply approval wait and that expired plans should be regenerated.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant