diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 8c82efbdf37..6027976d6dc 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -236,7 +236,9 @@ def __init__( self._iast_enabled = asm_config._iast_enabled self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled self._dogstatsd_url = agent.get_stats_url() if dogstatsd_url is None else dogstatsd_url - self._apm_opt_out = (self._asm_enabled or self._iast_enabled) and self._appsec_standalone_enabled + self._apm_opt_out = self._appsec_standalone_enabled and ( + self._asm_enabled or self._iast_enabled or config._sca_enabled + ) if self._apm_opt_out: self.enabled = False # Disable compute stats (neither agent or tracer should compute them) @@ -498,7 +500,7 @@ def configure( if appsec_standalone_enabled is not None: self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled = appsec_standalone_enabled - if self._appsec_standalone_enabled and (self._asm_enabled or self._iast_enabled): + if self._appsec_standalone_enabled and (self._asm_enabled or self._iast_enabled or config._sca_enabled): self._apm_opt_out = True self.enabled = False # Disable compute stats (neither agent or tracer should compute them) diff --git a/releasenotes/notes/feat-standalone-sca-billing-925c84d69fe061ce.yaml b/releasenotes/notes/feat-standalone-sca-billing-925c84d69fe061ce.yaml new file mode 100644 index 00000000000..733aaea6262 --- /dev/null +++ b/releasenotes/notes/feat-standalone-sca-billing-925c84d69fe061ce.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + ASM: This introduces "Standalone SCA billing", opting out for APM billing and applying to only SCA. Enable this by setting these two environment variables: ``DD_APPSEC_SCA_ENABLED`` and ``DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED`` diff --git a/tests/appsec/appsec/test_asm_standalone.py b/tests/appsec/appsec/test_asm_standalone.py index 31624724069..6841314cea8 100644 --- a/tests/appsec/appsec/test_asm_standalone.py +++ b/tests/appsec/appsec/test_asm_standalone.py @@ -1,41 +1,145 @@ #!/usr/bin/env python3 +import copy + import pytest +import ddtrace from ddtrace.contrib.trace_utils import set_http_meta from ddtrace.ext import SpanTypes +from tests.utils import override_env @pytest.fixture( params=[ - {"iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": True}, - {"iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": False}, - {"iast_enabled": True, "appsec_enabled": False, "appsec_standalone_enabled": False}, - {"iast_enabled": True, "appsec_enabled": False, "appsec_standalone_enabled": True}, - {"iast_enabled": False, "appsec_enabled": True, "appsec_standalone_enabled": True}, - {"iast_enabled": False, "appsec_enabled": True, "appsec_standalone_enabled": False}, - {"iast_enabled": False, "appsec_enabled": False, "appsec_standalone_enabled": False}, - {"iast_enabled": False, "appsec_enabled": False, "appsec_standalone_enabled": True}, - {"appsec_enabled": True}, - {"appsec_enabled": False}, - {"iast_enabled": True}, - {"iast_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "1", "iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": True}, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": True, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + {"DD_APPSEC_SCA_ENABLED": "1", "appsec_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "1", "appsec_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "1", "iast_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "1", "iast_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "0", "iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": True}, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": True, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + {"DD_APPSEC_SCA_ENABLED": "0", "appsec_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "0", "appsec_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "0", "iast_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "0", "iast_enabled": False}, ] ) def tracer_appsec_standalone(request, tracer): - tracer.configure(api_version="v0.4", **request.param) - yield tracer, request.param + new_env = {k: v for k, v in request.param.items() if k.startswith("DD_")} + with override_env(new_env): + # Reset the config so it picks up the env var value + ddtrace.config._reset() + + # Copy the params to a new dict, including the env var + request_param_copy = copy.deepcopy(request.param) + + # Remove the environment variables as they are unexpected args for the tracer configure + request.param.pop("DD_APPSEC_SCA_ENABLED", None) + tracer.configure(api_version="v0.4", **request.param) + + yield tracer, request_param_copy + # Reset tracer configuration + ddtrace.config._reset() tracer.configure(api_version="v0.4", appsec_enabled=False, appsec_standalone_enabled=False, iast_enabled=False) def test_appsec_standalone_apm_enabled_metric(tracer_appsec_standalone): tracer, args = tracer_appsec_standalone + with tracer.trace("test", span_type=SpanTypes.WEB) as span: set_http_meta(span, {}, raw_uri="http://example.com/.git", status_code="404") if args.get("appsec_standalone_enabled", None) and ( - args.get("appsec_enabled", None) or args.get("iast_enabled", None) + args.get("appsec_enabled", None) + or args.get("iast_enabled", None) + or args.get("DD_APPSEC_SCA_ENABLED", "0") == "1" ): + assert tracer._apm_opt_out is True assert span.get_metric("_dd.apm.enabled") == 0.0 else: + assert tracer._apm_opt_out is False assert span.get_metric("_dd.apm.enabled") is None diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index 2e1a299c4d4..61fec650a70 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -7,6 +7,7 @@ import mock import pytest +import ddtrace from ddtrace import tracer as ddtracer from ddtrace._trace._span_link import SpanLink from ddtrace._trace.context import Context @@ -45,6 +46,7 @@ from tests.contrib.fastapi.conftest import test_spans as fastapi_test_spans # noqa:F401 from tests.contrib.fastapi.conftest import tracer # noqa:F401 +from ..utils import override_env from ..utils import override_global_config @@ -318,95 +320,107 @@ def test_extract(tracer): # noqa: F811 assert len(context.get_all_baggage_items()) == 3 +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation( - tracer, appsec_enabled, iast_enabled # noqa: F811 + tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") - - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": str(USER_KEEP), - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.test=value,any=tag", - "ot-baggage-key1": "value1", - } + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": str(USER_KEEP), + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.test=value,any=tag", + "ot-baggage-key1": "value1", + } - context = HTTPPropagator.extract(headers) + context = HTTPPropagator.extract(headers) - tracer.context_provider.activate(context) + tracer.context_provider.activate(context) - with tracer.trace("local_root_span0") as span: - # First span should be kept, as we keep 1 per min - assert span.trace_id == 1234 - assert span.parent_id == 5678 - # Priority is unset - assert span.context.sampling_priority is None - assert "_sampling_priority_v1" not in span._metrics - assert span.context.dd_origin == "synthetics" - assert "_dd.p.test" in span.context._meta - assert "_dd.p.appsec" not in span.context._meta + with tracer.trace("local_root_span0") as span: + # First span should be kept, as we keep 1 per min + assert span.trace_id == 1234 + assert span.parent_id == 5678 + # Priority is unset + assert span.context.sampling_priority is None + assert "_sampling_priority_v1" not in span._metrics + assert span.context.dd_origin == "synthetics" + assert "_dd.p.test" in span.context._meta + assert "_dd.p.appsec" not in span.context._meta - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) - # Ensure propagation of headers is interrupted - assert "x-datadog-origin" not in next_headers - assert "x-datadog-tags" not in next_headers - assert "x-datadog-trace-id" not in next_headers - assert "x-datadog-parent-id" not in next_headers - assert "x-datadog-sampling-priority" not in next_headers + # Ensure propagation of headers is interrupted + assert "x-datadog-origin" not in next_headers + assert "x-datadog-tags" not in next_headers + assert "x-datadog-trace-id" not in next_headers + assert "x-datadog-parent-id" not in next_headers + assert "x-datadog-sampling-priority" not in next_headers - # Span priority was unset, but as we keep 1 per min, it should be kept - # Since we have a rate limiter, priorities used are USER_KEEP and USER_REJECT - assert span._metrics["_sampling_priority_v1"] == USER_KEEP + # Span priority was unset, but as we keep 1 per min, it should be kept + # Since we have a rate limiter, priorities used are USER_KEEP and USER_REJECT + assert span._metrics["_sampling_priority_v1"] == USER_KEEP - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": "0"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_missing_propagation_tags_no_appsec_event_trace_dropped( - tracer, appsec_enabled, iast_enabled # noqa: F811 + tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() - headers = {} + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass - context = HTTPPropagator.extract(headers) + headers = {} - tracer.context_provider.activate(context) + context = HTTPPropagator.extract(headers) - with tracer.trace("local_root_span") as span: - assert "_dd.p.appsec" not in span.context._meta + tracer.context_provider.activate(context) - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) + with tracer.trace("local_root_span") as span: + assert "_dd.p.appsec" not in span.context._meta - # Ensure propagation of headers takes place as expected - assert "x-datadog-origin" not in next_headers - assert "x-datadog-tags" not in next_headers - assert "x-datadog-trace-id" not in next_headers - assert "x-datadog-parent-id" not in next_headers - assert "x-datadog-sampling-priority" not in next_headers + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) - # Ensure span is dropped (no appsec event upstream or in this span) - assert span._metrics["_sampling_priority_v1"] == USER_REJECT - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + # Ensure propagation of headers takes place as expected + assert "x-datadog-origin" not in next_headers + assert "x-datadog-tags" not in next_headers + assert "x-datadog-trace-id" not in next_headers + assert "x-datadog-parent-id" not in next_headers + assert "x-datadog-sampling-priority" not in next_headers + + # Ensure span is dropped (no appsec event upstream or in this span) + assert span._metrics["_sampling_priority_v1"] == USER_REJECT + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": "0"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) def test_asm_standalone_missing_propagation_tags_appsec_event_present_trace_kept(tracer): # noqa: F811 @@ -443,58 +457,63 @@ def test_asm_standalone_missing_propagation_tags_appsec_event_present_trace_kept tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_missing_appsec_tag_no_appsec_event_propagation_resets( - tracer, appsec_enabled, iast_enabled # noqa: F811 + tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass + + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": str(USER_KEEP), + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.test=value,any=tag", + "ot-baggage-key1": "value1", + } - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass + context = HTTPPropagator.extract(headers) - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": str(USER_KEEP), - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.test=value,any=tag", - "ot-baggage-key1": "value1", - } + tracer.context_provider.activate(context) - context = HTTPPropagator.extract(headers) + with tracer.trace("local_root_span") as span: + assert span.trace_id == 1234 + assert span.parent_id == 5678 + # Priority is unset + assert span.context.sampling_priority is None + assert "_sampling_priority_v1" not in span._metrics + assert span.context.dd_origin == "synthetics" + assert "_dd.p.test" in span.context._meta + assert "_dd.p.appsec" not in span.context._meta - tracer.context_provider.activate(context) + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) - with tracer.trace("local_root_span") as span: - assert span.trace_id == 1234 - assert span.parent_id == 5678 - # Priority is unset - assert span.context.sampling_priority is None - assert "_sampling_priority_v1" not in span._metrics - assert span.context.dd_origin == "synthetics" - assert "_dd.p.test" in span.context._meta - assert "_dd.p.appsec" not in span.context._meta + # Ensure propagation of headers takes place as expected + assert "x-datadog-origin" not in next_headers + assert "x-datadog-tags" not in next_headers + assert "x-datadog-trace-id" not in next_headers + assert "x-datadog-parent-id" not in next_headers + assert "x-datadog-sampling-priority" not in next_headers - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) - - # Ensure propagation of headers takes place as expected - assert "x-datadog-origin" not in next_headers - assert "x-datadog-tags" not in next_headers - assert "x-datadog-trace-id" not in next_headers - assert "x-datadog-parent-id" not in next_headers - assert "x-datadog-sampling-priority" not in next_headers - - # Priority was unset, and trace is not kept, so it should be dropped - # As we have a rate limiter, priorities used are USER_KEEP and USER_REJECT - assert span._metrics["_sampling_priority_v1"] == USER_REJECT - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + # Priority was unset, and trace is not kept, so it should be dropped + # As we have a rate limiter, priorities used are USER_KEEP and USER_REJECT + assert span._metrics["_sampling_priority_v1"] == USER_REJECT + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": "false"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) def test_asm_standalone_missing_appsec_tag_appsec_event_present_trace_kept( @@ -546,131 +565,141 @@ def test_asm_standalone_missing_appsec_tag_appsec_event_present_trace_kept( @pytest.mark.parametrize("upstream_priority", ["1", "2"]) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_no_appsec_event_propagation_set_to_user_keep( - tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811 + tracer, upstream_priority, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") - - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass - - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": upstream_priority, - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.appsec=1,any=tag", - "ot-baggage-key1": "value1", - } + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass + + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": upstream_priority, + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.appsec=1,any=tag", + "ot-baggage-key1": "value1", + } - context = HTTPPropagator.extract(headers) + context = HTTPPropagator.extract(headers) - tracer.context_provider.activate(context) + tracer.context_provider.activate(context) - with tracer.trace("local_root_span") as span: - assert span.trace_id == 1234 - assert span.parent_id == 5678 - # Enforced user keep regardless of upstream priority - assert span.context.sampling_priority == USER_KEEP - assert span.context.dd_origin == "synthetics" - assert span.context._meta == { - "_dd.origin": "synthetics", - "_dd.p.dm": "-3", - "_dd.p.appsec": "1", - } - with tracer.trace("child_span") as child_span: - assert child_span.trace_id == 1234 - assert child_span.parent_id != 5678 - assert child_span.context.sampling_priority == USER_KEEP - assert child_span.context.dd_origin == "synthetics" - assert child_span.context._meta == { + with tracer.trace("local_root_span") as span: + assert span.trace_id == 1234 + assert span.parent_id == 5678 + # Enforced user keep regardless of upstream priority + assert span.context.sampling_priority == USER_KEEP + assert span.context.dd_origin == "synthetics" + assert span.context._meta == { "_dd.origin": "synthetics", "_dd.p.dm": "-3", "_dd.p.appsec": "1", } - - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) - assert next_headers["x-datadog-origin"] == "synthetics" - assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) - assert next_headers["x-datadog-trace-id"] == "1234" - assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") - - # Ensure span sets user keep regardless of received priority (appsec event upstream) - assert span._metrics["_sampling_priority_v1"] == USER_KEEP - - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + with tracer.trace("child_span") as child_span: + assert child_span.trace_id == 1234 + assert child_span.parent_id != 5678 + assert child_span.context.sampling_priority == USER_KEEP + assert child_span.context.dd_origin == "synthetics" + assert child_span.context._meta == { + "_dd.origin": "synthetics", + "_dd.p.dm": "-3", + "_dd.p.appsec": "1", + } + + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) + assert next_headers["x-datadog-origin"] == "synthetics" + assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) + assert next_headers["x-datadog-trace-id"] == "1234" + assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") + + # Ensure span sets user keep regardless of received priority (appsec event upstream) + assert span._metrics["_sampling_priority_v1"] == USER_KEEP + + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) @pytest.mark.parametrize("upstream_priority", ["1", "2"]) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_appsec_event_present_propagation_force_keep( - tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811 + tracer, upstream_priority, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") - - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass - - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": upstream_priority, - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.appsec=1,any=tag", - "ot-baggage-key1": "value1", - } + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass + + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": upstream_priority, + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.appsec=1,any=tag", + "ot-baggage-key1": "value1", + } - context = HTTPPropagator.extract(headers) + context = HTTPPropagator.extract(headers) - tracer.context_provider.activate(context) + tracer.context_provider.activate(context) - with tracer.trace("local_root_span") as span: - _asm_manual_keep(span) - assert span.trace_id == 1234 - assert span.parent_id == 5678 - assert span.context.sampling_priority == USER_KEEP # user keep always - assert span.context.dd_origin == "synthetics" - assert span.context._meta == { - "_dd.origin": "synthetics", - "_dd.p.dm": "-4", - "_dd.p.appsec": "1", - } - with tracer.trace("child_span") as child_span: - assert child_span.trace_id == 1234 - assert child_span.parent_id != 5678 - assert child_span.context.sampling_priority == USER_KEEP # user keep always - assert child_span.context.dd_origin == "synthetics" - assert child_span.context._meta == { + with tracer.trace("local_root_span") as span: + _asm_manual_keep(span) + assert span.trace_id == 1234 + assert span.parent_id == 5678 + assert span.context.sampling_priority == USER_KEEP # user keep always + assert span.context.dd_origin == "synthetics" + assert span.context._meta == { "_dd.origin": "synthetics", "_dd.p.dm": "-4", "_dd.p.appsec": "1", } - - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) - assert next_headers["x-datadog-origin"] == "synthetics" - assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) # user keep always - assert next_headers["x-datadog-trace-id"] == "1234" - assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") - - # Ensure span set to user keep regardless received priority (appsec event upstream) - assert span._metrics["_sampling_priority_v1"] == USER_KEEP # user keep always - - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + with tracer.trace("child_span") as child_span: + assert child_span.trace_id == 1234 + assert child_span.parent_id != 5678 + assert child_span.context.sampling_priority == USER_KEEP # user keep always + assert child_span.context.dd_origin == "synthetics" + assert child_span.context._meta == { + "_dd.origin": "synthetics", + "_dd.p.dm": "-4", + "_dd.p.appsec": "1", + } + + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) + assert next_headers["x-datadog-origin"] == "synthetics" + assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) # user keep always + assert next_headers["x-datadog-trace-id"] == "1234" + assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") + + # Ensure span set to user keep regardless received priority (appsec event upstream) + assert span._metrics["_sampling_priority_v1"] == USER_KEEP # user keep always + + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) def test_extract_with_baggage_http_propagation(tracer): # noqa: F811 diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index f432403d3f9..4cdcf876aba 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -2043,30 +2043,38 @@ def test_import_ddtrace_tracer_not_module(): assert isinstance(tracer, Tracer) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) -def test_asm_standalone_configuration(appsec_enabled, iast_enabled): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") +def test_asm_standalone_configuration(sca_enabled, appsec_enabled, iast_enabled): + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer = ddtrace.Tracer() + tracer.configure(appsec_enabled=appsec_enabled, iast_enabled=iast_enabled, appsec_standalone_enabled=True) + if appsec_enabled: + assert tracer._asm_enabled is True + if iast_enabled: + assert tracer._iast_enabled is True + if sca_enabled == "true": + assert bool(ddtrace.config._sca_enabled) is True + + assert tracer._appsec_standalone_enabled is True + assert tracer._apm_opt_out is True + assert tracer.enabled is False + + assert isinstance(tracer._sampler.limiter, RateLimiter) + assert tracer._sampler.limiter.rate_limit == 1 + assert tracer._sampler.limiter.time_window == 60e9 + + assert tracer._compute_stats is False - tracer = ddtrace.Tracer() - tracer.configure(appsec_enabled=appsec_enabled, iast_enabled=iast_enabled, appsec_standalone_enabled=True) - if appsec_enabled: - assert tracer._asm_enabled is True - if iast_enabled: - assert tracer._iast_enabled is True - - assert tracer._appsec_standalone_enabled is True - assert tracer._apm_opt_out is True - assert tracer.enabled is False - - assert isinstance(tracer._sampler.limiter, RateLimiter) - assert tracer._sampler.limiter.rate_limit == 1 - assert tracer._sampler.limiter.time_window == 60e9 - - assert tracer._compute_stats is False # reset tracer values - tracer.configure(appsec_enabled=False, iast_enabled=False, appsec_standalone_enabled=False) + with override_env({"DD_APPSEC_SCA_ENABLED": "false"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, iast_enabled=False, appsec_standalone_enabled=False) def test_gc_not_used_on_root_spans():