Scenario

  • We’re creating playbooks in a managed Microsoft Sentinel-workspace.
  • It needs to have the Microsoft Sentinel Responder role.
  • We want to be able to assign that role using access granted by Azure Lighthouse.
  • We should do as much of this as possible in a Azure DevOps pipeline.

Setup

  1. Create the Lighthouse-configuration and onboard it.
  2. Deploy playbooks with managed identities enabled.
  3. Create ARM-template for assigning rights (or use Azure CLI, API).
  4. Deploy ARM-template.

1. Lighthouse-configuration

Create a Lighthouse-configuration and grant the User Access Administrator to a principal (User, Group, SPN):

"authorizations": {
	        "type": "array",
            "metadata": {
                "description": "Specify an array of objects, containing tuples of Azure Active Directory principalId, a Azure roleDefinitionId, and an optional principalIdDisplayName. The roleDefinition specified is granted to the principalId in the provider's Active Directory and the principalIdDisplayName is visible to customers."
            },
            "defaultValue": [
                {
                    //Grants user access administrator to the SPN
                    "principalId": "3kl47fff-1337-1337-b726-2cf02b05c7c4",
                    "principalIdDisplayName": "Test-SPN",
                    "roleDefinitionId": "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9",
                    //Allows the SPN to assign the following roles
                    "delegatedRoleDefinitionIds": [
                        //Microsoft Sentinel Responder
                        "3e150937-b8fe-4cfb-8069-0eaf05ecd056"
                    ]
                }
            ]
}

The same principal also needs to have write-permission on the object the managed identity resides within - for a playbook with a managed identity we should be fine with Microsoft Sentinel Contributor.

{
    //Grants contributor to the SPN
    "principalId": "3kl47fff-5655-4779-b726-2cf02b05c7c4",
    "principalIdDisplayName": "Test-SPN",
    "roleDefinitionId": "ab8e14d6-4a74-4a29-9ba8-549422addade"
}

At this point we can onboard the template.

Note: to read more about onboarding using Azure Lighthouse, click here.

2. Deploy playbooks with managed identities enabled

Using the Sentinel-as-Code project (https://github.com/javiersoriano/sentinelascode) as inspiration, we can deploy playbooks with a simple push-pipeline:

Assuming a simple Azure DevOps setup similar to the one mentioned above with the SPN from the above Lighthouse-configuration defined as the service-connection or the subscription-object in the Azure Powershell-task.

Example pipeline

Simple pipeline that downloads the local repository and runs the CreatePlaybooks.ps1-script with inputs.

name: build and deploy Playbooks

trigger:
 paths:
   include:
     - Playbooks/*

stages:
- stage: deploy_playbooks
  jobs:
    - job: AgentJob
      pool:
       name: Azure Pipelines
       vmImage: 'windows-latest'
      variables: 
      - group: DeployGroup
      - name: RepositoryLocation
        value: '$(Build.Repository.LocalPath)/SentinelAsCode'
      steps:
      - checkout: self
      - task: AzurePowerShell@5
        displayName: 'Create and Update Playbooks'
        inputs:
         azureSubscription: $(subscription)
         ScriptPath: '$(RepositoryLocation)/Scripts/CreatePlaybooks.ps1'
         ScriptArguments: '-resourceGroup $(ResourceGroup) -PlaybooksFolder $(RepositoryLocation)/Playbooks'
         azurePowerShellVersion: LatestVersion
         pwsh: true

Example deployment script

Script that takes a path as input and loops through all files and tries to deploy .json files to Azure as ARM-templates.

param(
    [Parameter(Mandatory=$true)]$resourceGroup,
    [Parameter(Mandatory=$true)]$PlaybooksFolder
)

Write-Host "Folder is: $($PlaybooksFolder)"

$armTemplateFiles = Get-ChildItem -Path $PlaybooksFolder -Filter *.json

Write-Host "Files are: " $armTemplateFiles

foreach ($armTemplate in $armTemplateFiles) {
    try {
        New-AzResourceGroupDeployment -ResourceGroupName $resourceGroup -TemplateFile $armTemplate 
    }
    catch {
        $ErrorMessage = $_.Exception.Message
        Write-Error "Playbook deployment failed with message: $ErrorMessage" 
    }
}

Note: script courtesy of Javier Soriano.

Example playbook template

Sample playbook for changing incident severity can be found here.

Note: template courtesy of Yaniv Shasha

In order to enable managed identites the following block must be configured under the "type": "Microsoft.Logic/workflows"-resource:

"identity": {
  "type": "SystemAssigned"
}

If we add the template-file to the Playbook-folder this should trigger the pipeline and deploy the Playbook to Azure.

3. Create the ARM-template

In order to assign roles using the SPN mentioned above we need to create an ARM-template for the roleAssignment:

{
    "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        //Generates a new random guid to be used as assignmnent ID
        "rbacGuid": {
            "type": "string",
            "defaultValue": "[newGuid()]"
        } 
    },
    "variables": {
        //The GUID refering to the Microsoft Sentinel Responder
        "responder": "3e150937-b8fe-4cfb-8069-0eaf05ecd056"
    },
    "resources": [
        {
            "type": "Microsoft.Authorization/roleAssignments",
            "apiVersion": "2020-03-01-preview",
            "name": "[parameters('rbacGuid')]",
            "dependsOn": [
            ],
            "properties": {
                "roleDefinitionId": "[concat(subscription().id, '/resourceGroups/###resourceGroup###/providers/Microsoft.Authorization/roleDefinitions/', variables('responder'))]",
                // The principalType property will tell Microsoft.Authorization not to perform the check for existence on your principal ID during roleAssignment creation
                "principalType": "ServicePrincipal",
                "delegatedManagedIdentityResourceId": "###delegatedresourceid###",
                "principalId": "###principalId###"
            }
        }
    ]
}

Save as a .json file.

4. Deploy ARM-template

Create a script for parsing the roleAssignment-template

You grab the content of the template in Powershell:

$armTemplate = Get-Content "$templatePath/API-Connectors/ManagedIdentityAccess.json" 

You then grab the resourceId of the playbook(s) you’re trying to change - just change the matching of the name to grab more of them:

$Playbook = Get-AzResource -ResourceType Microsoft.Logic/workflows | Where-Object {$_.Name -match "PlaybookName-*"}

We can then do some simple parsing of the template, grab the principalId of the managedIdentity and insert:

$ID = $Playbook.Identity.PrincipalId
$parsedTemplate = $armTemplate
$parsedTemplate = $parsedTemplate -replace ("###resourceGroup###",$resourceGroup)
$parsedTemplate = $parsedTemplate -replace ("###principalId###",$ID)
$parsedTemplate = $parsedTemplate -replace ("###delegatedresourceid###",$Playbook.ResourceId)
$parsedTemplate | Set-Content "RoleAssignment.json"

After this we have a ready to deploy template, which can be pushed using the following line of code:

New-AzResourceGroupDeployment -ResourceGroupName $resourcegroup -Name GrantRightsSPN -TemplateFile "RoleAssignment.json"

This all results in the following script:

Assign-Roles.ps1

PARAM (
  $templatePath,
  $resourceGroup
)
$armTemplate = Get-Content $templatePath
$Playbook = Get-AzResource -ResourceType Microsoft.Logic/workflows | Where-Object {$_.Name -match "PlaybookName-*"}
foreach($p in $playbook) {
  $ID = $p.Identity.PrincipalId
  $parsedTemplate = $armTemplate
  $parsedTemplate = $parsedTemplate -replace ("###resourceGroup###",$resourceGroup)
  $parsedTemplate = $parsedTemplate -replace ("###principalId###",$ID)
  $parsedTemplate = $parsedTemplate -replace ("###delegatedresourceid###",$p.ResourceId)
  $parsedTemplate | Set-Content "RoleAssignment.json"
  New-AzResourceGroupDeployment -ResourceGroupName $resourcegroup -Name GrantRightsSPN -TemplateFile "RoleAssignment.json"
}

Deploy the roleAssignment-template using a pipeline

Using the script from above, we can create a simple pipeline similar to the one we used for playbooks:

name: assignRoles using Lighthouse

trigger:
 paths:
   include:
     - Playbooks/*

stages:
- stage: assign_roles
  jobs:
    - job: AgentJob
      pool:
       name: Azure Pipelines
       vmImage: 'windows-latest'
      variables: 
      - group: DeployGroup
      - name: RepositoryLocation
        value: '$(Build.Repository.LocalPath)/SentinelAsCode'
      steps:
      - checkout: self
      - task: AzurePowerShell@5
        displayName: 'Assign Roles'
        inputs:
         azureSubscription: $(subscription)
         ScriptPath: '$(RepositoryLocation)/Scripts/Assign-Roles.ps1'
         ScriptArguments: '-templatePath "$(RepositoryLocation)/API-Connectors/ManagedIdentityAccess.json" -resouceGroup $(resourceGroup)'
         azurePowerShellVersion: LatestVersion
         pwsh: true

This should assign the Sentinel Responder-role to all managed identities for all playbooks in the filter Where-Object {$_.Name -match "PlaybookName-*"} - you can also set the assignRoles-pipeline to trigger on finished runs by the Create-Playbook pipeline by adding it as a resource:

resources:
  pipelines:
  - pipeline: Create Playbooks # Name of the pipeline resource.
    source: create-playbooks # The name of the pipeline referenced by this pipeline resource.
    #project: SentinelAsCode # Required only if the source pipeline is in another project
    trigger: true # Run pipeline when any run of assignRoles completes

Sources