feat(azure): add Azure SQL Server service and 3 checks (#2665)

Co-authored-by: Pepe Fagoaga <pepe@verica.io>
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
This commit is contained in:
edurra
2023-08-03 11:29:17 +02:00
committed by GitHub
parent 2d832bca15
commit 44f7af3580
19 changed files with 299 additions and 3 deletions

18
poetry.lock generated
View File

@@ -168,6 +168,22 @@ azure-common = ">=1.1,<2.0"
azure-mgmt-core = ">=1.3.2,<2.0.0"
isodate = ">=0.6.1,<1.0.0"
[[package]]
name = "azure-mgmt-sql"
version = "3.0.1"
description = "Microsoft Azure SQL Management Client Library for Python"
optional = false
python-versions = "*"
files = [
{file = "azure-mgmt-sql-3.0.1.zip", hash = "sha256:129042cc011225e27aee6ef2697d585fa5722e5d1aeb0038af6ad2451a285457"},
{file = "azure_mgmt_sql-3.0.1-py2.py3-none-any.whl", hash = "sha256:1d1dd940d4d41be4ee319aad626341251572a5bf4a2addec71779432d9a1381f"},
]
[package.dependencies]
azure-common = ">=1.1,<2.0"
azure-mgmt-core = ">=1.2.0,<2.0.0"
msrest = ">=0.6.21"
[[package]]
name = "azure-mgmt-storage"
version = "21.0.0"
@@ -2875,4 +2891,4 @@ docs = ["mkdocs", "mkdocs-material"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "2e4af8e15db5d10b860a960d9fa3f4511182f858121e326f98aeca4a1bd75c86"
content-hash = "af1b95fa997c6e4eb7f6764a69c00cb89aac5cfce77c130e1722f6322f495755"

View File

@@ -15,7 +15,6 @@ class IAM(AzureService):
audit_info.identity.subscriptions, audit_info.credentials
)
self.roles = self.__get_roles__()
self.region = "azure"
def __set_clients__(self, subscriptions, credentials):
clients = {}

View File

@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "sqlserver_auditing_enabled",
"CheckTitle": "Ensure that SQL Servers have an audit policy configured",
"CheckType": [],
"ServiceName": "sqlserver",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "SQLServer",
"Description": "Ensure that there is an audit policy configured",
"Risk": "Audit policies are used to store logs associated to the SLQ server (for instance, successful/unsuccesful log in attempts). These logs may be useful to detect anomalies or to perform an investigation in case a security incident is detected",
"RelatedUrl": "https://docs.microsoft.com/en-us/azure/sql-database/sql-database-auditing",
"Remediation": {
"Code": {
"CLI": "https://docs.bridgecrew.io/docs/bc_azr_logging_2#cli-command",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Sql/auditing.html",
"Terraform": "https://docs.bridgecrew.io/docs/bc_azr_logging_2#terraform"
},
"Recommendation": {
"Text": "Create an audit policy for the SQL server",
"Url": ""
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -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_auditing_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.status = "PASS"
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} has a auditing policy configured"
report.resource_name = sql_server.name
report.resource_id = sql_server.id
for auditing_policy in sql_server.auditing_policies:
if auditing_policy.state == "Disabled":
report.status = "FAIL"
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} does not have any auditing policy configured"
break
findings.append(report)
return findings

View File

@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "sqlserver_azuread_administrator_enabled",
"CheckTitle": "Ensure that SQL Servers have an Azure Active Directory administrator",
"CheckType": [],
"ServiceName": "sqlserver",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "SQLServer",
"Description": "Ensure that there is an Azure Active Directory administrator configured",
"Risk": "Azure Active Directory provides a centralized way of managing identities. Using local SQL administrator identites makes it more difficult to manage user accounts. In addition, from Azure Active Directory, security policies can be enforced to users in centralized way.",
"RelatedUrl": "https://docs.microsoft.com/en-us/azure/sql-database/sql-database-aad-authentication",
"Remediation": {
"Code": {
"CLI": "az sql server ad-admin create --resource-group resource_group_name --server server_name --display-name display_name --object-id user_object_id",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Sql/active-directory-admin.html",
"Terraform": "https://docs.bridgecrew.io/docs/ensure-that-azure-active-directory-admin-is-configured#terraform"
},
"Recommendation": {
"Text": "Enable an Azure Active Directory administrator",
"Url": ""
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,26 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.sqlserver.sqlserver_client import sqlserver_client
class sqlserver_azuread_administrator_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.status = "PASS"
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} has an Active Directory administrator"
report.resource_name = sql_server.name
report.resource_id = sql_server.id
if (
sql_server.administrators is None
or sql_server.administrators.administrator_type != "ActiveDirectory"
):
report.status = "FAIL"
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} does not have an Active Directory administrator"
findings.append(report)
return findings

View File

@@ -0,0 +1,4 @@
from prowler.providers.azure.lib.audit_info.audit_info import azure_audit_info
from prowler.providers.azure.services.sqlserver.sqlserver_service import SQLServer
sqlserver_client = SQLServer(azure_audit_info)

View File

@@ -0,0 +1,108 @@
from dataclasses import dataclass
from azure.mgmt.sql import SqlManagementClient
from azure.mgmt.sql.models import (
FirewallRule,
ServerBlobAuditingPolicy,
ServerExternalAdministrator,
)
from prowler.lib.logger import logger
from prowler.providers.azure.lib.service.service import AzureService
########################## SQLServer
class SQLServer(AzureService):
def __init__(self, audit_info):
super().__init__(__class__.__name__, audit_info)
self.clients = self.__set_clients__(
audit_info.identity.subscriptions, audit_info.credentials
)
self.sql_servers = self.__get_sql_servers__()
def __set_clients__(self, subscriptions, credentials):
clients = {}
try:
for display_name, id in subscriptions.items():
clients.update(
{
display_name: SqlManagementClient(
credential=credentials, subscription_id=id
)
}
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
return clients
def __get_sql_servers__(self):
logger.info("SQL Server - Getting SQL servers...")
sql_servers = {}
for subscription, client in self.clients.items():
try:
sql_servers.update({subscription: []})
sql_servers_list = client.servers.list()
for sql_server in sql_servers_list:
resource_group = self.__get_resource_group__(sql_server.id)
auditing_policies = (
client.server_blob_auditing_policies.list_by_server(
resource_group_name=resource_group,
server_name=sql_server.name,
)
)
firewall_rules = client.firewall_rules.list_by_server(
resource_group_name=resource_group, server_name=sql_server.name
)
sql_servers[subscription].append(
SQL_Server(
id=sql_server.id,
name=sql_server.name,
public_network_access=sql_server.public_network_access,
minimal_tls_version=sql_server.minimal_tls_version,
administrators=sql_server.administrators,
auditing_policies=auditing_policies,
firewall_rules=firewall_rules,
)
)
except Exception as error:
logger.error(f"Subscription name: {subscription}")
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return sql_servers
def __get_resource_group__(self, id):
resource_group = id.split("/")[4]
return resource_group
@dataclass
class SQL_Server:
id: str
name: str
public_network_access: str
minimal_tls_version: str
administrators: ServerExternalAdministrator
auditing_policies: ServerBlobAuditingPolicy
firewall_rules: FirewallRule
def __init__(
self,
id,
name,
public_network_access,
minimal_tls_version,
administrators,
auditing_policies,
firewall_rules,
):
self.id = id
self.name = name
self.public_network_access = public_network_access
self.minimal_tls_version = minimal_tls_version
self.administrators = administrators
self.auditing_policies = auditing_policies
self.firewall_rules = firewall_rules

View File

@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "sqlserver_unrestricted_inbound_access",
"CheckTitle": "Ensure that there are no firewall rules allowing traffic from 0.0.0.0-255.255.255.255",
"CheckType": [],
"ServiceName": "sqlserver",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "SQLServer",
"Description": "Ensure that there are no firewall rules allowing traffic from 0.0.0.0-255.255.255.255",
"Risk": "Azure SQL servers provide a firewall that, by default, blocks all Internet connections. When the rule (0.0.0.0-255.255.255.255) is used, the server can be accessed by any source from the Internet, incrementing significantly the attack surface of the SQL Server. It is recommended to use more granular firewall rules.",
"RelatedUrl": "https://docs.microsoft.com/en-us/azure/sql-database/sql-database-vnet-service-endpoint-rule-overview",
"Remediation": {
"Code": {
"CLI": "az sql server firewall-rule delete --resource-group resource_group_name --server sql_server_name --name rule_name",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Sql/unrestricted-sql-database-access.html",
"Terraform": "https://docs.bridgecrew.io/docs/bc_azr_networking_4#terraform"
},
"Recommendation": {
"Text": "Remove firewall rules allowing all sources and, instead, use more granular rules",
"Url": ""
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,28 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.sqlserver.sqlserver_client import sqlserver_client
class sqlserver_unrestricted_inbound_access(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.status = "PASS"
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} does not have firewall rules allowing 0.0.0.0-255.255.255.255"
report.resource_name = sql_server.name
report.resource_id = sql_server.id
for firewall_rule in sql_server.firewall_rules:
if (
firewall_rule.start_ip_address == "0.0.0.0"
and firewall_rule.end_ip_address == "255.255.255.255"
):
report.status = "FAIL"
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} has firewall rules allowing 0.0.0.0-255.255.255.255"
break
findings.append(report)
return findings

View File

@@ -15,7 +15,6 @@ class Storage(AzureService):
audit_info.identity.subscriptions, audit_info.credentials
)
self.storage_accounts = self.__get_storage_accounts__()
self.region = "azure"
def __set_clients__(self, subscriptions, credentials):
clients = {}

View File

@@ -30,6 +30,7 @@ awsipranges = "0.3.3"
azure-identity = "1.13.0"
azure-mgmt-authorization = "4.0.0"
azure-mgmt-security = "5.0.0"
azure-mgmt-sql = "3.0.1"
azure-mgmt-storage = "21.0.0"
azure-mgmt-subscription = "3.1.1"
azure-storage-blob = "12.17.0"