mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 21:11:53 +00:00
feat(azure):App check related with http logs (#3568)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
This commit is contained in:
+30
@@ -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
|
||||
|
||||
|
||||
+192
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user