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 sca billing #11655

Merged
merged 7 commits into from
Dec 17, 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
6 changes: 4 additions & 2 deletions ddtrace/_trace/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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``
134 changes: 119 additions & 15 deletions tests/appsec/appsec/test_asm_standalone.py
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading