Microsoft Defender External Attack Surface Management (MDEASM) is an Azure based security service that helps organizations gain visibility and manage their external attack surface. I was recently presented with the challenge of having to deploy and configure this resource entirely through code using Azure DevOps. What follows are some hopefully useful notes on how I managed to do this.

All code referenced henceforth can be found in this repo.

Deployment

MDEASM is not yet supported through Azure Resource Manager (ARM). Nevertheless, you can still deploy using an ARM template if you know the resource provider. For this tutorial, I will reference Microsoft.Easm/workspaces@2023-04-01-preview as the resource provider. In case that changes in the future, you can check for the most up to date version here.

With the resource provider you can generate an ARM Template, which I did using a Bicep file:

param name string = 'easm-work'
param region string = 'westeurope'
resource Easm 'Microsoft.Easm/workspaces@2023-04-01-preview' = {
name: name
location: region
properties: {}
}

Using Azure CLI run the following command:

az bicep build mdeasm.bicep

This will generate an ARM template (main.json) from your Bicep file.

I renamed the json file to easm-deployment.json. Here’s what the template looks like:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "metadata": {
    "_generator": {
      "name": "bicep",
      "version": "0.9.1.41621",
      "templateHash": "10012041125269374402"
    }
  },
  "parameters": {
    "name": {
      "type": "string",
      "defaultValue": "mdeasm1"
    },
    "region": {
      "type": "string",
      "defaultValue": "westeurope"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Easm/workspaces",
      "apiVersion": "2023-04-01-preview",
      "name": "[parameters('name')]",
      "location": "[parameters('region')]",
      "properties": {}
    }
  ]
}

Terraform

I used Terraform to define and provision MDEASM (and a resource group for it to sit in). I won’t detail anything about using Terraform with Azure DevOps here as there are many tutorials on that subject already. But here are the three Terraform files I used for deployment with some important notes:

  1. main.tf - declare the ARM template to be used here. Note the use of ${path.module} which ensures to point to the current directory in a way that doesn’t throw up any Terraform errors.
  2. variables.tf
  3. providers.tf - Running a Destroy action fails unless prevent_deletion_if_contains_resources = false is included.

You can view these files here.

Azure DevOps

Create a repo in Azure DevOps and add the above mentioned files.

Then create a YAML file to define the steps for the pipeline. Here is a basic example of what this should look like:

trigger: none 

pool:
  vmImage: 'ubuntu-latest'

steps:
  # Step 1: Install Terraform
  - task: TerraformInstaller@0
    inputs:
      terraformVersion: 'latest'

  # Step 2: Azure CLI to authenticate with Azure
  - task: AzureCLI@2
    inputs:
      azureSubscription: '<YOUR_SERVICE_CONNECTION>'
      scriptType: 'bash'
      scriptLocation: 'inlineScript'
      inlineScript: |
        az account show

  # Step 3: Checkout the code (Terraform and ARM template files)
  - checkout: self

  # Step 4: Terraform Init
  - script: terraform init
    displayName: 'Terraform Init'

  # Step 5: Terraform Plan
  - script: terraform plan -out=tfplan
    displayName: 'Terraform Plan'

  # Step 6: Terraform Apply
  - script: terraform apply -auto-approve tfplan
    displayName: 'Terraform Apply'

Finally create a new pipeline and then run it.

Configuration

Once you have created your MDEASM workspace you need to provide it with seed data so that it can perform discovery of assets.

You can obviously do this via the workspace UI, but due to rights restrictions in my case I had to figure out a way of doing this using only an azure service connection and the MDEASM API.

The MDEASM API allows you to configure seed data by creating what are called Discovery Groups. Thus I built a pipeline that creates/updates a Discovery Group with my seed data:

trigger:
  branches:
    include:
      - main
  paths:
    include:
      - seed-data.json

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: AzureCLI@2
  displayName: Run Command
  inputs:
    azuresubscription: # name of the azure service connection 
    scriptType: "bash"
    scriptLocation: "inlineScript"
    addSpnToEnvironment: true
    inlineScript: |
      echo "Running AZ CLI commands"
      
      # authenticate using AZ CLI
      az account show

      # get access token
      access_token=$(az account get-access-token --scope=https://easm.defender.microsoft.com/.default --query accessToken --output tsv)

      # create discovery group and populate with seeds
      curl -X PUT "https://westeurope.easm.defender.microsoft.com/subscriptions/SUBSCRIPTION-ID/resourceGroups/RESOURCE-GROUP/workspaces/MDEASM-WORKSPACE-NAME/discoGroups/DISCO-GROUP-NAME?api-version=2024-03-01-preview" \
        -H "Authorization: Bearer $access_token" \
        -H "Content-Type: application/json" \
        -d @seed-data.json

As the name suggests the seed-data.json file will contain the seed data to include in the discovery group. When you have a lot of seeds its a bit of a pain to put together, so I wrote a simple python script that does the formatting for you. Here is an example of what the seed-data.json will look like:

{
    "description": "seeds for production instance of MDEASM",
    "frequencyMilliseconds": 0,
    "tier": "advanced",
    "excludes": [
        {"name": "example.com", "kind": "domain"},
        {"name": "[email protected]", "kind": "contact"}
    ],
    "seeds": [
        {"name": "[email protected]", "kind": "contact"},
        {"name": "12345", "kind": "as"},
        {"name": "example.co.nl", "kind": "host"},
        {"name": "8.8.8.8/24", "kind": "ipBlock"},
        {"name": "example1.com", "kind": "domain"},
        {"name": "example2.com", "kind": "domain"}
    ],
    "names": [
      "Org A",
      "Org B"
    ]
  }

Take note of the frequencyMilliseconds field since this instructs the workspace how often to run discovery using the seeds. I set mine to never.

After you’ve run this pipeline for the first time the workspace UI will display something like “building your attack surface”. This can take anything between one and three days depending on the amount of seed data initally supplied. Please note that during this build phase you can’t add/remove seeds from the discovery group.

But the great thing is that after the build phase, when you do adjust the seed-data.json file the pipeline will atuomatically trigger and update the seed data in the workspace.

And that’s it, you’re now deploying and configuring MDEASM entirely through code!

The MDEASM API provides much more capability than the UI alone. Here’s a nice blog post from Microsoft detailing how to experiment with the API using Jupyter notebooks. Microsoft’s MDEASM-Solutions Git repo is also full of great automation ideas.


⚡️ Enjoyed this post? Buy me a coffee… in sats! Just tip me via my Lightning address: [email protected]