Skip to content
Open
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
6 changes: 6 additions & 0 deletions ddtrace/appsec/_listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ def _abort_appsec(failure_msg: str) -> None:

log.warning("Disabling AppSec: libddwaf failed to load (%s)", failure_msg or "unknown error")

if asm_config._asm_enabled:
from ddtrace.internal.telemetry import telemetry_writer
from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT

telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, False)

asm_config._asm_enabled = False
asm_config._asm_can_be_enabled = False
asm_config._asm_libddwaf_available = False
Expand Down
10 changes: 6 additions & 4 deletions ddtrace/appsec/_remoteconfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def enable_appsec_rc() -> None:

load_common_appsec_modules()

telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, True)
if asm_config._asm_enabled:
telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, True)
asm_config._rc_client_id = remoteconfig_poller._client.id


Expand All @@ -69,8 +70,6 @@ def disable_appsec_rc() -> None:
remoteconfig_poller.unregister_callback(product_name)
remoteconfig_poller.disable_product(product_name)

telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, False)


class AppSecCallback(RCCallback):
"""Remote config callback for AppSec products."""
Expand Down Expand Up @@ -177,10 +176,13 @@ def disable_asm() -> None:
from ddtrace.appsec._listeners import disable_appsec

disable_appsec(reconfigure_tracer=True)
if not asm_config._asm_enabled:
telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, False)


def enable_asm() -> None:
if asm_config._asm_can_be_enabled and not asm_config._asm_enabled:
from ddtrace.appsec._listeners import load_appsec

load_appsec(reconfigure_tracer=True, origin=APPSEC.ENABLED_ORIGIN_RC)
if load_appsec(reconfigure_tracer=True, origin=APPSEC.ENABLED_ORIGIN_RC):
telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
AAP: This fix resolves an issue where Application and API Protection (AAP) was incorrectly reported as an enabled
product in internal telemetry for all services by default. Previously, registering remote configuration listeners
caused AAP to be reported as activated even when it was not actually enabled. This had no impact on customers as it
only affected internal telemetry data. AAP is now only reported as activated when it is explicitly enabled or enabled
through remote configuration.
51 changes: 51 additions & 0 deletions tests/appsec/appsec/test_remoteconfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ddtrace.internal.remoteconfig.client import TargetFile
from ddtrace.internal.service import ServiceStatus
from ddtrace.internal.settings.asm import config as asm_config
from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT
from ddtrace.internal.utils.formats import asbool
import tests.appsec.rules as rules
from tests.appsec.utils import asm_context
Expand Down Expand Up @@ -564,3 +565,53 @@ def test_rc_activation_ip_blocking_data_not_expired(tracer, rc_poller):
)
assert get_triggers(span)
assert get_waf_addresses("http.request.remote_ip") == "8.8.4.4"


def test_rc_activation_does_not_report_appsec_product_when_only_rc_enabled(tracer, rc_poller):
"""Regression test: registering RC listeners should not report AppSec as an enabled product in telemetry."""
with override_global_config(dict(_asm_enabled=False, _asm_can_be_enabled=True, _remote_config_enabled=True)):
with mock.patch("ddtrace.appsec._remoteconfiguration.telemetry_writer") as mock_tw:
enable_appsec_rc()

# RC listeners are registered but AppSec is not enabled
assert rc_poller._client._product_callbacks["ASM_FEATURES"]
# Telemetry should NOT report AppSec as activated
mock_tw.product_activated.assert_not_called()

disable_appsec_rc()


def test_rc_activation_reports_appsec_product_when_enabled(tracer, rc_poller):
"""When AppSec is explicitly enabled, enable_appsec_rc should report the product as activated."""
with override_global_config(dict(_asm_enabled=True, _remote_config_enabled=True)):
tracer.configure(appsec_enabled=True)
with mock.patch("ddtrace.appsec._remoteconfiguration.telemetry_writer") as mock_tw:
enable_appsec_rc()

mock_tw.product_activated.assert_called_once_with(TELEMETRY_APM_PRODUCT.APPSEC, True)

disable_appsec_rc()


def test_rc_enable_then_disable_asm_reports_telemetry(tracer, rc_poller):
"""When AppSec is enabled/disabled via RC, telemetry should reflect the changes."""
with override_global_config(dict(_asm_enabled=False, _asm_can_be_enabled=True, _remote_config_enabled=True)):
with mock.patch("ddtrace.appsec._remoteconfiguration.telemetry_writer") as mock_tw:
enable_appsec_rc()

# Initially not activated
mock_tw.product_activated.assert_not_called()

# Simulate RC enabling AppSec
enable_config = [build_payload("ASM_FEATURES", {"asm": {"enabled": True}}, "config")]
_appsec_callback(enable_config)
mock_tw.product_activated.assert_called_once_with(TELEMETRY_APM_PRODUCT.APPSEC, True)

mock_tw.product_activated.reset_mock()

# Simulate RC disabling AppSec
disable_config = [build_payload("ASM_FEATURES", {"asm": {}}, "config")]
_appsec_callback(disable_config)
mock_tw.product_activated.assert_called_once_with(TELEMETRY_APM_PRODUCT.APPSEC, False)

disable_appsec_rc()
Loading