Skip to content

matsest/bicep-deploy-whatif-without-write-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bicep Deploy What-If Without Write Permissions Demo

Background

Since I started using ARM templates, and later Bicep, for infrastructure as code more than 5 years ago, there has been a filed issue for being able to do "what-if" deployments without requiring write permissions.

Why is this a problem?

For people with previous experience with Terraform (including myself), being able to do a plan or a form of dry-run of a deployment in a PR before merging to the main branch is often a desired pattern. This also applies to developers running their dry-runs locally. To be able to do this securely, it's definitely a requirement that the PR branch or local developer environment does not actually have write access to your production deployment environment.

Finally a possible solution?

So, after 5+ years of not much development, I recently learned in the latest Bicep community call that there actually was a recent feature released to support this. The only "official" news I've seen of this is the ambiguous changelog entry from Azure PowerShell:

  • Added ValidationLevel Parameter to WhatIf and Validate cmdlets for deployments

For Az.Resources 7.10, if we dig into the documentation for the new parameter, we see:

-ValidationLevel

Sets the validation level for validate/what-if. ValidationLevel can be Template (Skips provider validation), Provider (Performs full validation), or ProviderNoRbac (Performs full validation using RBAC read checks instead of RBAC write checks for provider validation).

Since there are few examples of using this and it's not very well documented elsewhere in the what-if documentation regarding what permissions are needed and such - let's see how it works!

Note

As of July 3rd, 2025, it's not released for Azure CLI (will be released in the upcoming 2.76 according to MS).

Demo setup

The following resources will be set up in this demo:

Azure:

  • A resource group (for demo purposes, to scope the role assignments used)
  • Two user-assigned managed identities - one with only Reader permissions and one with Contributor permissions
  • A Custom RBAC role that is similar to the Reader role but also has deployments/whatIf/action and deployments/validate/action.

GitHub:

  • A PR environment with environment secrets for the PR user-assigned identity
  • A main environment with environment secrets for the deploy user-assigned identity, including branch protection to only allow access from the main branch.
---
title: Demo Environment
---
flowchart LR;
    subgraph Azure;
        Reader["PR Managed Identity"]--Custom What-if role-->RG["Azure Resource Group"];
        Deployer["Main Managed Identity"]--Contributor Role-->RG;
    end;

    subgraph GitHub;

    subgraph GitHubPR["PR Environment"];
        prSecrets["Environment Secrets"];
        Reader-. Credentials .->prSecrets;
    end;

    subgraph GitHubMain["Main Environment"];
        mainBranch["Branch Protection: Main"];
        mainSecrets["Environment Secrets"];
        Deployer-. Credentials .->mainSecrets;
    end;

    GitHubPR ~~~ GitHubMain;
    end;
Loading

After this is set up, two separate workflows referring to the two environments will be used:

  1. pr-validate-yaml: triggers upon pull requests and runs a what-if resource group deployment against the Azure resource group - using the dedicated managed identity which does not have write permissions
  2. main-deploy.yaml: triggers upon push to the main branch and runs a resource group deployment against the Azure resource group - using the other managed identity which has write permissions

These workflows use the Azure/login and Azure/bicep-deploy actions.

Prerequisites

Usage

1. Initialize environment

To be able to set this up, you need to fork this repository, clone it, and do the following steps from your own fork:

Azure

To set up the environment in your Azure subscription, open a terminal locally and run to deploy the prereqs Bicep file:

Connect-AzAccount # Select the correct subscription after logging in

# Configure variables
$ghUserName = '<your GitHub username>'

# Deploy locally:
$deploy = New-AzSubscriptionDeployment -Name "demo-whatif-env" -Location norwayeast `
    -TemplateFile ./bicep/prereqs/main.bicep -ghUserName $ghUserName

Keep the terminal open after running the command to ensure we can use the outputs from the $deploy variable:

$deploy.Outputs | ConvertTo-Json

{
  "subscriptionId": {
    "Type": "String",
    "Value": "<sub id>"
  },
  "tenantId": {
    "Type": "String",
    "Value": "<tenant id>"
  },
  "deployerClientId": {
    "Type": "String",
    "Value": "<client id>"
  },
  "prClientId": {
    "Type": "String",
    "Value": "<client id>"
  }
}

GitHub

  1. Create two environments with the following names:

    • Azure
    • Azure-PR
  2. Create two repo level secrets with the values for your Azure subscription and tenant:

    • AZURE_SUBSCRIPTION_ID
    • AZURE_TENANT_ID
  3. For both environments, create two environment level secrets with the values for the two user-assigned managed identities:

    • AZURE_CLIENT_ID

Tip

For steps 2 and 3, you can run the following with GitHub CLI if you have it installed and set up:

# Set repo level secrets - replace with your values!
gh secret set AZURE_SUBSCRIPTION_ID --body $deploy.Outputs.subscriptionId.Value
gh secret set AZURE_TENANT_ID --body $deploy.Outputs.tenantId.Value

# Set environment level secrets - replace values!
gh secret set AZURE_CLIENT_ID --body $deploy.Outputs.deployerClientId.Value --env Azure
gh secret set AZURE_CLIENT_ID --body $deploy.Outputs.prClientId.Value --env Azure-PR
  1. For the Azure environment:
    1. Select Deployment branches and tags and choose Selected branches and tags
    2. Only allow main with Ref type branch

This will ensure that we can only run actual deployments from the main 🔒

2. Run what-if

  1. Create a new branch and edit the allowedoutboundaddresses parameter value in the demo Bicep file
  2. Save the edits, commit, and push to a new remote branch
  3. Create a pull request from the newly created branch to the main branch
  4. Verify that the 'Azure what-if' workflow is running against the PR

You can also run this workflow manually using a workflow dispatch run.

Status check:

Status check in PR

Example output:

What-if example output

Note

Note that when running the 'Azure What-If' workflow, it runs in the Azure-PR environment, which uses a managed identity that does not have write permissions.

3. Deploy

  1. Merge the pull request (or push a Bicep change to main)
  2. Verify that the 'Azure Deploy' workflow is running against the latest commit on the main branch

You can also run this workflow manually using a workflow dispatch run.

Note

Note that when running the 'Azure Deploy' workflow, it runs in the 'Azure' environment, which uses the managed identity with Contributor permissions.

Clean up

Delete the resource group, including the managed identities, their credentials, and other created resources by running:

Remove-AzResourceGroup -Name "demo-whatif-gh-rg"

Considerations

  • There are still other fundamental issues with what-if (noise, resource provider support, nested modules limitations) that need improvements before a Bicep what-if is really comparable to a Terraform Plan. Kudos to the Bicep team for pushing and doing a lot of improvements in this area!

  • There might be more improvements coming soon to do more secure PR-level validations with Bicep snapshots. See a demo repo here: https://github.com/anthony-c-martin/bicep-snapshot-demo.

  • If you want to simulate the same what-if behavior when running locally or something else than Azure/bicep-deploy, you will need to add the -ValidationLevel ProviderNoRbac argument to the deployment cmdlet (see docs for New-AzResourceGroupDeployment).

LICENSE

MIT License

About

Demo of running Bicep What-If deploy without write permissions

Topics

Resources

License

Stars

Watchers

Forks

Languages