Azure Conditional Access Policies as Code with Graph API, Part 2: Plan

In the second part of our Azure Conditional Access Policies as Code with Graph API series, we'll be covering the Plan phase of our policies.

You can check out the other parts from the links below:

Now that we have our current conditional access policies written, we can start making changes to the configuration through code rather than the portal.

We need to have a way to compare the local configuration with that of the remote, such as in Terraform Plan. The below PowerShell script will help with that.

PowerShell script

The script to compare the local configuration with the remote is [here]

  • The script imports the graph token module (created in the previous post)

  • Declares two functions. The first, Get-Policies, gets all local and remote policy configurations and stores them in variables

  • The second function, Get-JsonPaths, is a recursive function to get all paths for our JSON properties to allow for comparisons

  • Get-Policies is called and Get-JsonPaths then loops over the policies to get all paths for properties which are then stored

  • Conducts a soft check to see if the remote JSON schema has changed from the local configuration

  • Compares the values for each local and remote JSON property and stores the results

  • Outputs a Plan of changes to let you preview the changes the script plans to make to the live policies

  • Creates directories and copies the policy files to create, update and delete to them

Pipeline

Now that we have the script to Plan our changes, let's add a stage to our pipeline to run it

  - stage: Plan
    dependsOn: [Validate]
    condition: succeeded('Validate')
    jobs:
    - job: compare_local_remote
      steps:
      - task: PowerShell@2
        displayName: 'plan policies'
        inputs:
          filePath: '$(Build.SourcesDirectory)/scripts/compare-conditional-access-policies.ps1'
          arguments: '-clientID $(clientID) -clientSecret $(clientSecret) -tenantID $(tenantID)'
      - task: PublishPipelineArtifact@1
        inputs:
          targetPath: '$(Build.SourcesDirectory)\update'
          artifact: 'update'
          publishLocation: 'pipeline'
      - task: PublishPipelineArtifact@1
        inputs:
          targetPath: '$(Build.SourcesDirectory)\new'
          artifact: 'new'
          publishLocation: 'pipeline'
      - task: PublishPipelineArtifact@1
        inputs:
          targetPath: '$(Build.SourcesDirectory)\remove'
          artifact: 'remove'
          publishLocation: 'pipeline'

I've set variables for the service principal's credentials in the pipeline settings UI and these are then passed in as arguments to the PowerShell script.

There are 3 tasks that publish the folders, with the policy files to create, update and delete, as pipeline artifacts. These will then be downloaded and used later in the Apply stage.

Running the pipeline now has the below output:

Within the Plan, I have made use of log formatting commands to better the output for readability. For instance, in the PowerShell script I've used ##[group] and ##[endgroup] so that I can output the live and local policy configurations in collapsible sections to prevent the plan from becoming too noisy.

Be sure to check out the last part 3 of this series where we'll discuss the Apply phase of Conditional Access Policies as Code with Graph API.