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(asm): standalone code security #11446

Merged
merged 16 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
5 changes: 5 additions & 0 deletions .github/workflows/system-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ jobs:
docker load < images_artifacts/${{ matrix.weblog-variant}}_weblog_${{ github.sha }}.tar.gz
docker load < images_artifacts/agent_${{ github.sha }}.tar.gz

# TODO: Enable once https://github.com/DataDog/system-tests/pull/3506 is merged
# - name: Run IAST_STANDALONE
# if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1'
# run: ./run.sh IAST_STANDALONE

- name: Run DEFAULT
if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'other'
run: ./run.sh DEFAULT
Expand Down
6 changes: 3 additions & 3 deletions ddtrace/_trace/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,10 @@ def __init__(
# _user_sampler is the backup in case we need to revert from remote config to local
self._user_sampler: Optional[BaseSampler] = DatadogSampler()
self._asm_enabled = asm_config._asm_enabled
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 and self._appsec_standalone_enabled
self._apm_opt_out = (self._asm_enabled or self._iast_enabled) and self._appsec_standalone_enabled
if self._apm_opt_out:
self.enabled = False
# Disable compute stats (neither agent or tracer should compute them)
Expand Down Expand Up @@ -267,7 +268,6 @@ def __init__(
self._partial_flush_min_spans = config._partial_flush_min_spans
# Direct link to the appsec processor
self._appsec_processor = None
self._iast_enabled = asm_config._iast_enabled
self._endpoint_call_counter_span_processor = EndpointCallCounterProcessor()
self._span_processors, self._appsec_processor, self._deferred_processors = _default_span_processors_factory(
self._filters,
Expand Down Expand Up @@ -498,7 +498,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:
if self._appsec_standalone_enabled and (self._asm_enabled or self._iast_enabled):
self._apm_opt_out = True
self.enabled = False
# Disable compute stats (neither agent or tracer should compute them)
Expand Down
2 changes: 0 additions & 2 deletions ddtrace/appsec/_iast/_iast_request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from ddtrace.appsec._iast._metrics import _set_span_tag_iast_executed_sink
from ddtrace.appsec._iast._metrics import _set_span_tag_iast_request_tainted
from ddtrace.appsec._iast.reporter import IastSpanReporter
from ddtrace.appsec._trace_utils import _asm_manual_keep
from ddtrace.constants import ORIGIN_KEY
from ddtrace.internal import core
from ddtrace.internal.logger import get_logger
Expand Down Expand Up @@ -132,7 +131,6 @@ def _iast_end_request(ctx=None, span=None, *args, **kwargs):
if report_data:
report_data.build_and_scrub_value_parts()
req_span.set_tag_str(IAST.JSON, report_data._to_str())
_asm_manual_keep(req_span)
_set_metric_iast_request_tainted()
_set_span_tag_iast_request_tainted(req_span)
_set_span_tag_iast_executed_sink(req_span)
Expand Down
2 changes: 2 additions & 0 deletions ddtrace/appsec/_iast/taint_sinks/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Text

from ddtrace import tracer
from ddtrace.appsec._trace_utils import _asm_manual_keep
from ddtrace.internal.logger import get_logger
from ddtrace.internal.utils.cache import LFUCache

Expand Down Expand Up @@ -90,6 +91,7 @@ def _prepare_report(cls, vulnerability_type, evidence, file_name, line_number):
span = tracer.current_root_span()
if span:
span_id = span.span_id
_asm_manual_keep(span)
gnufede marked this conversation as resolved.
Show resolved Hide resolved

vulnerability = Vulnerability(
type=vulnerability_type,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- |
Code Security: This introduces "Standalone Code Security", a feature that disables APM in the tracer but keeps Code Security (IAST) enabled. In order to enable it, set the environment variables ``DD_IAST_ENABLED=1`` and ``DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED=1``.
20 changes: 14 additions & 6 deletions tests/appsec/appsec/test_asm_standalone.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,35 @@

@pytest.fixture(
params=[
{"appsec_enabled": True, "appsec_standalone_enabled": True},
{"appsec_enabled": True, "appsec_standalone_enabled": False},
{"appsec_enabled": False, "appsec_standalone_enabled": False},
{"appsec_enabled": False, "appsec_standalone_enabled": True},
{"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},
]
)
def tracer_appsec_standalone(request, tracer):
tracer.configure(api_version="v0.4", **request.param)
yield tracer, request.param
# Reset tracer configuration
tracer.configure(api_version="v0.4", appsec_enabled=False, appsec_standalone_enabled=False)
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 == {"appsec_enabled": True, "appsec_standalone_enabled": True}:
if args.get("appsec_standalone_enabled", None) and (
args.get("appsec_enabled", None) or args.get("iast_enabled", None)
):
assert span.get_metric("_dd.apm.enabled") == 0.0
else:
assert span.get_metric("_dd.apm.enabled") is None
49 changes: 39 additions & 10 deletions tests/tracer/test_propagation.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,15 @@ def test_extract(tracer): # noqa: F811
assert len(context.get_all_baggage_items()) == 3


def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation(tracer): # noqa: F811
tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True)
@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
):
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",
Expand Down Expand Up @@ -362,8 +369,15 @@ def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation(t
tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False)


def test_asm_standalone_missing_propagation_tags_no_appsec_event_trace_dropped(tracer): # noqa: F811
tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True)
@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
):
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
Expand Down Expand Up @@ -428,10 +442,15 @@ def test_asm_standalone_missing_propagation_tags_appsec_event_present_trace_kept
tracer.configure(appsec_enabled=False, appsec_standalone_enabled=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, # noqa: F811
tracer, appsec_enabled, iast_enabled # noqa: F811
):
tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True)
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
Expand Down Expand Up @@ -526,10 +545,15 @@ def test_asm_standalone_missing_appsec_tag_appsec_event_present_trace_kept(


@pytest.mark.parametrize("upstream_priority", ["1", "2"])
@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 # noqa: F811
tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811
):
tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True)
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
Expand Down Expand Up @@ -585,10 +609,15 @@ def test_asm_standalone_present_appsec_tag_no_appsec_event_propagation_set_to_us


@pytest.mark.parametrize("upstream_priority", ["1", "2"])
@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 # noqa: F811
tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811
):
tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True)
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
Expand Down
17 changes: 13 additions & 4 deletions tests/tracer/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2043,10 +2043,19 @@ def test_import_ddtrace_tracer_not_module():
assert isinstance(tracer, Tracer)


def test_asm_standalone_configuration():
@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")

tracer = ddtrace.Tracer()
tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True)
assert tracer._asm_enabled is True
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
Expand All @@ -2057,7 +2066,7 @@ def test_asm_standalone_configuration():

assert tracer._compute_stats is False
# reset tracer values
tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False)
tracer.configure(appsec_enabled=False, iast_enabled=False, appsec_standalone_enabled=False)


def test_gc_not_used_on_root_spans():
Expand Down
Loading