feat(azure):App check related with http logs (#3568)

Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
This commit is contained in:
Hugo966
2024-03-20 11:11:52 +01:00
committed by GitHub
parent 3bab7552b2
commit e224215fa3
7 changed files with 350 additions and 2 deletions
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_http_logs_enabled",
"CheckTitle": "Ensure that logging for Azure AppService 'HTTP logs' is enabled",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Microsoft.Web/sites/config",
"Description": "Enable AppServiceHTTPLogs diagnostic log category for Azure App Service instances to ensure all http requests are captured and centrally logged.",
"Risk": "Capturing web requests can be important supporting information for security analysts performing monitoring and incident response activities. Once logging, these logs can be ingested into SIEM or other central aggregation point for the organization.",
"RelatedUrl": "https://learn.microsoft.com/en-us/security/benchmark/azure/mcsb-logging-threat-detection#lt-3-enable-logging-for-security-investigation",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "https://docs.bridgecrew.io/docs/ensure-that-app-service-enables-http-logging#terraform"
},
"Recommendation": {
"Text": "1. Go to App Services For each App Service: 2. Go to Diagnostic Settings 3. Click Add Diagnostic Setting 4. Check the checkbox next to 'HTTP logs' 5. Configure a destination based on your specific logging consumption capability (for example Stream to an event hub and then consuming with SIEM integration for Event Hub logging).",
"Url": "https://docs.microsoft.com/en-us/azure/app-service/troubleshoot-diagnostic-logs"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Log consumption and processing will incur additional cost."
}
@@ -0,0 +1,29 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
class app_http_logs_enabled(Check):
def execute(self) -> Check_Report_Azure:
findings = []
for subscription_name, apps in app_client.apps.items():
for app_name, app in apps.items():
if "functionapp" not in app.kind:
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.subscription = subscription_name
report.resource_name = app_name
report.resource_id = app.resource_id
if not app.monitor_diagnostic_settings:
report.status_extended = f"App {app_name} does not have a diagnostic setting in subscription {subscription_name}."
else:
for diagnostic_setting in app.monitor_diagnostic_settings:
report.status_extended = f"App {app_name} does not have HTTP Logs enabled in diagnostic setting {diagnostic_setting.name} in subscription {subscription_name}"
for log in diagnostic_setting.logs:
if log.category == "AppServiceHTTPLogs" and log.enabled:
report.status = "PASS"
report.status_extended = f"App {app_name} has HTTP Logs enabled in diagnostic setting {diagnostic_setting.name} in subscription {subscription_name}"
break
findings.append(report)
return findings
@@ -6,6 +6,8 @@ from azure.mgmt.web.models import ManagedServiceIdentity, SiteConfigResource
from prowler.lib.logger import logger
from prowler.providers.azure.lib.audit_info.models import Azure_Audit_Info
from prowler.providers.azure.lib.service.service import AzureService
from prowler.providers.azure.services.monitor.monitor_client import monitor_client
from prowler.providers.azure.services.monitor.monitor_service import DiagnosticSetting
########################## App
@@ -49,8 +51,12 @@ class App(AzureService):
getattr(app, "client_cert_enabled", False),
getattr(app, "client_cert_mode", "Ignore"),
),
monitor_diagnostic_settings=self.__get_app_monitor_settings__(
app.name, app.resource_group, subscription_name
),
https_only=getattr(app, "https_only", False),
identity=getattr(app, "identity", None),
kind=getattr(app, "kind", "app"),
)
}
)
@@ -78,6 +84,21 @@ class App(AzureService):
return cert_mode
def __get_app_monitor_settings__(self, app_name, resource_group, subscription):
logger.info(f"App - Getting monitor diagnostics settings for {app_name}...")
monitor_diagnostics_settings = []
try:
monitor_diagnostics_settings = monitor_client.diagnostic_settings_with_uri(
self.subscriptions[subscription],
f"subscriptions/{self.subscriptions[subscription]}/resourceGroups/{resource_group}/providers/Microsoft.Web/sites/{app_name}",
monitor_client.clients[subscription],
)
except Exception as error:
logger.error(
f"Subscription name: {self.subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return monitor_diagnostics_settings
@dataclass
class WebApp:
@@ -87,3 +108,5 @@ class WebApp:
client_cert_mode: str = "Ignore"
auth_enabled: bool = False
https_only: bool = False
monitor_diagnostic_settings: list[DiagnosticSetting] = None
kind: str = "app"
@@ -42,14 +42,18 @@ class Monitor(AzureService):
DiagnosticSetting(
id=setting.id,
name=setting.id.split("/")[-1],
storage_account_name=setting.storage_account_id.split("/")[-1],
storage_account_name=(
setting.storage_account_id.split("/")[-1]
if getattr(setting, "storage_account_id", None)
else None
),
logs=setting.logs,
storage_account_id=setting.storage_account_id,
)
)
except Exception as error:
logger.error(
f"Subscription name: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
f"Subscription id: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return diagnostics_settings
@@ -0,0 +1,192 @@
from unittest import mock
from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION
class Test_app_http_logs_enabled:
def test_app_http_logs_enabled_no_subscriptions(self):
app_client = mock.MagicMock
app_client.apps = {}
with mock.patch(
"prowler.providers.azure.services.app.app_http_logs_enabled.app_http_logs_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_http_logs_enabled.app_http_logs_enabled import (
app_http_logs_enabled,
)
check = app_http_logs_enabled()
result = check.execute()
assert len(result) == 0
def test_app_subscriptions_empty(self):
app_client = mock.MagicMock
app_client.apps = {AZURE_SUBSCRIPTION: {}}
with mock.patch(
"prowler.providers.azure.services.app.app_register_with_identity.app_register_with_identity.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_register_with_identity.app_register_with_identity import (
app_register_with_identity,
)
check = app_register_with_identity()
result = check.execute()
assert len(result) == 0
def test_no_diagnostics_settings(self):
app_client = mock.MagicMock()
with mock.patch(
"prowler.providers.azure.services.app.app_http_logs_enabled.app_http_logs_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_http_logs_enabled.app_http_logs_enabled import (
app_http_logs_enabled,
)
from prowler.providers.azure.services.app.app_service import WebApp
app_client.apps = {
AZURE_SUBSCRIPTION: {
"app1": WebApp(
resource_id="resource_id",
auth_enabled=True,
configurations=None,
client_cert_mode="Ignore",
https_only=False,
identity=None,
kind="webapps",
)
}
}
check = app_http_logs_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].resource_name == "app1"
assert result[0].resource_id == "resource_id"
assert (
result[0].status_extended
== f"App app1 does not have a diagnostic setting in subscription {AZURE_SUBSCRIPTION}."
)
assert result[0].subscription == AZURE_SUBSCRIPTION
def test_diagnostic_setting_configured(self):
app_client = mock.MagicMock
with mock.patch(
"prowler.providers.azure.services.app.app_http_logs_enabled.app_http_logs_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_http_logs_enabled.app_http_logs_enabled import (
app_http_logs_enabled,
)
from prowler.providers.azure.services.app.app_service import WebApp
from prowler.providers.azure.services.monitor.monitor_service import (
DiagnosticSetting,
)
app_client.apps = {
AZURE_SUBSCRIPTION: {
"app_id-1": WebApp(
resource_id="resource_id1",
auth_enabled=True,
configurations=None,
client_cert_mode="Ignore",
https_only=False,
kind="functionapp",
identity=mock.MagicMock,
monitor_diagnostic_settings=[
DiagnosticSetting(
id="id1/id1",
logs=[
mock.MagicMock(
category="AppServiceHTTPLogs",
enabled=True,
),
mock.MagicMock(
category="AppServiceConsoleLogs",
enabled=False,
),
mock.MagicMock(
category="AppServiceAppLogs",
enabled=True,
),
mock.MagicMock(
category="AppServiceAuditLogs",
enabled=False,
),
mock.MagicMock(
category="AppServiceIPSecAuditLogs",
enabled=False,
),
mock.MagicMock(
category="AppServicePlatformLogs",
enabled=False,
),
],
storage_account_name="storage_account_name1",
storage_account_id="storage_account_id1",
name="name_diagnostic_setting1",
),
],
),
"app_id-2": WebApp(
resource_id="resource_id2",
auth_enabled=True,
configurations=None,
client_cert_mode="Ignore",
https_only=False,
kind="WebApp",
identity=mock.MagicMock,
monitor_diagnostic_settings=[
DiagnosticSetting(
id="id2/id2",
logs=[
mock.MagicMock(
category="AppServiceHTTPLogs",
enabled=True,
),
mock.MagicMock(
category="AppServiceConsoleLogs",
enabled=True,
),
mock.MagicMock(
category="AppServiceAppLogs",
enabled=True,
),
mock.MagicMock(
category="AppServiceAuditLogs",
enabled=False,
),
mock.MagicMock(
category="AppServiceIPSecAuditLogs",
enabled=True,
),
mock.MagicMock(
category="AppServicePlatformLogs",
enabled=False,
),
],
storage_account_name="storage_account_name2",
storage_account_id="storage_account_id2",
name="name_diagnostic_setting2",
),
],
),
}
}
check = app_http_logs_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == AZURE_SUBSCRIPTION
assert result[0].resource_name == "app_id-2"
assert result[0].resource_id == "resource_id2"
assert (
result[0].status_extended
== f"App app_id-2 has HTTP Logs enabled in diagnostic setting name_diagnostic_setting2 in subscription {AZURE_SUBSCRIPTION}"
)
@@ -1,8 +1,10 @@
from unittest import mock
from unittest.mock import patch
from azure.mgmt.web.models import ManagedServiceIdentity, SiteConfigResource
from prowler.providers.azure.services.app.app_service import App, WebApp
from prowler.providers.azure.services.monitor.monitor_service import DiagnosticSetting
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION,
set_mocked_azure_audit_info,
@@ -19,6 +21,40 @@ def mock_app_get_apps(self):
auth_enabled=True,
client_cert_mode="Required",
https_only=True,
monitor_diagnostic_settings=[
DiagnosticSetting(
id="id2/id2",
logs=[
mock.MagicMock(
category="AppServiceHTTPLogs",
enabled=False,
),
mock.MagicMock(
category="AppServiceConsoleLogs",
enabled=True,
),
mock.MagicMock(
category="AppServiceAppLogs",
enabled=True,
),
mock.MagicMock(
category="AppServiceAuditLogs",
enabled=False,
),
mock.MagicMock(
category="AppServiceIPSecAuditLogs",
enabled=True,
),
mock.MagicMock(
category="AppServicePlatformLogs",
enabled=False,
),
],
storage_account_name="storage_account_name2",
storage_account_id="storage_account_id2",
name="name_diagnostic_setting2",
),
],
)
}
}
@@ -77,3 +113,37 @@ class Test_App_Service:
assert app_service.__get_client_cert_mode__(True, "Optional") == "Allow"
assert app_service.__get_client_cert_mode__(True, "Required") == "Required"
assert app_service.__get_client_cert_mode__(True, "Foo") == "Ignore"
def test__get_app_monitor_settings(self):
app_service = App(set_mocked_azure_audit_info())
assert (
app_service.apps[AZURE_SUBSCRIPTION]["app_id-1"]
.monitor_diagnostic_settings[0]
.id
== "id2/id2"
)
assert (
app_service.apps[AZURE_SUBSCRIPTION]["app_id-1"]
.monitor_diagnostic_settings[0]
.logs[0]
.category
== "AppServiceHTTPLogs"
)
assert (
app_service.apps[AZURE_SUBSCRIPTION]["app_id-1"]
.monitor_diagnostic_settings[0]
.storage_account_name
== "storage_account_name2"
)
assert (
app_service.apps[AZURE_SUBSCRIPTION]["app_id-1"]
.monitor_diagnostic_settings[0]
.storage_account_id
== "storage_account_id2"
)
assert (
app_service.apps[AZURE_SUBSCRIPTION]["app_id-1"]
.monitor_diagnostic_settings[0]
.name
== "name_diagnostic_setting2"
)