diff --git a/checkov/arm/checks/resource/WinVMAutomaticUpdates.py b/checkov/arm/checks/resource/WinVMAutomaticUpdates.py new file mode 100644 index 00000000000..ecbe38ee424 --- /dev/null +++ b/checkov/arm/checks/resource/WinVMAutomaticUpdates.py @@ -0,0 +1,20 @@ +from checkov.common.models.enums import CheckCategories, CheckResult +from checkov.arm.base_resource_value_check import BaseResourceValueCheck + + +class WinVMAutomaticUpdates(BaseResourceValueCheck): + def __init__(self) -> None: + name = "Ensure Windows VM enables automatic updates" + id = "CKV_AZURE_177" + supported_resources = ("Microsoft.Compute/virtualMachines", "Microsoft.Compute/virtualMachineScaleSets") + categories = (CheckCategories.GENERAL_SECURITY,) + super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, + missing_block_result=CheckResult.PASSED,) + + def get_inspected_key(self) -> str: + if self.entity_type == "Microsoft.Compute/virtualMachineScaleSets": + return "properties/virtualMachineProfile/osProfile/windowsConfiguration/enableAutomaticUpdates" + return "properties/osProfile/windowsConfiguration/enableAutomaticUpdates" + + +check = WinVMAutomaticUpdates() diff --git a/tests/arm/checks/resource/example_WinVMAutomaticUpdates/fail.json b/tests/arm/checks/resource/example_WinVMAutomaticUpdates/fail.json new file mode 100644 index 00000000000..c7e88aa5aff --- /dev/null +++ b/tests/arm/checks/resource/example_WinVMAutomaticUpdates/fail.json @@ -0,0 +1,165 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vmSku": { + "type": "string", + "defaultValue": "Standard_D2s_v3", + "metadata": { + "description": "Size of VMs in the VM Scale Set." + } + }, + "vmssName": { + "type": "string", + "metadata": { + "description": "Unique name for the scale set. Must be 3-61 characters in length and unique across the VNet." + }, + "maxLength": 61 + }, + "instanceCount": { + "type": "int", + "metadata": { + "description": "Number of VM instances (100 or less)." + }, + "defaultValue": 2 + }, + "adminUsername": { + "type": "string", + "metadata": { + "description": "Admin username on all VMs." + } + }, + "adminPassword": { + "type": "securestring", + "metadata": { + "description": "Admin password on all VMs." + } + }, + "existingVnetName": { + "type": "string", + "metadata": { + "description": "Name of the existing virtual network to deploy the scale set into." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location parameter" + } + }, + "existingSubnetName": { + "type": "string", + "metadata": { + "description": "Name of the existing subnet to deploy the scale set into." + } + } + }, + "variables": { + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachineScaleSets", + "name": "fail", + "location": "[parameters('location')]", + "apiVersion": "2020-12-01", + "sku": { + "name": "[parameters('vmSku')]", + "capacity": "[parameters('instanceCount')]" + }, + "properties": { + "overprovision": false, + "upgradePolicy": { + "mode": "Manual" + }, + "virtualMachineProfile": { + "storageProfile": { + "osDisk": { + "createOption": "FromImage", + "caching": "ReadWrite" + }, + "imageReference": { + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer", + "sku": "2016-Datacenter", + "version": "latest" + } + }, + "osProfile": { + "computerNamePrefix": "[parameters('vmssName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]", + "windowsConfiguration": { + "enableAutomaticUpdates": false, + "provisionVmAgent": true + } + }, + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "nic", + "properties": { + "primary": true, + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('existingVnetName'), parameters('existingSubnetName'))]" + } + } + } + ] + } + } + ] + } + } + } + }, + { + "name": "fail", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2019-12-01", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + } + }, + "imageReference": { + "publisher": "MicrosoftVisualStudio", + "offer": "visualstudio2019latest", + "sku": "vs-2019-comm-latest-ws2019", + "version": "latest" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]", + "windowsConfiguration": { + "enableAutomaticUpdates": false, + "provisionVmAgent": true + } + } + } + } + ] +} diff --git a/tests/arm/checks/resource/example_WinVMAutomaticUpdates/pass.json b/tests/arm/checks/resource/example_WinVMAutomaticUpdates/pass.json new file mode 100644 index 00000000000..d3341021058 --- /dev/null +++ b/tests/arm/checks/resource/example_WinVMAutomaticUpdates/pass.json @@ -0,0 +1,229 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vmSku": { + "type": "string", + "defaultValue": "Standard_D2s_v3", + "metadata": { + "description": "Size of VMs in the VM Scale Set." + } + }, + "vmssName": { + "type": "string", + "metadata": { + "description": "Unique name for the scale set. Must be 3-61 characters in length and unique across the VNet." + }, + "maxLength": 61 + }, + "instanceCount": { + "type": "int", + "metadata": { + "description": "Number of VM instances (100 or less)." + }, + "defaultValue": 2 + }, + "adminUsername": { + "type": "string", + "metadata": { + "description": "Admin username on all VMs." + } + }, + "adminPassword": { + "type": "securestring", + "metadata": { + "description": "Admin password on all VMs." + } + }, + "existingVnetName": { + "type": "string", + "metadata": { + "description": "vName of the existing virtual network to deploy the scale set into." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location parameter" + } + }, + "existingSubnetName": { + "type": "string", + "metadata": { + "description": "Name of the existing subnet to deploy the scale set into." + } + } + }, + "variables": { + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachineScaleSets", + "name": "pass", + "location": "[parameters('location')]", + "apiVersion": "2020-12-01", + "sku": { + "name": "[parameters('vmSku')]", + "capacity": "[parameters('instanceCount')]" + }, + "properties": { + "overprovision": false, + "upgradePolicy": { + "mode": "Manual" + }, + "virtualMachineProfile": { + "storageProfile": { + "osDisk": { + "createOption": "FromImage", + "caching": "ReadWrite" + }, + "imageReference": { + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer", + "sku": "2016-Datacenter", + "version": "latest" + } + }, + "osProfile": { + "computerNamePrefix": "[parameters('vmssName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]", + "windowsConfiguration": { + "enableAutomaticUpdates": true, + "provisionVmAgent": true + } + }, + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "nic", + "properties": { + "primary": true, + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('existingVnetName'), parameters('existingSubNetName'))]" + } + } + } + ] + } + } + ] + } + } + } + }, + { + "name": "pass", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2019-12-01", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + } + }, + "imageReference": { + "publisher": "MicrosoftVisualStudio", + "offer": "visualstudio2019latest", + "sku": "vs-2019-comm-latest-ws2019", + "version": "latest" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]", + "windowsConfiguration": { + "enableAutomaticUpdates": true, + "provisionVmAgent": true + } + } + } + }, + { + "apiVersion": "2019-12-01", + "type": "Microsoft.Compute/virtualMachines", + "name": "missing", + "location": "[parameters('location')]", + "dependsOn": [ + "[variables('storageAccountName')]", + "[variables('nicName')]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "osProfile": { + "computerName": "[variables('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]" + }, + "storageProfile": { + "imageReference": { + "publisher": "[variables('imagePublisher')]", + "offer": "[variables('imageOffer')]", + "sku": "[parameters('windowsOSVersion')]", + "version": "latest" + } + } + } + }, + { + "type": "Microsoft.Compute/virtualMachineScaleSets", + "name": "missing", + "location": "[parameters('location')]", + "apiVersion": "2020-12-01", + "sku": { + "name": "[parameters('vmSku')]", + "capacity": "[parameters('instanceCount')]" + }, + "properties": { + "overprovision": false, + "upgradePolicy": { + "mode": "Manual" + }, + "virtualMachineProfile": { + "storageProfile": { + "osDisk": { + "createOption": "FromImage", + "caching": "ReadWrite" + }, + "imageReference": { + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer", + "sku": "2016-Datacenter", + "version": "latest" + } + }, + "osProfile": { + "computerNamePrefix": "[parameters('vmssName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]" + + } + } + } + } + ] +} diff --git a/tests/arm/checks/resource/test_WinVMAutomaticUpdates.py b/tests/arm/checks/resource/test_WinVMAutomaticUpdates.py new file mode 100644 index 00000000000..7f6e3562fb1 --- /dev/null +++ b/tests/arm/checks/resource/test_WinVMAutomaticUpdates.py @@ -0,0 +1,44 @@ +import unittest +from pathlib import Path + +from checkov.runner_filter import RunnerFilter +from checkov.arm.checks.resource.WinVMAutomaticUpdates import check +from checkov.arm.runner import Runner + + +class TestWinVMAutomaticUpdates(unittest.TestCase): + def test(self): + # given + test_files_dir = Path(__file__).parent / "example_WinVMAutomaticUpdates" + + # when + report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id])) + + # then + summary = report.get_summary() + + passing_resources = { + "Microsoft.Compute/virtualMachines.pass", + "Microsoft.Compute/virtualMachineScaleSets.pass", + "Microsoft.Compute/virtualMachines.missing", + "Microsoft.Compute/virtualMachineScaleSets.missing" + } + failing_resources = { + "Microsoft.Compute/virtualMachines.fail", + "Microsoft.Compute/virtualMachineScaleSets.fail", + } + + passed_check_resources = {c.resource for c in report.passed_checks} + failed_check_resources = {c.resource for c in report.failed_checks} + + self.assertEqual(summary["passed"], len(passing_resources)) + self.assertEqual(summary["failed"], len(failing_resources)) + self.assertEqual(summary["skipped"], 0) + self.assertEqual(summary["parsing_errors"], 0) + + self.assertEqual(passing_resources, passed_check_resources) + self.assertEqual(failing_resources, failed_check_resources) + + +if __name__ == "__main__": + unittest.main()