From 030b183cb1a8e686c3022b81a2bfe13024cacb54 Mon Sep 17 00:00:00 2001 From: James Woolfenden Date: Thu, 14 Sep 2023 20:41:09 +0100 Subject: [PATCH] feat(arm): implement CKV2_AZURE_27 for arm (#5534) * feat(arm): implement CKV2_AZURE_27 for arm * revert --- .../checks/resource/SQLServerUsesADAuth.py | 29 +++ .../azure/AzureConfigMSSQLwithAD.yaml | 4 +- .../example_SQLServerUsesADAuth/fail.json | 181 ++++++++++++++++++ .../example_SQLServerUsesADAuth/pass.json | 179 +++++++++++++++++ .../resource/test_SQLServerUsesADAuth.py | 40 ++++ 5 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 checkov/arm/checks/resource/SQLServerUsesADAuth.py create mode 100644 tests/arm/checks/resource/example_SQLServerUsesADAuth/fail.json create mode 100644 tests/arm/checks/resource/example_SQLServerUsesADAuth/pass.json create mode 100644 tests/arm/checks/resource/test_SQLServerUsesADAuth.py diff --git a/checkov/arm/checks/resource/SQLServerUsesADAuth.py b/checkov/arm/checks/resource/SQLServerUsesADAuth.py new file mode 100644 index 00000000000..3b228e23fc7 --- /dev/null +++ b/checkov/arm/checks/resource/SQLServerUsesADAuth.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from typing import Any + +from checkov.common.models.consts import ANY_VALUE +from checkov.common.models.enums import CheckCategories +from checkov.arm.base_resource_negative_value_check import BaseResourceNegativeValueCheck + + +class SQLServerUsesADAuth(BaseResourceNegativeValueCheck): + def __init__(self) -> None: + """ + I think that this check is really, ensure that only AD auth is used (not user/pass) + """ + + name = "Ensure Azure AD authentication is enabled for Azure SQL (MSSQL)" + id = "CKV2_AZURE_27" + supported_resources = ["Microsoft.Sql/servers"] + categories = [CheckCategories.GENERAL_SECURITY] + super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources) + + def get_inspected_key(self) -> str: + return 'properties/administratorLogin' + + def get_forbidden_values(self) -> list[Any]: + return [ANY_VALUE] + + +check = SQLServerUsesADAuth() diff --git a/checkov/terraform/checks/graph_checks/azure/AzureConfigMSSQLwithAD.yaml b/checkov/terraform/checks/graph_checks/azure/AzureConfigMSSQLwithAD.yaml index 3adcd44f85b..350ed303a77 100644 --- a/checkov/terraform/checks/graph_checks/azure/AzureConfigMSSQLwithAD.yaml +++ b/checkov/terraform/checks/graph_checks/azure/AzureConfigMSSQLwithAD.yaml @@ -17,5 +17,5 @@ definition: operator: "number_of_words_not_equals" value: 0 -# Checking for condition "number_of_words_not_equals=0" instead of "is_not_empty" because -# even whitespaces were getting considered in terraform YAML file \ No newline at end of file +# Checking for condition "number_of_words_not_equals=0" instead of "is_not_empty" because +# even whitespaces were getting considered in terraform YAML file diff --git a/tests/arm/checks/resource/example_SQLServerUsesADAuth/fail.json b/tests/arm/checks/resource/example_SQLServerUsesADAuth/fail.json new file mode 100644 index 00000000000..47172be23ec --- /dev/null +++ b/tests/arm/checks/resource/example_SQLServerUsesADAuth/fail.json @@ -0,0 +1,181 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "administratorLogin": { + "type": "string", + "defaultValue": "anythingisbad" + }, + "administratorLoginPassword": { + "type": "securestring", + "defaultValue": "" + }, + "administrators": { + "type": "object", + "defaultValue": {} + }, + "location": { + "type": "string" + }, + "serverName": { + "type": "string" + }, + "enableADS": { + "type": "bool", + "defaultValue": false + }, + "useVAManagedIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "To enable vulnerability assessments, the user deploying this template must have an administrator or owner permissions." + } + }, + "vaStoragelessEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Flag for enabling vulnerability assessments with express configuration (storage less), the user deploying this template must have administrator or owner permissions." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "" + }, + "minimalTlsVersion": { + "type": "string", + "defaultValue": "" + }, + "allowAzureIps": { + "type": "bool", + "defaultValue": true + }, + "enableVA": { + "type": "bool", + "defaultValue": false + }, + "serverTags": { + "type": "object", + "defaultValue": {} + } + }, + "variables": { + "subscriptionId": "[subscription().subscriptionId]", + "resourceGroupName": "[resourceGroup().name]", + "uniqueStorage": "[uniqueString(variables('subscriptionId'), variables('resourceGroupName'), parameters('location'))]", + "storageName": "[tolower(concat('sqlva', variables('uniqueStorage')))]", + "uniqueRoleGuid": "[guid(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), variables('storageBlobContributor'), resourceId('Microsoft.Sql/servers', parameters('serverName')))]", + "StorageBlobContributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + }, + "resources": [ + { + "condition": "[parameters('enableVA')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-04-01", + "name": "[variables('storageName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "minimumTlsVersion": "TLS1_2", + "supportsHttpsTrafficOnly": "true", + "allowBlobPublicAccess": "false" + }, + "resources": [ + { + "condition": "[parameters('useVAManagedIdentity')]", + "type": "Microsoft.Storage/storageAccounts/providers/roleAssignments", + "apiVersion": "2018-09-01-preview", + "name": "[concat(variables('storageName'), '/Microsoft.Authorization/', variables('uniqueRoleGuid') )]", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]" + ], + "properties": { + "roleDefinitionId": "[variables('StorageBlobContributor')]", + "principalId": "[reference(resourceId('Microsoft.Sql/servers', parameters('serverName')), '2018-06-01-preview', 'Full').identity.principalId]", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]", + "principalType": "ServicePrincipal" + } + } + ] + }, + { + "type": "Microsoft.Sql/servers", + "apiVersion": "2020-11-01-preview", + "name": "fail", + "location": "[parameters('location')]", + "properties": { + "version": "12.0", + "minimalTlsVersion": "[parameters('minimalTlsVersion')]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "administratorLogin": "[parameters('administratorLogin')]", + "administratorLoginPassword": "[parameters('administratorLoginPassword')]", + "administrators": "[parameters('administrators')]" + }, + "identity": "[if(and(parameters('enableVA'),parameters('useVAManagedIdentity')), json('{\"type\":\"SystemAssigned\"}'), json('null'))]", + "tags": "[parameters('serverTags')]", + "resources": [ + { + "condition": "[parameters('allowAzureIPs')]", + "type": "firewallRules", + "apiVersion": "2021-11-01", + "name": "AllowAllWindowsAzureIps", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" + ], + "properties": { + "endIpAddress": "0.0.0.0", + "startIpAddress": "0.0.0.0" + } + }, + { + "condition": "[parameters('enableADS')]", + "type": "advancedThreatProtectionSettings", + "apiVersion": "2021-11-01-preview", + "name": "Default", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" + ], + "properties": { + "state": "Enabled" + } + }, + { + "condition": "[parameters('enableVA')]", + "type": "vulnerabilityAssessments", + "apiVersion": "2018-06-01-preview", + "name": "Default", + "dependsOn": [ + "[concat('Microsoft.Sql/servers/', parameters('serverName'))]", + "[concat('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[concat('Microsoft.Sql/servers/', parameters('serverName'), '/advancedThreatProtectionSettings/Default')]" + ], + "properties": { + "storageContainerPath": "[if(parameters('enableVA'), concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))).primaryEndpoints.blob, 'vulnerability-assessment'), '')]", + "storageAccountAccessKey": "[if(and(parameters('enableVA'),not(parameters('useVAManagedIdentity'))), listKeys(variables('storageName'), '2018-02-01').keys[0].value, '')]", + "recurringScans": { + "isEnabled": true, + "emailSubscriptionAdmins": false + } + } + }, + { + "condition": "[parameters('vaStoragelessEnabled')]", + "type": "sqlVulnerabilityAssessments", + "apiVersion": "2022-02-01-preview", + "name": "Default", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" + ], + "properties": { + "state": "Enabled" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/arm/checks/resource/example_SQLServerUsesADAuth/pass.json b/tests/arm/checks/resource/example_SQLServerUsesADAuth/pass.json new file mode 100644 index 00000000000..a14d0be0cb6 --- /dev/null +++ b/tests/arm/checks/resource/example_SQLServerUsesADAuth/pass.json @@ -0,0 +1,179 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "administratorLogin": { + "type": "string", + "defaultValue": "" + }, + "administratorLoginPassword": { + "type": "securestring", + "defaultValue": "" + }, + "administrators": { + "type": "object", + "defaultValue": {} + }, + "location": { + "type": "string" + }, + "serverName": { + "type": "string" + }, + "enableADS": { + "type": "bool", + "defaultValue": false + }, + "useVAManagedIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "To enable vulnerability assessments, the user deploying this template must have an administrator or owner permissions." + } + }, + "vaStoragelessEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Flag for enabling vulnerability assessments with express configuration (storage less), the user deploying this template must have administrator or owner permissions." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "" + }, + "minimalTlsVersion": { + "type": "string", + "defaultValue": "" + }, + "allowAzureIps": { + "type": "bool", + "defaultValue": true + }, + "enableVA": { + "type": "bool", + "defaultValue": false + }, + "serverTags": { + "type": "object", + "defaultValue": {} + } + }, + "variables": { + "subscriptionId": "[subscription().subscriptionId]", + "resourceGroupName": "[resourceGroup().name]", + "uniqueStorage": "[uniqueString(variables('subscriptionId'), variables('resourceGroupName'), parameters('location'))]", + "storageName": "[tolower(concat('sqlva', variables('uniqueStorage')))]", + "uniqueRoleGuid": "[guid(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), variables('storageBlobContributor'), resourceId('Microsoft.Sql/servers', parameters('serverName')))]", + "StorageBlobContributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + }, + "resources": [ + { + "condition": "[parameters('enableVA')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-04-01", + "name": "[variables('storageName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "minimumTlsVersion": "TLS1_2", + "supportsHttpsTrafficOnly": "true", + "allowBlobPublicAccess": "false" + }, + "resources": [ + { + "condition": "[parameters('useVAManagedIdentity')]", + "type": "Microsoft.Storage/storageAccounts/providers/roleAssignments", + "apiVersion": "2018-09-01-preview", + "name": "[concat(variables('storageName'), '/Microsoft.Authorization/', variables('uniqueRoleGuid') )]", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]" + ], + "properties": { + "roleDefinitionId": "[variables('StorageBlobContributor')]", + "principalId": "[reference(resourceId('Microsoft.Sql/servers', parameters('serverName')), '2018-06-01-preview', 'Full').identity.principalId]", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]", + "principalType": "ServicePrincipal" + } + } + ] + }, + { + "type": "Microsoft.Sql/servers", + "apiVersion": "2020-11-01-preview", + "name": "pass", + "location": "[parameters('location')]", + "properties": { + "version": "12.0", + "minimalTlsVersion": "[parameters('minimalTlsVersion')]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "administrators": "[parameters('administrators')]" + }, + "identity": "[if(and(parameters('enableVA'),parameters('useVAManagedIdentity')), json('{\"type\":\"SystemAssigned\"}'), json('null'))]", + "tags": "[parameters('serverTags')]", + "resources": [ + { + "condition": "[parameters('allowAzureIPs')]", + "type": "firewallRules", + "apiVersion": "2021-11-01", + "name": "AllowAllWindowsAzureIps", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" + ], + "properties": { + "endIpAddress": "0.0.0.0", + "startIpAddress": "0.0.0.0" + } + }, + { + "condition": "[parameters('enableADS')]", + "type": "advancedThreatProtectionSettings", + "apiVersion": "2021-11-01-preview", + "name": "Default", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" + ], + "properties": { + "state": "Enabled" + } + }, + { + "condition": "[parameters('enableVA')]", + "type": "vulnerabilityAssessments", + "apiVersion": "2018-06-01-preview", + "name": "Default", + "dependsOn": [ + "[concat('Microsoft.Sql/servers/', parameters('serverName'))]", + "[concat('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[concat('Microsoft.Sql/servers/', parameters('serverName'), '/advancedThreatProtectionSettings/Default')]" + ], + "properties": { + "storageContainerPath": "[if(parameters('enableVA'), concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))).primaryEndpoints.blob, 'vulnerability-assessment'), '')]", + "storageAccountAccessKey": "[if(and(parameters('enableVA'),not(parameters('useVAManagedIdentity'))), listKeys(variables('storageName'), '2018-02-01').keys[0].value, '')]", + "recurringScans": { + "isEnabled": true, + "emailSubscriptionAdmins": false + } + } + }, + { + "condition": "[parameters('vaStoragelessEnabled')]", + "type": "sqlVulnerabilityAssessments", + "apiVersion": "2022-02-01-preview", + "name": "Default", + "dependsOn": [ + "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" + ], + "properties": { + "state": "Enabled" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/arm/checks/resource/test_SQLServerUsesADAuth.py b/tests/arm/checks/resource/test_SQLServerUsesADAuth.py new file mode 100644 index 00000000000..903322ff3b4 --- /dev/null +++ b/tests/arm/checks/resource/test_SQLServerUsesADAuth.py @@ -0,0 +1,40 @@ +import unittest +from pathlib import Path + +from checkov.arm.checks.resource.SQLServerUsesADAuth import check +from checkov.arm.runner import Runner +from checkov.runner_filter import RunnerFilter + + +class TestSQLServerUsesADAuth(unittest.TestCase): + def test_summary(self): + # given + test_files_dir = Path(__file__).parent / "example_SQLServerUsesADAuth" + + # when + report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id])) + + # then + summary = report.get_summary() + + passing_resources = { + "Microsoft.Sql/servers.pass", + } + failing_resources = { + "Microsoft.Sql/servers.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()