Skip to content
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
19 changes: 19 additions & 0 deletions ddtrace/appsec/_asm_request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ddtrace.appsec._constants import APPSEC
from ddtrace.appsec._constants import SPAN_DATA_NAMES
from ddtrace.appsec._constants import Constant_Class
from ddtrace.appsec._metrics import UNKNOWN_VERSION
from ddtrace.appsec._metrics import report_waf_run_error
from ddtrace.appsec._metrics import report_waf_truncation
from ddtrace.appsec._metrics import set_waf_request_metrics
Expand All @@ -24,9 +25,11 @@
from ddtrace.appsec._utils import is_inferred_span
from ddtrace.contrib.internal.trace_utils_base import _normalize_tag_name
from ddtrace.internal import core
from ddtrace.internal import telemetry
from ddtrace.internal._exceptions import BlockingException
import ddtrace.internal.logger as ddlogger
from ddtrace.internal.settings.asm import config as asm_config
from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE


if TYPE_CHECKING:
Expand Down Expand Up @@ -276,10 +279,20 @@ def flush_waf_triggers(env: ASM_Environment) -> None:
entry_span._set_attribute(APPSEC.WAF_VERSION, asm_config._ddwaf_version)
if env.downstream_requests:
update_span_metrics(entry_span, APPSEC.DOWNSTREAM_REQUESTS, env.downstream_requests)
tags = (
("event_rules_version", telemetry_results.version or UNKNOWN_VERSION),
("waf_version", asm_config._ddwaf_version),
)
if telemetry_results.total_duration:
update_span_metrics(entry_span, APPSEC.WAF_DURATION, telemetry_results.duration)
telemetry.telemetry_writer.add_distribution_metric(
TELEMETRY_NAMESPACE.APPSEC, "waf.duration", telemetry_results.duration, tags=tags
)
telemetry_results.duration = 0.0
update_span_metrics(entry_span, APPSEC.WAF_DURATION_EXT, telemetry_results.total_duration)
telemetry.telemetry_writer.add_distribution_metric(
TELEMETRY_NAMESPACE.APPSEC, "waf.duration_ext", telemetry_results.total_duration, tags=tags
)
telemetry_results.total_duration = 0.0
if telemetry_results.timeout:
update_span_metrics(entry_span, APPSEC.WAF_TIMEOUTS, telemetry_results.timeout)
Expand All @@ -290,6 +303,12 @@ def flush_waf_triggers(env: ASM_Environment) -> None:
update_span_metrics(entry_span, APPSEC.RASP_DURATION, telemetry_results.rasp.duration)
update_span_metrics(entry_span, APPSEC.RASP_DURATION_EXT, telemetry_results.rasp.total_duration)
update_span_metrics(entry_span, APPSEC.RASP_RULE_EVAL, telemetry_results.rasp.sum_eval)
telemetry.telemetry_writer.add_distribution_metric(
TELEMETRY_NAMESPACE.APPSEC, "rasp.duration", telemetry_results.rasp.duration, tags=tags
Comment thread
christophe-papazian marked this conversation as resolved.
)
telemetry.telemetry_writer.add_distribution_metric(
TELEMETRY_NAMESPACE.APPSEC, "rasp.duration_ext", telemetry_results.rasp.total_duration, tags=tags
)
if telemetry_results.truncation.string_length:
entry_span._set_attribute(APPSEC.TRUNCATION_STRING_LENGTH, max(telemetry_results.truncation.string_length))
if telemetry_results.truncation.container_size:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
aap: Adds instrumentation telemetry distributions for WAF and RASP execution
durations, including ``waf.duration``, ``waf.duration_ext``,
``rasp.duration``, and ``rasp.duration_ext``.
73 changes: 57 additions & 16 deletions tests/appsec/appsec/test_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@

import ddtrace.appsec._asm_request_context as asm_request_context
from ddtrace.appsec._constants import APPSEC
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
import ddtrace.appsec._ddwaf.ddwaf_types
import ddtrace.appsec._ddwaf.waf
from ddtrace.appsec._deduplications import deduplication
from ddtrace.appsec._processor import AppSecSpanProcessor
from ddtrace.appsec._remoteconfiguration import enable_asm
from ddtrace.appsec._utils import DDWaf_result
from ddtrace.appsec._utils import _observator
from ddtrace.constants import APPSEC_ENV
from ddtrace.contrib.internal.trace_utils import set_http_meta
from ddtrace.ext import SpanTypes
Expand Down Expand Up @@ -76,22 +79,6 @@ def _assert_generate_metrics(metrics_result, is_rule_triggered=False, is_blocked
pytest.fail("Unexpected generate_metrics {}".format(metric_name))


def _assert_distributions_metrics(metrics_result, is_rule_triggered=False, is_blocked_request=False):
distributions_metrics = metrics_result[TELEMETRY_EVENT_TYPE.DISTRIBUTIONS][TELEMETRY_NAMESPACE.APPSEC.value]

assert len(distributions_metrics) == 2, "Expected 2 distributions_metrics"
for metric in distributions_metrics:
if metric["metric"] in ["waf.duration", "waf.duration_ext"]:
assert len(metric["points"]) >= 1
assert isinstance(metric["points"][0], float)
assert f"rule_triggered:{str(is_rule_triggered).lower()}" in metric["tags"]
assert f"request_blocked:{str(is_blocked_request).lower()}" in metric["tags"]
assert f"waf_version:{asm_config._ddwaf_version}" in metric["tags"]
assert any("event_rules_version" in t for t in metric["tags"])
else:
pytest.fail("Unexpected distributions_metrics {}".format(metric["metric"]))


def test_metrics_when_appsec_doesnt_runs(telemetry_writer, tracer):
with override_global_config(dict(_asm_enabled=False)):
tracer.configure(appsec_enabled=False)
Expand Down Expand Up @@ -193,6 +180,60 @@ def test_report_user_auth_missing(telemetry_writer, user_id, user_login, report_
for metric in user_auth_metrics:
assert "framework:django" in metric["tags"]
assert "event_type:login_failure" in metric["tags"]


def test_waf_duration_distribution_metrics(telemetry_writer, tracer):
telemetry_writer._namespace.flush()
with asm_context(tracer=tracer, span_name="test", config=config_asm) as span:
set_http_meta(span, rules.Config())

distributions_metrics = telemetry_writer._namespace.flush()[TELEMETRY_EVENT_TYPE.DISTRIBUTIONS][
TELEMETRY_NAMESPACE.APPSEC.value
]
waf_metrics = {metric["metric"]: metric for metric in distributions_metrics if metric["metric"].startswith("waf.")}

assert set(waf_metrics) == {"waf.duration", "waf.duration_ext"}
for metric in waf_metrics.values():
assert len(metric["points"]) >= 1
assert isinstance(metric["points"][0], float)
assert f"waf_version:{asm_config._ddwaf_version}" in metric["tags"]
assert any(tag.startswith("event_rules_version:") for tag in metric["tags"])
assert len(metric["tags"]) == 2


def test_rasp_duration_distribution_metrics(telemetry_writer, tracer):
telemetry_writer._namespace.flush()
with asm_context(tracer=tracer, span_name="test", config=config_asm):
waf_result = DDWaf_result(0, [], {}, 12.5, 20.25, False, _observator(), {})
asm_request_context.set_waf_telemetry_results(
"rules_rasp",
False,
waf_result,
EXPLOIT_PREVENTION.TYPE.SQLI,
False,
)
waf_result = DDWaf_result(0, [], {}, 3.0, 4.0, False, _observator(), {})
asm_request_context.set_waf_telemetry_results(
"rules_rasp",
False,
waf_result,
EXPLOIT_PREVENTION.TYPE.LFI,
False,
)

distributions_metrics = telemetry_writer._namespace.flush()[TELEMETRY_EVENT_TYPE.DISTRIBUTIONS][
TELEMETRY_NAMESPACE.APPSEC.value
]
rasp_metrics = {
metric["metric"]: metric for metric in distributions_metrics if metric["metric"].startswith("rasp.")
}

assert set(rasp_metrics) == {"rasp.duration", "rasp.duration_ext"}
assert rasp_metrics["rasp.duration"]["points"] == [15.5]
assert rasp_metrics["rasp.duration_ext"]["points"] == [24.25]
for metric in rasp_metrics.values():
assert f"waf_version:{asm_config._ddwaf_version}" in metric["tags"]
assert any(tag.startswith("event_rules_version:") for tag in metric["tags"])
assert len(metric["tags"]) == 2


Expand Down
Loading