Skip to content
5 changes: 5 additions & 0 deletions compliance/frameworks/cis_azure_benchmark.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
"control_name": "Ensure that 'OS disk' are encrypted",
"description": "Virtual machines that are reachable from the internet should have Network Security Groups attached to their network interfaces to control and restrict inbound and outbound traffic, reducing the attack surface."
},
"AZ-CMP-002": {
"control_id": "7.2",
"control_name": "Ensure that 'OS disk' are encrypted",
"description": "Virtual machine OS and data disks are using platform-managed encryption only (EncryptionAtRestWithPlatformKey). CIS 7.2 requires disks to be protected using customer-managed keys or Azure Disk Encryption. Platform-managed encryption does not give the organisation control over the encryption keys and does not satisfy this control."
},
"AZ-KV-001": {
"control_id": "8.5",
"control_name": "Ensure the Key Vault is Recoverable",
Expand Down
5 changes: 5 additions & 0 deletions compliance/frameworks/iso27001.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
"control_name": "Network controls",
"description": "Virtual machines with public IPs and no NSG have unrestricted network access. Network controls should be applied to all compute resources accessible from the internet."
},
"AZ-CMP-002": {
"control_id": "A.10.1.1",
"control_name": "Policy on the use of cryptographic controls",
"description": "Virtual machine OS and data disks are using platform-managed encryption only (EncryptionAtRestWithPlatformKey). A.10.1.1 requires that a policy on the use of cryptographic controls is developed and implemented. Platform-managed encryption does not give the organisation control over the encryption keys. Customer-managed keys or Azure Disk Encryption are required to satisfy this control."
},
"AZ-KV-001": {
"control_id": "A.17.2.1",
"control_name": "Availability of information processing facilities",
Expand Down
5 changes: 5 additions & 0 deletions compliance/frameworks/nist_csf.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
"control_name": "Remote access is managed",
"description": "Virtual machines with public IPs and no NSG have unrestricted network access. NSGs should be attached to control inbound and outbound traffic and manage remote access to compute resources."
},
"AZ-CMP-002": {
"control_id": "PR.DS-1",
"control_name": "Data-at-rest is protected",
"description": "Virtual machine OS and data disks are using platform-managed encryption only (EncryptionAtRestWithPlatformKey). PR.DS-1 requires that data at rest is protected using appropriate controls. Platform-managed encryption does not give the organisation control over the encryption keys. Customer-managed keys or Azure Disk Encryption are required to satisfy this control."
},
"AZ-KV-001": {
"control_id": "PR.IP-4",
"control_name": "Backups of information are conducted, maintained, and tested",
Expand Down
5 changes: 5 additions & 0 deletions compliance/frameworks/soc2.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@
"control_name": "Restricts Access from Outside the Network Boundary",
"description": "A virtual machine with a public IP and no NSG has unrestricted inbound network access from the internet with no filtering in place. CC6.6 requires that logical access from outside the network perimeter is restricted and controlled. Attaching an NSG with explicit rules enforces the network boundary and controls what traffic can reach the VM."
},
"AZ-CMP-002": {
"control_id": "CC6.7",
"control_name": "Protects Data in Transit and At Rest",
"description": "Virtual machine OS and data disks are using platform-managed encryption only (EncryptionAtRestWithPlatformKey). CC6.7 requires that data is protected using encryption. Platform-managed encryption does not give the organisation control over the encryption keys. Customer-managed keys or Azure Disk Encryption are required to satisfy this control."
},
"AZ-KV-001": {
"control_id": "A1.2",
"control_name": "Environmental Threats and Recovery",
Expand Down
39 changes: 39 additions & 0 deletions playbooks/cli/fix_az_cmp_002.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
# OpenShield Remediation Playbook
# Rule: AZ-CMP-002 — Virtual machine disk not protected by CMK or ADE
# Usage: ./fix_az_cmp_002.sh <resource-group> <vm-name> <keyvault-name>
# Severity: HIGH

set -e

RESOURCE_GROUP=$1
VM_NAME=$2
KEYVAULT_NAME=$3

if [ -z "$RESOURCE_GROUP" ] || [ -z "$VM_NAME" ] || [ -z "$KEYVAULT_NAME" ]; then
echo "Usage: $0 <resource-group> <vm-name> <keyvault-name>"
echo ""
echo "Prerequisites:"
echo " 1. Create a Key Vault if one does not exist:"
echo " az keyvault create --resource-group <rg> --name <kv-name> --enabled-for-disk-encryption true"
echo " 2. Ensure the VM is running before enabling encryption"
exit 1
fi

echo "Enabling Azure Disk Encryption on VM '$VM_NAME'..."

az vm encryption enable \
--resource-group "$RESOURCE_GROUP" \
--name "$VM_NAME" \
--disk-encryption-keyvault "$KEYVAULT_NAME" \
--volume-type All

echo "Waiting for encryption to complete..."

az vm encryption show \
--resource-group "$RESOURCE_GROUP" \
--name "$VM_NAME"

echo "Disk encryption enabled on all volumes for VM '$VM_NAME'."
echo "The VM may restart during the encryption process."
echo "Encryption of large disks can take several hours to complete."
115 changes: 115 additions & 0 deletions scanner/rules/az_cmp_002.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""AZ-CMP-002: Virtual machine OS or data disk using platform-managed encryption only."""

import logging
from typing import Any, Dict, List

RULE_ID = "AZ-CMP-002"
RULE_NAME = "Virtual machine disk not protected by customer-managed key or ADE"
SEVERITY = "HIGH"
CATEGORY = "Compute"
FRAMEWORKS = {"CIS": "7.2", "NIST": "PR.DS-1", "ISO27001": "A.10.1.1", "SOC2": "CC6.7"}
DESCRIPTION = (
"One or more disks attached to this virtual machine are using platform-managed "
"encryption only (EncryptionAtRestWithPlatformKey). CIS 7.2 requires disks to be "
"protected using either Azure Disk Encryption (ADE) or server-side encryption with "
"a customer-managed key (CMK). Platform-managed encryption does not give the "
"organisation control over the encryption keys."
)
REMEDIATION = (
"Configure server-side encryption with a customer-managed key via a Disk Encryption "
"Set, or enable Azure Disk Encryption on all OS and data disks. Navigate to: "
"Virtual Machine > Disks > Additional settings > Disk encryption set, or use "
"az vm encryption enable with a Key Vault."
)
PLAYBOOK = "playbooks/cli/fix_az_cmp_002.sh"

logger = logging.getLogger(__name__)


def _disk_needs_flagging(managed_disk: Any) -> bool:
"""Return True only if the disk uses platform-managed encryption.

Azure platform-managed encryption (EncryptionAtRestWithPlatformKey) is the
default for all managed disks and does not satisfy CIS 7.2, which requires
customer-managed keys (CMK) or Azure Disk Encryption (ADE).

Disks using EncryptionAtRestWithCustomerKey or
EncryptionAtRestWithPlatformAndCustomerKeys are compliant and should not
be flagged.
"""
if managed_disk is None:
return False

encryption = getattr(managed_disk, "security_profile", None)
if encryption is None:
encryption = getattr(managed_disk, "encryption", None)

encryption_type = getattr(encryption, "type", None)

if encryption_type is None:
return False

return encryption_type == "EncryptionAtRestWithPlatformKey"


def scan(azure_client: Any, subscription_id: str) -> List[Dict[str, Any]]:
"""Detect virtual machines whose disks use platform-managed encryption only."""
findings: List[Dict[str, Any]] = []

for vm in azure_client.get_virtual_machines():
vm_id = getattr(vm, "id", "")
vm_name = getattr(vm, "name", "")
location = getattr(vm, "location", "")

if not vm_id or not vm_name:
continue

parsed = azure_client.parse_resource_id(vm_id)
resource_group = parsed.get("resource_group", "")

storage_profile = getattr(vm, "storage_profile", None)
if not storage_profile:
continue

unencrypted_disks = []

# Check OS disk
os_disk = getattr(storage_profile, "os_disk", None)
if os_disk:
managed_disk = getattr(os_disk, "managed_disk", None)
if _disk_needs_flagging(managed_disk):
unencrypted_disks.append(
getattr(os_disk, "name", "os-disk")
)

# Check data disks
data_disks = getattr(storage_profile, "data_disks", []) or []
for disk in data_disks:
managed_disk = getattr(disk, "managed_disk", None)
if _disk_needs_flagging(managed_disk):
unencrypted_disks.append(
getattr(disk, "name", f"data-disk-{getattr(disk, 'lun', '?')}")
)

if unencrypted_disks:
findings.append({
"rule_id": RULE_ID,
"rule_name": RULE_NAME,
"severity": SEVERITY,
"category": CATEGORY,
"resource_id": vm_id,
"resource_name": vm_name,
"resource_type": "Microsoft.Compute/virtualMachines",
"description": DESCRIPTION,
"remediation": REMEDIATION,
"playbook": PLAYBOOK,
"frameworks": FRAMEWORKS,
"metadata": {
"resource_group": resource_group,
"location": location,
"unencrypted_disks": unencrypted_disks,
"unencrypted_disk_count": len(unencrypted_disks),
},
})

return findings
Loading