Terraform is an Infrastructure as a Code tool created by Hashicorp. It’s used to manage your infrastructure in Azure, as well as other clouds. In this article, we’ll be showing you how to deploy Microsoft Defender for Cloud (MDC) using Terraform from scratch. This way if you use Terraform, it’s recommended that you stick entirely with Terraform and don’t use any other management methods such as the Azure Portal.
As part of using Terraform to manage MDC, you will need to setup the Terraform configuration in a workspace including the Azure Resource Manager (RM) provider which configures your Azure resources. In this workspace, you’ll have the following files:
- Main.tf: The declarative configuration of the state of your MDC deployment. This is where all the updates for your Azure resources are performed, including the deployment of MDC.
- Variables.tf: Contains different values per environment e.g., development vs production environment.
- Outputs.tf: Declares information that you only determine after deployment
The following commands for Terraform are most crucial for you to know:
Terraform init
- Summary: Initialize Terraform.
- Typically run this once or just when adding in new providers or new versions
- This will parse through all the workspace files to create an initial state of determining what is needed e.g., plugins referenced in the Main.tf file e.g., azure plugin.
- Result: Once you run this file It will download these files to a terraform subfolder called .terraform subfolder where it will store the Azure RM provider.
Terraform plan
- Summary: View the changes that will be applied.
- Creates an execution plan of the actions needed to make the current state match the desired configuration in the terraform files.
- No changes in Azure will be made with this command, it will just show you me what will be done but won’t do any of the changes.
Terraform apply
- Summary: Applies the changes from main.tf to your Azure environment.
Setup Terraform environment
- Go to Downloads | Terraform by HashiCorp and download the Terraform file relevant to your device.
- Then move the downloaded Terraform application in a directory of your choice.
- You will need to add the path that Terraform is found in as an environment variable if you’re using Windows. If this still doesn’t work, then use the following command:
$env:PATH =$env:PATH+";'<path to Terraform installation directory>”"
- Go to the Microsoft Defender for Cloud GitHub repository and clone the Terraform configuration to the same directory.
- Open the directory that you just cloned in Visual Studio Code or your preferred source code editor.
- In the terminal of the editor, test that Terraform has been installed correctly by using the following command:
terraform -version
Now you have confirmed that Terraform has been correctly installed.
Azure RM provider
To manage Azure resources with Terraform, you need to use the Azure RM provider. In some situations, where an Azure RM REST API endpoint is not supported by the Azure RM provider, you can use the AzAPI provider to get full access to the Azure REST API. In a providers.tf file, you will place the following Terraform declarations, which state you are going to work with a minimum or specific Terraform provider version:
terraform {
required_version = ">=0.12"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.61"
}
azapi = {
source = "Azure/azapi"
version = "=1.6.0"
}
}
}
provider "azurerm" {
features {}
}
This providers declaration will be used next by the Terraform initialization procedure to set itself up for Azure management. See more guidance on this provider in the Terraform resources for MDC section.
Configure Terraform
- First thing you need to do is logging in to Azure, using the following command (your web browser will open up a new tab asking you to sign in with your Azure credentials):
az login
2. You will need to initialize Terraform to prepare the current working directory to be used with Terraform and to install the required providers, using the following command:
terraform init
- Run the following command to determine what changes are required in Azure to match the Main.tf file:
terraform plan
This allows you to see what changes are different from your main.tf and what is in your Azure environment. All the Azure configuration should go in the main.tf file.
- When you’re satisfied with the proposed changes, then you run the following command to actually apply the changes:
terraform apply
You now have the configuration needed for MDC.
You can make further changes to your main.tf file which will be incorporated to your Azure environment when you run the terraform apply command again.
Note: Once you start using Terraform to deploy your Azure resources, it’s a best practise to continue using terraform for this. Try to avoid using the Azure Portal UI to make further changes as that may cause issues in your Terraform configuration.
Terraform resources for MDC
There are many Terraform resources available for setting up MDfC. You can browse for them in the Azure RM Terraform provider documentation. You will notice they appear aggregated under “Security Center”, which was the previous brand for MDfC. In this section, you will learn which Terraform resources to use for each MDfC setup step, for a particular Azure subscription.
Many of the Terraform examples below are going to reference the current Azure subscription ID we are working with. This is done by means of a data declaration which stores the current Azure subscription properties:
data "azurerm_subscription" "current" {}
Note: The example code below should go into your main.tf file.
Enabling the default Microsoft Cloud Security Benchmark Policy initiative
After an Azure Subscription is registered for the Microsoft.Security resource provider – this should have at least happened automatically after you ran terraform init –, MDC will eventually enable the default Azure Policy initiative for Microsoft Cloud Security Benchmark, which fuels its Security Posture recommendations. As this will happen only after some hours, you may want to leverage Terraform to enable it yourself and speed things up.
resource "azurerm_subscription_policy_assignment" "mcsb_assignment" {
name = "mcsb"
display_name = "Microsoft Cloud Security Benchmark"
policy_definition_id = "/providers/Microsoft.Authorization/policySetDefinitions/1f3afdf9-d0c9-4c3d-847f-89da613e70a8"
subscription_id = data.azurerm_subscription.current.id
}
We are using the Policy Assignment resource applied at the Subscription level and we are referring to the Microsoft Cloud Security Benchmark Policy Initiative ID. You will notice the use of the data.azurerm_subscription.current data resource we declared earlier, to populate the Subscription ID.
Enabling MDC Plans
Now that we’ve already set up Security Posture, let’s move on to Workload Protection. After choosing which Defender Plans you want to enable, you’ll declare a Terraform resource for each plan.
resource "azurerm_security_center_subscription_pricing" "mdc_arm" {
tier = "Standard"
resource_type = "Arm"
subplan = "PerApiCall"
}
resource "azurerm_security_center_subscription_pricing" "mdc_servers" {
tier = "Standard"
resource_type = "VirtualMachines"
subplan = "P2"
}
resource "azurerm_security_center_subscription_pricing" "mdc_cspm" {
tier = "Standard"
resource_type = "CloudPosture"
}
resource "azurerm_security_center_subscription_pricing" "mdc_storage" {
tier = "Standard"
resource_type = "StorageAccounts"
subplan = "DefenderForStorageV2"
}
In the examples above, we are enabling Defender for ARM and Defender for Servers (P2), Defender CSPM, and Defender for Storage (V2). For other plans, check out the Terraform documentation.
Enabling plan settings
Some Microsoft Defender for Cloud plans include additional settings, such as Defender CSPM (Agentless Scanning, Sensitive Data Discovery, etc.) or Defender for Servers (Defender for Endpoint integration). In the case of Defender CSPM settings, the Azure RM Terraform provider does not support it yet and we must fall back to the AzAPI provider instead, which provides us with full access to the Azure Resource Manager REST API.
resource "azurerm_security_center_setting" "setting_mde" {
setting_name = "WDATP"
enabled = true
}
resource "azapi_resource" "setting_agentless_vm" {
type = "Microsoft.Security/vmScanners@2022-03-01-preview"
name = "default"
parent_id = data.azurerm_subscription.current.id
body = jsonencode({
properties = {
scanningMode = "Default"
}
})
schema_validation_enabled = false
}
resource "azapi_update_resource" "setting_cspm" {
type = "Microsoft.Security/pricings@2023-01-01"
name = "CloudPosture"
parent_id = data.azurerm_subscription.current.id
body = jsonencode({
properties = {
pricingTier = "Standard"
extensions = [
{
name = "SensitiveDataDiscovery"
isEnabled = "True"
},
{
name = "ContainerRegistriesVulnerabilityAssessments"
isEnabled = "True"
},
{
name = "AgentlessDiscoveryForKubernetes"
isEnabled = "True"
}
]
}
})
}
The first resource enables Microsoft Defender for Servers integration with Microsoft Defender for Endpoint (including auto-provisioning of the MDE extension). The second resource enables the Agentless VM scanning feature of Defender CSPM/Defender for Servers. The third resource controls the Defender CSPM extensions for the different agentless scanners for sensitive data, container registries and Kubernetes – it does it thanks to the azapi_update_resource resource type, but you could also use azapi_resource instead, which would fully replace the usage of the Defender CSPM plan declaration we did before for the mdc_cspm resource.
Setting up security contacts
If MDC needs to notify you about a security incident, it’s a good idea to have e-mail and phone contacts set up.
resource "azurerm_security_center_contact" "mdc_contact" {
email = "john.doe@contoso.com"
phone = "+351919191919"
alert_notifications = true
alerts_to_admins = true
}
The phone property is the only optional one. The alert_notifications property enables/disables sending notifications to the security contact, while the alerts_to_admins is about sending notifications to the Azure Subscription administrators.
Enabling Log Analytics agent auto-provisioning
OK, now that we have set the basics up, let’s configure more advanced features, such as auto-provisioning Log Analytics agents, in the context of the Defender for Servers plan. This involves multiple steps and Azure resources. First, we must turn auto-provisioning on:
resource "azurerm_security_center_auto_provisioning" "auto-provisioning" {
auto_provision = "On"
}
There’s a specific resource for that and it’s very simple to deal with. It’s just an On/Off property. Next, we are going to associate Defender for Servers to a specific Log Analytics workspace.
resource "azurerm_security_center_workspace" "la_workspace" {
scope = data.azurerm_subscription.current.id
workspace_id = "/subscriptions/<subscription id>/resourcegroups/<resource group name>/providers/microsoft.operationalinsights/workspaces/<workspace name>"
}
The declaration above will work for an existing Log Analytics workspace. If you want to create the Log Analytics workspace together with MDC, you will use a slightly different approach:
resource "azurerm_resource_group" "security_rg" {
name = "security-rg"
location = "West Europe"
}
resource "azurerm_log_analytics_workspace" "la_workspace" {
name = "mdc-security-workspace"
location = azurerm_resource_group.security_rg.location
resource_group_name = azurerm_resource_group.security_rg.name
sku = "PerGB2018"
}
resource "azurerm_security_center_workspace" "la_workspace" {
scope = data.azurerm_subscription.current.id
workspace_id = azurerm_log_analytics_workspace.la_workspace.id
}
In the declarations above, we create a Resource Group and Log Analytics Workspace and then reference its ID it in the MDC workspace resource. You just need to adjust the Resource Group location and Log Analytics SKU to your requirements.
Enabling Vulnerability Assessment auto-provisioning
Depending on the Vulnerability Assessment provider you choose for Defender for Servers, you will follow different approaches. For Microsoft Defender Vulnerability Management (part of Microsoft Defender for Endpoint), you simply create a Microsoft.Security/serverVulnerabilityAssessmentsSettings resource:
resource "azapi_resource" "DfSMDVMSettings" {
type = "Microsoft.Security/serverVulnerabilityAssessmentsSettings@2022-01-01-preview"
name = "AzureServersSetting"
parent_id = data.azurerm_subscription.current.id
body = jsonencode({
properties = {
selectedProvider = "MdeTvm"
}
kind = "AzureServersSetting"
})
schema_validation_enabled = false
}
For Qualys, Vulnerability Assessment auto-provisioning is configured with the help of an Azure Policy assignment.
resource "azurerm_subscription_policy_assignment" "va-auto-provisioning" {
name = "mdc-va-autoprovisioning"
display_name = "Configure machines to receive a vulnerability assessment provider"
policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/13ce0167-8ca6-4048-8e6b-f996402e3c1b"
subscription_id = data.azurerm_subscription.current.id
identity {
type = "SystemAssigned"
}
location = "West Europe"
parameters = <<PARAMS
{ "vaType": { "value": "default" } }
PARAMS
}
resource "azurerm_role_assignment" "va-auto-provisioning-identity-role" {
scope = data.azurerm_subscription.current.id
role_definition_id = "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd"
principal_id = azurerm_subscription_policy_assignment.va-auto-provisioning.identity[0].principal_id
}
In the example above, we chose the Qualys vulnerability assessment (default value for the vaType Policy parameter). We are also assigning the Security Admin role to the Managed Identity that will be used to perform the automatic provisioning of the Vulnerability Assessment solution.
Configuring Continuous Export settings
The last Terraform resource for MDC we cover in this article is the one allowing you to configure Continuous Export settings. You have many configuration possibilities available. In the example below, we are exporting to a specific Log Analytics workspace High/Medium Security Alerts and all the Secure Score controls. We are referring to a Log Analytics workspace ID that was declared in the same Main.tf file.
resource "azurerm_security_center_automation" "la-exports" {
name = "ExportToWorkspace"
location = azurerm_resource_group.security_rg.location
resource_group_name = azurerm_resource_group.security_rg.name
action {
type = "loganalytics"
resource_id = azurerm_log_analytics_workspace.la_workspace.id
}
source {
event_source = "Alerts"
rule_set {
rule {
property_path = "Severity"
operator = "Equals"
expected_value = "High"
property_type = "String"
}
rule {
property_path = "Severity"
operator = "Equals"
expected_value = "Medium"
property_type = "String"
}
}
}
source {
event_source = "SecureScores"
}
source {
event_source = "SecureScoreControls"
}
scopes = [ data.azurerm_subscription.current.id ]
}
Final considerations
Given the stateful nature of Terraform-based deployments, you should bear in mind the following:
- Some MDC settings and plans may have already been set before your first MDC Terraform deployment. In this case, Terraform may ask you to import the resource into the Terraform state, with the help of the import command.
- Depending on the resource type, removing it from the Terraform file does not necessarily mean it will result in a resource deletion in Azure. Some MDC settings are not removable and, for those, the Terraform provider has a different behavior, which can be turning off the setting or simply leaving it unchanged. Likewise, calling the Terraform destroy command on your code may not necessarily remove all the MDC options you previously set. Please, check the Terraform documentation for each resource.
Huge thanks to the reviewers of this post:
Safeena Begum Lepakshi, Senior Program Manager, Microsoft Defender for Cloud
Vasavi_Pasula , Senior Program Manager, Microsoft Defender for Cloud
@Yuri Diogenes , Principal PM Manager, Microsoft Defender for Cloud