Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Original file line number Diff line number Diff line change
@@ -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."
}
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions prowler/providers/azure/services/app/app_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
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
Expand Down Expand Up @@ -49,8 +51,12 @@
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"),
)
}
)
Expand Down Expand Up @@ -78,6 +84,21 @@

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(

Check warning on line 91 in prowler/providers/azure/services/app/app_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/azure/services/app/app_service.py#L88-L91

Added lines #L88 - L91 were not covered by tests
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(

Check warning on line 97 in prowler/providers/azure/services/app/app_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/azure/services/app/app_service.py#L96-L97

Added lines #L96 - L97 were not covered by tests
f"Subscription name: {self.subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return monitor_diagnostics_settings

Check warning on line 100 in prowler/providers/azure/services/app/app_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/azure/services/app/app_service.py#L100

Added line #L100 was not covered by tests


@dataclass
class WebApp:
Expand All @@ -87,3 +108,5 @@
client_cert_mode: str = "Ignore"
auth_enabled: bool = False
https_only: bool = False
monitor_diagnostic_settings: list[DiagnosticSetting] = None
kind: str = "app"
8 changes: 6 additions & 2 deletions prowler/providers/azure/services/monitor/monitor_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,18 @@ def diagnostic_settings_with_uri(self, subscription, uri, client):
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

Expand Down
Original file line number Diff line number Diff line change
@@ -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}"
)
Loading