CodeBundle Design Spec — azure-nsg-desired-state-drift
Parent: codecollection-registry#49 (Firewall & NSG Integrity)
codebundle_name: "azure-nsg-desired-state-drift"
target_collection: "rw-cli-codecollection"
display_name: "Azure NSG Desired-State Drift Detection"
author: "rw-codebundle-agent"
purpose: |
Detect out-of-band changes to Network Security Groups by comparing live NSG
rules and associations against a repo-managed baseline (declared desired state).
Surfaces drift that indicates manual portal/CLI edits or unauthorized changes
outside your IaC pipeline.
tasks:
-
name: "Export Live NSG Rules for Comparison"
description: "For each NSG in scope, export security rules and default rules in stable JSON (subscription, RG, NSG name, rule set) for diffing."
script_name: "nsg-export-live-rules.sh"
expected_issue_severity: [2, 3]
access_level: "read-only"
data_type: "logs-config"
-
name: "Load and Normalize Baseline NSG Definition"
description: "Read baseline from user-supplied path (JSON export, Terraform plan artifact, or directory of per-NSG files) and normalize to the same schema as live export."
script_name: "nsg-load-baseline.sh"
expected_issue_severity: [2]
access_level: "read-only"
data_type: "logs-config"
-
name: "Diff Live vs Baseline and Report Drift"
description: "Compare normalized live vs baseline; flag added/removed/changed rules (priority, direction, access, protocol, ports, source/destination). Emit actionable issue per drift category."
script_name: "nsg-diff-desired-state.sh"
expected_issue_severity: [3, 4]
access_level: "read-only"
data_type: "logs-config"
-
name: "Validate Subnet and NIC NSG Associations"
description: "Cross-check which subnets/NICs attach to each NSG; optionally compare association list to baseline to catch attachment drift."
script_name: "nsg-association-audit.sh"
expected_issue_severity: [2, 3]
access_level: "read-only"
data_type: "logs-config"
-
name: "Summarize Drift Scope for Operators"
description: "Aggregate drift counts by subscription/RG, link to Azure Portal resource URLs, and suggest rollback via pipeline or IaC reconcile."
script_name: "nsg-drift-summary.sh"
expected_issue_severity: [1, 2]
access_level: "read-only"
data_type: "logs-config"
scope:
level: "ResourceGroup"
qualifiers:
- AZURE_SUBSCRIPTION_ID
- AZURE_RESOURCE_GROUP
- NSG_NAMES
iteration_pattern: |
One SLX per discovered NSG (or per user-provided NSG list) within the
subscription/RG qualifiers. Loop variable: NSG name and resource ID.
resource_types:
- "microsoft_network_network_security_groups"
generation_strategy: |
Generation rules: platform azure; resourceTypes microsoft.network/networkSecurityGroups
(or equivalent RunWhen resource type for NSGs). Match by name pattern within
configured resource groups. Qualifiers: subscription_id, resource_group, nsg name.
env_vars:
-
name: AZURE_SUBSCRIPTION_ID
description: "Azure subscription ID for scope"
required: true
-
name: AZURE_RESOURCE_GROUP
description: "Resource group containing NSGs (or empty with discovery across listed RGs)"
required: false
default: ""
-
name: NSG_NAMES
description: "Comma-separated NSG names, or 'All' for all NSGs in the resource group"
required: false
default: "All"
-
name: BASELINE_PATH
description: "Path or URI to baseline JSON (or directory of baseline files) matching export schema"
required: true
-
name: BASELINE_FORMAT
description: "json-bundle | per-nsg-dir | terraform-show (optional future)"
required: false
default: "json-bundle"
secrets:
- name: azure_credentials
description: "Service principal for Azure Resource Manager read access to NSGs and associations"
format: |
JSON: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID
platform:
name: "azure"
cli_tools:
- "az network nsg"
- "az network vnet subnet"
- "az network nic"
- "jq"
- "diff"
auth_methods:
- "Service Principal (azure_credentials)"
api_docs: "https://learn.microsoft.com/en-us/rest/api/virtual-network/network-security-groups/"
related_bundles:
-
name: "azure-aks-triage"
relationship: "complements"
notes: "AKS triage lists subnet NSG rules in passing; this bundle is dedicated to drift vs baseline across NSGs."
-
name: "azure-network-security-activity-audit"
relationship: "complements"
notes: "Activity audit explains who changed rules; drift bundle shows what differs from desired state."
test_scenarios:
-
name: "nsg_matches_baseline"
description: "Live export identical to baseline"
expected_issues: 0
-
name: "manual_rule_added"
description: "Extra allow rule added in portal not in baseline"
expected_issues: 2
expected_severities: [3, 3]
notes: |
Baseline source is customer-specific: many teams store Terraform in Git and can
export terraform show -json or use az network nsg show checked into a config
repo. The bundle should document one supported canonical JSON shape and allow
optional jq transforms for team-specific exports. Effective security rules
(including Azure defaults) may need to be filtered so only customer-managed rule
blocks are compared—parameterize rule name prefixes or ignore lists. Read-only
RBAC: Reader on subscription/RG plus Microsoft.Network/*/read.
CodeBundle Design Spec — azure-nsg-desired-state-drift
Parent: codecollection-registry#49 (Firewall & NSG Integrity)
codebundle_name: "azure-nsg-desired-state-drift"
target_collection: "rw-cli-codecollection"
display_name: "Azure NSG Desired-State Drift Detection"
author: "rw-codebundle-agent"
purpose: |
Detect out-of-band changes to Network Security Groups by comparing live NSG
rules and associations against a repo-managed baseline (declared desired state).
Surfaces drift that indicates manual portal/CLI edits or unauthorized changes
outside your IaC pipeline.
tasks:
name: "Export Live NSG Rules for Comparison"
description: "For each NSG in scope, export security rules and default rules in stable JSON (subscription, RG, NSG name, rule set) for diffing."
script_name: "nsg-export-live-rules.sh"
expected_issue_severity: [2, 3]
access_level: "read-only"
data_type: "logs-config"
name: "Load and Normalize Baseline NSG Definition"
description: "Read baseline from user-supplied path (JSON export, Terraform plan artifact, or directory of per-NSG files) and normalize to the same schema as live export."
script_name: "nsg-load-baseline.sh"
expected_issue_severity: [2]
access_level: "read-only"
data_type: "logs-config"
name: "Diff Live vs Baseline and Report Drift"
description: "Compare normalized live vs baseline; flag added/removed/changed rules (priority, direction, access, protocol, ports, source/destination). Emit actionable issue per drift category."
script_name: "nsg-diff-desired-state.sh"
expected_issue_severity: [3, 4]
access_level: "read-only"
data_type: "logs-config"
name: "Validate Subnet and NIC NSG Associations"
description: "Cross-check which subnets/NICs attach to each NSG; optionally compare association list to baseline to catch attachment drift."
script_name: "nsg-association-audit.sh"
expected_issue_severity: [2, 3]
access_level: "read-only"
data_type: "logs-config"
name: "Summarize Drift Scope for Operators"
description: "Aggregate drift counts by subscription/RG, link to Azure Portal resource URLs, and suggest rollback via pipeline or IaC reconcile."
script_name: "nsg-drift-summary.sh"
expected_issue_severity: [1, 2]
access_level: "read-only"
data_type: "logs-config"
scope:
level: "ResourceGroup"
qualifiers:
- AZURE_SUBSCRIPTION_ID
- AZURE_RESOURCE_GROUP
- NSG_NAMES
iteration_pattern: |
One SLX per discovered NSG (or per user-provided NSG list) within the
subscription/RG qualifiers. Loop variable: NSG name and resource ID.
resource_types:
generation_strategy: |
Generation rules: platform azure; resourceTypes microsoft.network/networkSecurityGroups
(or equivalent RunWhen resource type for NSGs). Match by name pattern within
configured resource groups. Qualifiers: subscription_id, resource_group, nsg name.
env_vars:
name: AZURE_SUBSCRIPTION_ID
description: "Azure subscription ID for scope"
required: true
name: AZURE_RESOURCE_GROUP
description: "Resource group containing NSGs (or empty with discovery across listed RGs)"
required: false
default: ""
name: NSG_NAMES
description: "Comma-separated NSG names, or 'All' for all NSGs in the resource group"
required: false
default: "All"
name: BASELINE_PATH
description: "Path or URI to baseline JSON (or directory of baseline files) matching export schema"
required: true
name: BASELINE_FORMAT
description: "json-bundle | per-nsg-dir | terraform-show (optional future)"
required: false
default: "json-bundle"
secrets:
description: "Service principal for Azure Resource Manager read access to NSGs and associations"
format: |
JSON: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID
platform:
name: "azure"
cli_tools:
- "az network nsg"
- "az network vnet subnet"
- "az network nic"
- "jq"
- "diff"
auth_methods:
- "Service Principal (azure_credentials)"
api_docs: "https://learn.microsoft.com/en-us/rest/api/virtual-network/network-security-groups/"
related_bundles:
name: "azure-aks-triage"
relationship: "complements"
notes: "AKS triage lists subnet NSG rules in passing; this bundle is dedicated to drift vs baseline across NSGs."
name: "azure-network-security-activity-audit"
relationship: "complements"
notes: "Activity audit explains who changed rules; drift bundle shows what differs from desired state."
test_scenarios:
name: "nsg_matches_baseline"
description: "Live export identical to baseline"
expected_issues: 0
name: "manual_rule_added"
description: "Extra allow rule added in portal not in baseline"
expected_issues: 2
expected_severities: [3, 3]
notes: |
Baseline source is customer-specific: many teams store Terraform in Git and can
export
terraform show -jsonor useaz network nsg showchecked into a configrepo. The bundle should document one supported canonical JSON shape and allow
optional jq transforms for team-specific exports. Effective security rules
(including Azure defaults) may need to be filtered so only customer-managed rule
blocks are compared—parameterize rule name prefixes or ignore lists. Read-only
RBAC: Reader on subscription/RG plus Microsoft.Network/*/read.