From de77f3ff132bc071e75e799d361161ddd9cf12c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Mon, 5 Feb 2024 11:39:05 +0100 Subject: [PATCH] feat(azure): new check sqlserver_vulnerability_assessment_enabled (#3349) --- .../services/sqlserver/sqlserver_service.py | 17 ++ .../__init__.py | 0 ...erability_assessment_enabled.metadata.json | 30 +++ ...server_vulnerability_assessment_enabled.py | 25 +++ tests/lib/check/check_test.py | 32 +++ .../sqlserver_auditing_enabled_test.py | 3 +- .../sqlserver/sqlserver_service_test.py | 31 ++- ...r_vulnerability_assessment_enabled_test.py | 190 ++++++++++++++++++ 8 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/__init__.py create mode 100644 prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.metadata.json create mode 100644 prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.py create mode 100644 tests/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled_test.py diff --git a/prowler/providers/azure/services/sqlserver/sqlserver_service.py b/prowler/providers/azure/services/sqlserver/sqlserver_service.py index 0516e04a..6a2027c7 100644 --- a/prowler/providers/azure/services/sqlserver/sqlserver_service.py +++ b/prowler/providers/azure/services/sqlserver/sqlserver_service.py @@ -6,6 +6,7 @@ from azure.mgmt.sql.models import ( FirewallRule, ServerBlobAuditingPolicy, ServerExternalAdministrator, + ServerVulnerabilityAssessment, TransparentDataEncryption, ) @@ -40,6 +41,9 @@ class SQLServer(AzureService): encryption_protector = self.__get_enctyption_protectors__( subscription, resource_group, sql_server.name ) + vulnerability_assessment = self.__get_vulnerability_assesments__( + subscription, resource_group, sql_server.name + ) sql_servers[subscription].append( SQL_Server( id=sql_server.id, @@ -53,6 +57,7 @@ class SQLServer(AzureService): databases=self.__get_databases__( subscription, resource_group, sql_server.name ), + vulnerability_assessment=vulnerability_assessment, ) ) except Exception as error: @@ -115,6 +120,17 @@ class SQLServer(AzureService): ) return databases + def __get_vulnerability_assesments__( + self, subscription, resource_group, server_name + ): + client = self.clients[subscription] + vulnerability_assessment = client.server_vulnerability_assessments.get( + resource_group_name=resource_group, + server_name=server_name, + vulnerability_assessment_name="default", + ) + return vulnerability_assessment + @dataclass class DatabaseServer: @@ -137,3 +153,4 @@ class SQL_Server: firewall_rules: FirewallRule encryption_protector: EncryptionProtector = None databases: list[DatabaseServer] = None + vulnerability_assessment: ServerVulnerabilityAssessment = None diff --git a/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/__init__.py b/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.metadata.json b/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.metadata.json new file mode 100644 index 00000000..26ab2f80 --- /dev/null +++ b/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "sqlserver_vulnerability_assessment_enabled", + "CheckTitle": "Ensure that Vulnerability Assessment (VA) is enabled on a SQL server by setting a Storage Account", + "CheckType": [], + "ServiceName": "sqlserver", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "SQLServer", + "Description": "Enable Vulnerability Assessment (VA) service scans for critical SQL servers and corresponding SQL databases.", + "Risk": "The Vulnerability Assessment service scans databases for known security vulnerabilities and highlights deviations from best practices, such as misconfigurations, excessive permissions, and unprotected sensitive data. Results of the scan include actionable steps to resolve each issue and provide customized remediation scripts where applicable. Additionally, an assessment report can be customized by setting an acceptable baseline for permission configurations, feature configurations, and database settings.", + "RelatedUrl": "https://docs.microsoft.com/en-us/azure/sql-database/sql-vulnerability-assessment", + "Remediation": { + "Code": { + "CLI": "Update-AzSqlServerVulnerabilityAssessmentSetting -ResourceGroupName resource_group_name -ServerName Server_Name -StorageAccountName Storage_Name_from_same_subscription_and_same_Location -ScanResultsContainerName vulnerability-assessment -RecurringScansInterval Weekly -EmailSubscriptionAdmins $true -NotificationEmail @('mail1@mail.com' , 'mail2@mail.com')", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/Sql/vulnerability-assessment-sql-servers.html#", + "Terraform": "https://docs.bridgecrew.io/docs/ensure-that-vulnerability-assessment-va-is-enabled-on-a-sql-server-by-setting-a-storage-account" + }, + "Recommendation": { + "Text": "1. Go to SQL servers 2. Select a server instance 3. Click on Security Center 4. Select Configure next to Enabled at subscription-level 5. In Section Vulnerability Assessment Settings, Click Select Storage account 6. Choose Storage Account (Existing or Create New). Click Ok 7. Click Save", + "Url": "https://learn.microsoft.com/en-us/azure/defender-for-cloud/sql-azure-vulnerability-assessment-enable" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Enabling the Microsoft Defender for SQL features will incur additional costs for each SQL server." +} diff --git a/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.py b/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.py new file mode 100644 index 00000000..a01cce57 --- /dev/null +++ b/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled.py @@ -0,0 +1,25 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.sqlserver.sqlserver_client import sqlserver_client + + +class sqlserver_vulnerability_assessment_enabled(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + for subscription, sql_servers in sqlserver_client.sql_servers.items(): + for sql_server in sql_servers: + report = Check_Report_Azure(self.metadata()) + report.subscription = subscription + report.resource_name = sql_server.name + report.resource_id = sql_server.id + report.status = "FAIL" + report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} has vulnerability assessment disabled." + if ( + sql_server.vulnerability_assessment + and sql_server.vulnerability_assessment.storage_container_path + is not None + ): + report.status = "PASS" + report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} has vulnerability assessment enabled." + findings.append(report) + + return findings diff --git a/tests/lib/check/check_test.py b/tests/lib/check/check_test.py index c3ffb6a6..1f628d92 100644 --- a/tests/lib/check/check_test.py +++ b/tests/lib/check/check_test.py @@ -115,6 +115,20 @@ expected_packages = [ name="prowler.providers.azure.services.sqlserver.sqlserver_auditing_retention_90_days.sqlserver_auditing_retention_90_days", ispkg=False, ), + ModuleInfo( + module_finder=FileFinder( + "/root_dir/prowler/providers/azure/services/sqlserver" + ), + name="prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled", + ispkg=True, + ), + ModuleInfo( + module_finder=FileFinder( + "/root_dir/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled" + ), + name="prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled", + ispkg=False, + ), ] @@ -208,6 +222,20 @@ def mock_list_modules(*_): name="prowler.providers.azure.services.sqlserver.sqlserver_auditing_retention_90_days.sqlserver_auditing_retention_90_days", ispkg=False, ), + ModuleInfo( + module_finder=FileFinder( + "/root_dir/prowler/providers/azure/services/sqlserver" + ), + name="prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled", + ispkg=True, + ), + ModuleInfo( + module_finder=FileFinder( + "/root_dir/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled" + ), + name="prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled", + ispkg=False, + ), ] return modules @@ -601,6 +629,10 @@ class Test_Check: "sqlserver_auditing_retention_90_days", "/root_dir/prowler/providers/azure/services/sqlserver/sqlserver_auditing_retention_90_days", ), + ( + "sqlserver_vulnerability_assessment_enabled", + "/root_dir/prowler/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled", + ), ] returned_checks = recover_checks_from_provider(provider, service) assert returned_checks == expected_checks diff --git a/tests/providers/azure/services/sqlserver/sqlserver_auditing_enabled/sqlserver_auditing_enabled_test.py b/tests/providers/azure/services/sqlserver/sqlserver_auditing_enabled/sqlserver_auditing_enabled_test.py index a202d645..c02aed42 100644 --- a/tests/providers/azure/services/sqlserver/sqlserver_auditing_enabled/sqlserver_auditing_enabled_test.py +++ b/tests/providers/azure/services/sqlserver/sqlserver_auditing_enabled/sqlserver_auditing_enabled_test.py @@ -8,8 +8,7 @@ from azure.mgmt.sql.models import ( ) from prowler.providers.azure.services.sqlserver.sqlserver_service import SQL_Server - -AZURE_SUSCRIPTION = str(uuid4()) +from tests.providers.azure.azure_fixtures import AZURE_SUSCRIPTION class Test_sqlserver_auditing_enabled: diff --git a/tests/providers/azure/services/sqlserver/sqlserver_service_test.py b/tests/providers/azure/services/sqlserver/sqlserver_service_test.py index 8813b307..41f8ee1f 100644 --- a/tests/providers/azure/services/sqlserver/sqlserver_service_test.py +++ b/tests/providers/azure/services/sqlserver/sqlserver_service_test.py @@ -1,6 +1,10 @@ from unittest.mock import patch -from azure.mgmt.sql.models import EncryptionProtector, TransparentDataEncryption +from azure.mgmt.sql.models import ( + EncryptionProtector, + ServerVulnerabilityAssessment, + TransparentDataEncryption, +) from prowler.providers.azure.services.sqlserver.sqlserver_service import ( DatabaseServer, @@ -36,6 +40,9 @@ def mock_sqlserver_get_sql_servers(_): server_key_type="AzureKeyVault" ), databases=[database], + vulnerability_assessment=ServerVulnerabilityAssessment( + storage_container_path="/subcription_id/resource_group/sql_server" + ), ) ] } @@ -87,6 +94,12 @@ class Test_SqlServer_Service: == "EncryptionProtector" ) assert sql_server.sql_servers[AZURE_SUSCRIPTION][0].databases == [database] + assert ( + sql_server.sql_servers[AZURE_SUSCRIPTION][ + 0 + ].vulnerability_assessment.__class__.__name__ + == "ServerVulnerabilityAssessment" + ) def test__get_databases__(self): sql_server = SQLServer(set_mocked_azure_audit_info()) @@ -146,3 +159,19 @@ class Test_SqlServer_Service: id = "/subscriptions/subscription_id/resourceGroups/resource_group/providers/Microsoft.Sql/servers/sql_server" sql_server = SQLServer(set_mocked_azure_audit_info()) assert sql_server.__get_resource_group__(id) == "resource_group" + + def test__get_vulnerability_assessment__(self): + sql_server = SQLServer(set_mocked_azure_audit_info()) + storage_container_path = "/subcription_id/resource_group/sql_server" + assert ( + sql_server.sql_servers[AZURE_SUSCRIPTION][ + 0 + ].vulnerability_assessment.__class__.__name__ + == "ServerVulnerabilityAssessment" + ) + assert ( + sql_server.sql_servers[AZURE_SUSCRIPTION][ + 0 + ].vulnerability_assessment.storage_container_path + == storage_container_path + ) diff --git a/tests/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled_test.py b/tests/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled_test.py new file mode 100644 index 00000000..3e181c19 --- /dev/null +++ b/tests/providers/azure/services/sqlserver/sqlserver_vulnerability_assessment_enabled/sqlserver_vulnerability_assessment_enabled_test.py @@ -0,0 +1,190 @@ +from unittest import mock +from uuid import uuid4 + +from azure.mgmt.sql.models import ( + EncryptionProtector, + ServerVulnerabilityAssessment, + TransparentDataEncryption, +) + +from prowler.providers.azure.services.sqlserver.sqlserver_service import ( + DatabaseServer, + SQL_Server, +) + +AZURE_SUSCRIPTION = str(uuid4()) + + +class Test_sqlserver_vulnerability_assessment_enabled: + def test_no_sql_servers(self): + sqlserver_client = mock.MagicMock + sqlserver_client.sql_servers = {} + + with mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled import ( + sqlserver_vulnerability_assessment_enabled, + ) + + check = sqlserver_vulnerability_assessment_enabled() + result = check.execute() + assert len(result) == 0 + + def test_sql_servers_no_vulnerability_assessment(self): + sqlserver_client = mock.MagicMock + sql_server_name = "SQL Server Name" + sql_server_id = str(uuid4()) + database = DatabaseServer( + id="id", + name="name", + type="type", + location="location", + managed_by="managed_by", + tde_encryption=None, + ) + sqlserver_client.sql_servers = { + AZURE_SUSCRIPTION: [ + SQL_Server( + id=sql_server_id, + name=sql_server_name, + public_network_access="", + minimal_tls_version="", + administrators=None, + auditing_policies=None, + firewall_rules=None, + databases=[database], + encryption_protector=EncryptionProtector( + server_key_type="ServiceManaged" + ), + vulnerability_assessment=None, + ) + ] + } + + with mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled import ( + sqlserver_vulnerability_assessment_enabled, + ) + + check = sqlserver_vulnerability_assessment_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"SQL Server {sql_server_name} from subscription {AZURE_SUSCRIPTION} has vulnerability assessment disabled." + ) + assert result[0].subscription == AZURE_SUSCRIPTION + assert result[0].resource_name == sql_server_name + assert result[0].resource_id == sql_server_id + + def test_sql_servers_no_vulnerability_assessment_path(self): + sqlserver_client = mock.MagicMock + sql_server_name = "SQL Server Name" + sql_server_id = str(uuid4()) + database = DatabaseServer( + id="id", + name="name", + type="type", + location="location", + managed_by="managed_by", + tde_encryption=TransparentDataEncryption(status="Disabled"), + ) + sqlserver_client.sql_servers = { + AZURE_SUSCRIPTION: [ + SQL_Server( + id=sql_server_id, + name=sql_server_name, + public_network_access="", + minimal_tls_version="", + administrators=None, + auditing_policies=None, + firewall_rules=None, + databases=[database], + encryption_protector=EncryptionProtector( + server_key_type="AzureKeyVault" + ), + vulnerability_assessment=ServerVulnerabilityAssessment( + storage_container_path=None + ), + ) + ] + } + + with mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled import ( + sqlserver_vulnerability_assessment_enabled, + ) + + check = sqlserver_vulnerability_assessment_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"SQL Server {sql_server_name} from subscription {AZURE_SUSCRIPTION} has vulnerability assessment disabled." + ) + assert result[0].subscription == AZURE_SUSCRIPTION + assert result[0].resource_name == sql_server_name + assert result[0].resource_id == sql_server_id + + def test_sql_servers_vulnerability_assessment_enabled(self): + sqlserver_client = mock.MagicMock + sql_server_name = "SQL Server Name" + sql_server_id = str(uuid4()) + database = DatabaseServer( + id="id", + name="name", + type="type", + location="location", + managed_by="managed_by", + tde_encryption=TransparentDataEncryption(status="Enabled"), + ) + sqlserver_client.sql_servers = { + AZURE_SUSCRIPTION: [ + SQL_Server( + id=sql_server_id, + name=sql_server_name, + public_network_access="", + minimal_tls_version="", + administrators=None, + auditing_policies=None, + firewall_rules=None, + databases=[database], + encryption_protector=EncryptionProtector( + server_key_type="AzureKeyVault" + ), + vulnerability_assessment=ServerVulnerabilityAssessment( + storage_container_path="/subcription_id/resource_group/sql_server" + ), + ) + ] + } + + with mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_vulnerability_assessment_enabled.sqlserver_vulnerability_assessment_enabled import ( + sqlserver_vulnerability_assessment_enabled, + ) + + check = sqlserver_vulnerability_assessment_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"SQL Server {sql_server_name} from subscription {AZURE_SUSCRIPTION} has vulnerability assessment enabled." + ) + assert result[0].subscription == AZURE_SUSCRIPTION + assert result[0].resource_name == sql_server_name + assert result[0].resource_id == sql_server_id