This check flags Azure subscriptions that have no activity log alert for Network Security Group changes. Without it, someone can open up your firewall rules and you won't hear about it until it's too late. Create an activity log alert on NSG write and delete operations to get notified the moment a rule changes.
Network Security Groups are the workhorse of network access control in Azure. They decide which traffic can reach your VMs, subnets, and load balancers. When an NSG rule changes, the security posture of everything behind it changes too. The problem is that these changes are quiet by default. Someone can add an inbound rule allowing SSH from 0.0.0.0/0 and nothing alerts you. This Lensix check verifies that an activity log alert exists to catch those changes as they happen.
What this check detects
The activitylog_nonsgchanges check inspects your Azure subscription for an activity log alert configured to fire on Network Security Group operations. Specifically, it looks for an alert rule that monitors these operation types:
Microsoft.Network/networkSecurityGroups/write— creating or modifying an NSGMicrosoft.Network/networkSecurityGroups/delete— deleting an NSGMicrosoft.Network/networkSecurityGroups/securityRules/write— creating or modifying a rule inside an NSGMicrosoft.Network/networkSecurityGroups/securityRules/delete— deleting a rule
If no alert exists for these operations, the check fails. The activity log itself still records the change, but a log entry that nobody reads is not the same as a notification. The gap this check closes is the time between a change happening and a human finding out about it.
Note: Azure activity logs are control plane logs. They record management operations against resources, like creating a VM or editing an NSG rule. They do not record data plane traffic such as packets flowing through the NSG. For traffic visibility you want NSG flow logs, which is a separate concern from this check.
Why it matters
NSG rules are one of the first things an attacker touches after gaining a foothold. If they compromise a service principal or user with network contributor rights, opening a port is often step one toward exfiltration or lateral movement. A single permissive inbound rule can expose a database, a management port, or an internal API to the entire internet.
Here is a realistic scenario. An attacker phishes credentials for an engineer who has Contributor on a resource group. They add an inbound rule to an NSG allowing TCP 3389 from any source, then RDP into a jump box. Without an alert, this change blends into normal activity. The NSG still shows green in the portal. Nobody notices until the next manual review, which might be weeks away.
There is also an everyday operational angle. Not every dangerous NSG change comes from an attacker. A rushed deployment, a copy-pasted Terraform block, or a junior engineer testing connectivity can all open something that should stay closed. An alert turns a silent mistake into an immediate conversation.
Warning: Compliance frameworks care about this. CIS Microsoft Azure Foundations Benchmark explicitly recommends an activity log alert for NSG changes. If you are pursuing SOC 2, ISO 27001, or PCI DSS, an auditor will look for evidence that network configuration changes are monitored and alertable.
How to fix it
You can create the alert through the Azure CLI, the portal, or infrastructure as code. The alert is scoped to a subscription and ties an activity log condition to an action group that delivers the notification.
Step 1: Create or identify an action group
An action group defines who gets notified and how. If you already have one for security alerts, skip ahead. Otherwise create one that emails your security distribution list.
az monitor action-group create \
--name "secops-notify" \
--resource-group "monitoring-rg" \
--short-name "secops" \
--action email secops-team [email protected]
Step 2: Create the activity log alert
This alert fires when any NSG write operation succeeds in the subscription.
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
az monitor activity-log alert create \
--name "alert-nsg-changes" \
--resource-group "monitoring-rg" \
--scope "/subscriptions/$SUBSCRIPTION_ID" \
--condition category=Administrative and operationName=Microsoft.Network/networkSecurityGroups/write \
--action-group "secops-notify" \
--description "Alerts on Network Security Group create or update operations"
Note: The CLI --condition flag accepts a single operation. To cover delete operations and rule-level changes, create additional alerts for each operation name, or use a Bicep or ARM template that supports an anyOf condition block (shown below).
Step 3: Verify the alert is active
az monitor activity-log alert show \
--name "alert-nsg-changes" \
--resource-group "monitoring-rg" \
--query "{name:name, enabled:enabled, scopes:scopes}"
Portal alternative
- Go to Monitor then Alerts then Create then Alert rule.
- Set the scope to your subscription.
- Under condition, choose Activity Log signal type and search for the NSG operations.
- Attach an action group with your notification channel.
- Name the rule and create it.
How to prevent it from happening again
Creating the alert once by hand solves the problem for one subscription today. New subscriptions, deleted alerts, and drift will bring it back. Bake the alert into your infrastructure as code so it ships with every environment.
Bicep example covering all NSG operations
resource nsgAlert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {
name: 'alert-nsg-changes'
location: 'global'
properties: {
scopes: [
subscription().id
]
enabled: true
condition: {
allOf: [
{
field: 'category'
equals: 'Administrative'
}
{
anyOf: [
{ field: 'operationName', equals: 'Microsoft.Network/networkSecurityGroups/write' }
{ field: 'operationName', equals: 'Microsoft.Network/networkSecurityGroups/delete' }
{ field: 'operationName', equals: 'Microsoft.Network/networkSecurityGroups/securityRules/write' }
{ field: 'operationName', equals: 'Microsoft.Network/networkSecurityGroups/securityRules/delete' }
]
}
]
}
actions: {
actionGroups: [
{
actionGroupId: actionGroup.id
}
]
}
}
}
Enforce it with Azure Policy
Azure Policy can audit subscriptions that lack the alert and even deploy it automatically using a deployIfNotExists effect. Assign the policy at the management group level so every current and future subscription inherits it.
Tip: Pair the IaC definition with a CI gate. Run a check in your pipeline that fails the build if the activity log alert resource is missing from the deployment plan. With Terraform you can assert the resource exists in the plan output before apply. That stops the alert from being accidentally removed in a future refactor.
Terraform equivalent
resource "azurerm_monitor_activity_log_alert" "nsg_changes" {
name = "alert-nsg-changes"
resource_group_name = azurerm_resource_group.monitoring.name
location = "global"
scopes = [data.azurerm_subscription.current.id]
description = "Alerts on Network Security Group changes"
criteria {
category = "Administrative"
operation_name = "Microsoft.Network/networkSecurityGroups/write"
}
action {
action_group_id = azurerm_monitor_action_group.secops.id
}
}
Warning: The Terraform criteria block supports one operation_name per resource. Define separate azurerm_monitor_activity_log_alert resources for write, delete, and rule level operations, or loop over a list with for_each to keep it DRY.
Best practices
- Alert at the management group scope where possible. A single alert covering all subscriptions under a management group is easier to maintain than one per subscription and harder to accidentally miss.
- Route alerts somewhere people actually look. Email is fine as a baseline, but a webhook into Slack, Teams, or PagerDuty gets faster eyes on a change. Action groups support all of these.
- Include the actor in your triage runbook. Activity log entries carry the caller identity. When an NSG alert fires, the first question is always who made the change and whether it was expected.
- Do not stop at NSGs. The same alerting pattern applies to firewall policies, route tables, public IP assignments, and Key Vault access policies. NSG changes are the highest value target, but they are not the only one.
- Test the alert end to end. Make a harmless NSG change in a non-production subscription and confirm the notification arrives. An alert that was never fired is an alert you cannot trust.
Tip: Lensix tracks this check continuously across every subscription you connect, so a deleted or disabled alert shows up as a failed check rather than a silent gap. Use it as the safety net behind your policy assignments.
NSG changes are small, frequent, and easy to overlook. An activity log alert turns each one into a signal you can act on. Set it up once in code, enforce it with policy, and verify it with a real change. That combination keeps the gap closed even as your environment grows.

