Back up Azure Conditional Access Policies with PowerShell and Azure DevOps

Back up Azure Conditional Access Policies with PowerShell and Azure DevOps

November 3, 2022

I’ve been playing a lot with Azure Policies lately. One of my lastest requirements was to be able to export my company’s CA policies.

Below you can find my implementation to automatically back up my policies. I’m also leveraging Azure DevOps pipelines as I wanted something that’s free, would run on a schedule and save files to a Blob Container. That’s a very simple implementation and works very well for my needs.

You can find the source code here: https://github.com/danielvca/ca_policy_bkp

  • AZDO Project and repo.
  • Blob Container
  • AZDO Library for secrets.
  • AZDO Service Connection.

The first step is to create a library and input the variables:

We will need the following secrets.

  • clientid
  • ClientSecret
  • strkey
  • tenantid

export_ca_.ps1

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
#Backup and Export Azure Conditional Access Policies.
# https://azureblog.dev

function GetGraphToken {


Param(
    [parameter(Mandatory = $true)]
    $clientId,
    [parameter(Mandatory = $true)]
    $tenantId,
    [parameter(Mandatory = $true)]
    $clientSecret

    )

    # Construct URI
    $uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

    # Construct Body
    $body = @{
        client_id     = $clientId
        scope         = "https://graph.microsoft.com/.default"
        client_secret = $clientSecret
        grant_type    = "client_credentials"
    }

    # Get OAuth 2.0 Token
    $tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing

    # Access Token
    $token = ($tokenRequest.Content | ConvertFrom-Json).access_token

    #Returns token
    return $token
}

function RunQueryandEnumerateResults {

    Param(
        [parameter(Mandatory = $true)]
        [String]
        $apiUri,
        [parameter(Mandatory = $true)]
        $token

    )

    #Run Graph Query
    $Results = (Invoke-RestMethod -Headers @{Authorization = "Bearer $($Token)" } -Uri $apiUri -Method Get)
    #Output Results for debug checking
    #write-host $results

    #Begin populating results
    $ResultsValue = $Results.value

    #If there is a next page, query the next page until there are no more pages and append results to existing set
    if ($results."@odata.nextLink" -ne $null) {
        write-host enumerating pages -ForegroundColor yellow
        $NextPageUri = $results."@odata.nextLink"
        ##While there is a next page, query it and loop, append results
        While ($NextPageUri -ne $null) {
            $NextPageRequest = (Invoke-RestMethod -Headers @{Authorization = "Bearer $($Token)" } -Uri $NextPageURI -Method Get)
            $NxtPageData = $NextPageRequest.Value
            $NextPageUri = $NextPageRequest."@odata.nextLink"
            $ResultsValue = $ResultsValue + $NxtPageData
        }
    }

    ##Return completed results
    return $ResultsValue


}

function Report-ConditionalAccess{

    <#
    .SYNOPSIS
    Returns a report of Conditional Access Policies in a tenent

    #>

    # Application (client) ID, tenant ID and secret
    Param(
        [parameter(Mandatory = $true)]
        $clientId,
        [parameter(Mandatory = $true)]
        $tenantId ,
        [parameter(Mandatory = $true)]
        $clientSecret

    )

    $apiUri = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
    $token = GetGraphToken -clientId $clientId -tenantId  $tenantId -clientSecret $clientSecret

    $Policies = RunQueryandEnumerateResults -apiuri $apiUri -token $token



    foreach($policy in $policies){

        $policy | convertto-json | out-file ("$($policy.displayName).json").replace('[','').replace(']','').replace('/','')
    }

}

Since we want this to run on a schedule, here’s my pipeline.

You can choose not to upload the files to the Blob Container. Simply remove the last step from the yml file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# You can edit the cron below to change the recurrency.
schedules:
- cron: "0 0 * * FRI"
  displayName: Weekly Backup
  branches:
    include:
    - main

stages :
  - stage: BackUpPolicies
    variables:
      - group: 'CA Policies'
    jobs:
    - deployment: bkp_policies
      pool:
        vmImage: 'ubuntu-latest'
      continueOnError: false
      environment: 'AZ Policies'
      strategy:
        runOnce:
          deploy:
            steps:
              - checkout: self
              - task: PowerShell@2
                displayName: 'Back-up AZ CA Policies'
                inputs:
                  filePath: '$(System.DefaultWorkingDirectory)/export_ca_.ps1'
                  arguments: -clientId $clientid -tenantId $tenantid -clientSecret $ClientSecret
                  pwsh: true
              - task: AzurePowerShell@5
                inputs:
                  azureSubscription: '' #Enter your service connection name here
                  azurePowerShellVersion: LatestVersion
                  pwsh: true
                  ScriptType: 'InlineScript'
                  Inline: | #Add your storage account name below
                    $StorageContext = New-AzStorageContext -StorageAccountName <str account name> -StorageAccountKey $(strkey)
                    Get-ChildItem -File -Recurse -Filter "*.json" | Set-AzStorageBlobContent -Container "ca-policies" -Context $StorageContext -Force

Every Friday at midnight this pipeline will automatically back up your policies.