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
26 changes: 26 additions & 0 deletions ext/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ ZEND_EXTERN_MODULE_GLOBALS(datadog);

#include <tracer/configuration_dependencies.h>

static bool ddtrace_alter_DD_TRACE_HEADER_TAGS(zval *old_value, zval *new_value, zend_string *new_str);

#define DD_TO_DATADOG_INC 5 /* "DD" expanded to "datadog" */

#define APPLY_0(...)
Expand Down Expand Up @@ -135,6 +137,28 @@ static void dd_ini_env_to_ini_name(const zai_str env_name, zai_config_name *ini_
ini_name->ptr[ini_name->len] = '\0';
}

// Security-testing headers (APPSEC-62412) are injected permanently into
// DD_TRACE_HEADER_TAGS so they ride the existing header-tags loop in
// dd_add_header_to_meta with zero extra per-request overhead.
static void dd_ensure_security_testing_headers(zend_array *ht) {
zval empty;
ZVAL_EMPTY_STRING(&empty);
zend_hash_str_add(ht, ZEND_STRL("x-datadog-endpoint-scan"), &empty);
zend_hash_str_add(ht, ZEND_STRL("x-datadog-security-test"), &empty);
}

static bool ddtrace_alter_DD_TRACE_HEADER_TAGS(zval *old_value, zval *new_value, zend_string *new_str) {
UNUSED(old_value);
if (DATADOG_G(remote_config_state) && !DATADOG_G(remote_config_writing)) {
if (!ddog_remote_config_alter_dynamic_config(DATADOG_G(remote_config_state),
DDOG_CHARSLICE_C("datadog.trace.header_tags"), zend_string_copy(new_str))) {
return false;
}
}
dd_ensure_security_testing_headers(Z_ARR_P(new_value));
return true;
}

bool datadog_config_minit(int module_number) {
if (!zai_config_minit(datadog_config_entries, (sizeof datadog_config_entries / sizeof *datadog_config_entries), dd_ini_env_to_ini_name,
module_number)) {
Expand All @@ -147,6 +171,8 @@ bool datadog_config_minit(int module_number) {
// This is intentional, so that places wishing to use values pre-RINIT do have to explicitly opt in by using the
// arduous way of accessing the decoded_value directly from zai_config_memoized_entries.
zai_config_first_time_rinit(false);
dd_ensure_security_testing_headers(
Z_ARR(zai_config_memoized_entries[DATADOG_CONFIG_DD_TRACE_HEADER_TAGS].decoded_value));

datadog_alter_dd_trace_debug(NULL, &zai_config_memoized_entries[DATADOG_CONFIG_DD_TRACE_DEBUG].decoded_value, NULL);
datadog_log_ginit();
Expand Down
71 changes: 71 additions & 0 deletions tests/Integrations/Swoole/SecurityTestingHeadersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace DDTrace\Tests\Integrations\Swoole;

use DDTrace\Tests\Common\WebFrameworkTestCase;
use DDTrace\Tests\Frameworks\Util\Request\GetSpec;

class SecurityTestingHeadersTest extends WebFrameworkTestCase
{
public static function getAppIndexScript()
{
return __DIR__ . '/../../Frameworks/Swoole/index.php';
}

protected static function isSwoole()
{
return true;
}

protected static function getEnvs()
{
return array_merge(parent::getEnvs(), [
'DD_TRACE_CLI_ENABLED' => 'true',
]);
}

protected static function getInis()
{
return array_merge(parent::getInis(), [
'extension' => 'swoole.so',
]);
}

public function testSecurityTestingHeadersCollectedUnconditionally()
{
$traces = $this->tracesFromWebRequest(function () {
$spec = GetSpec::create('request', '/', [
'X-Datadog-Endpoint-Scan: endpoint-scan-uuid',
'X-Datadog-Security-Test: security-test-uuid',
]);
$this->call($spec);
});

$span = $traces[0][0];
$this->assertSame(
'endpoint-scan-uuid',
$span['meta']['http.request.headers.x-datadog-endpoint-scan']
);
$this->assertSame(
'security-test-uuid',
$span['meta']['http.request.headers.x-datadog-security-test']
);
}

public function testSecurityTestingHeadersAbsentWhenNotSent()
{
$traces = $this->tracesFromWebRequest(function () {
$this->call(GetSpec::create('request', '/'));
});

$span = $traces[0][0];
$this->assertArrayNotHasKey(
'http.request.headers.x-datadog-endpoint-scan',
$span['meta']
);
$this->assertArrayNotHasKey(
'http.request.headers.x-datadog-security-test',
$span['meta']
);
}
}
50 changes: 50 additions & 0 deletions tests/ext/inferred_proxy/security_headers_forwarded.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
Security-testing headers are forwarded to the inferred proxy span
--ENV--
DD_TRACE_AUTO_FLUSH_ENABLED=0
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_CODE_ORIGIN_FOR_SPANS_ENABLED=0
DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED=1
HTTP_X_DD_PROXY=aws-apigateway
HTTP_X_DD_PROXY_REQUEST_TIME_MS=100
HTTP_X_DD_PROXY_PATH=/test
HTTP_X_DD_PROXY_HTTPMETHOD=GET
HTTP_X_DD_PROXY_DOMAIN_NAME=example.com
HTTP_X_DD_PROXY_STAGE=aws-prod
HTTP_X_DATADOG_ENDPOINT_SCAN=endpoint-scan-uuid
HTTP_X_DATADOG_SECURITY_TEST=security-test-uuid
METHOD=GET
SERVER_NAME=localhost:8888
SCRIPT_NAME=/foo.php
REQUEST_URI=/foo
DD_TRACE_DEBUG_PRNG_SEED=42
--FILE--
<?php
DDTrace\start_span();
DDTrace\close_span();
$spans = dd_trace_serialize_closed_spans();

// The PHP service-entry span has a parent_id pointing to the inferred span;
// the inferred span itself has no parent_id (it is the trace root).
$rootSpan = null;
$inferredSpan = null;
foreach ($spans as $span) {
if (!isset($span['parent_id'])) {
$inferredSpan = $span;
} else {
$rootSpan = $span;
}
}

// Tags must be present on the PHP service-entry span
var_dump($rootSpan['meta']['http.request.headers.x-datadog-endpoint-scan'] ?? 'NOT SET');
var_dump($rootSpan['meta']['http.request.headers.x-datadog-security-test'] ?? 'NOT SET');
// And forwarded to the inferred proxy span
var_dump($inferredSpan['meta']['http.request.headers.x-datadog-endpoint-scan'] ?? 'NOT SET');
var_dump($inferredSpan['meta']['http.request.headers.x-datadog-security-test'] ?? 'NOT SET');
?>
--EXPECT--
string(18) "endpoint-scan-uuid"
string(18) "security-test-uuid"
string(18) "endpoint-scan-uuid"
string(18) "security-test-uuid"
19 changes: 19 additions & 0 deletions tests/ext/root_span_security_testing_headers.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Security-testing headers are collected unconditionally on the root span
--ENV--
DD_TRACE_AUTO_FLUSH_ENABLED=0
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_TRACE_HEADER_TAGS=
HTTP_X_DATADOG_ENDPOINT_SCAN=endpoint-scan-uuid
HTTP_X_DATADOG_SECURITY_TEST=security-test-uuid
--FILE--
<?php
DDTrace\start_span();
DDTrace\close_span(0);
$spans = dd_trace_serialize_closed_spans();
var_dump($spans[0]['meta']['http.request.headers.x-datadog-endpoint-scan']);
var_dump($spans[0]['meta']['http.request.headers.x-datadog-security-test']);
?>
--EXPECT--
string(18) "endpoint-scan-uuid"
string(18) "security-test-uuid"
16 changes: 16 additions & 0 deletions tests/ext/root_span_security_testing_headers_absent.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Security-testing header tags are absent when headers are not sent
--ENV--
DD_TRACE_AUTO_FLUSH_ENABLED=0
DD_TRACE_GENERATE_ROOT_SPAN=0
--FILE--
<?php
DDTrace\start_span();
DDTrace\close_span(0);
$spans = dd_trace_serialize_closed_spans();
var_dump(array_key_exists('http.request.headers.x-datadog-endpoint-scan', $spans[0]['meta']));
var_dump(array_key_exists('http.request.headers.x-datadog-security-test', $spans[0]['meta']));
?>
--EXPECT--
bool(false)
bool(false)
1 change: 0 additions & 1 deletion tracer/configuration_dependencies.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ static bool dd_parse_tags(zai_str value, zval *decoded_value, bool persistent) {
return ddog_remote_config_alter_dynamic_config(DATADOG_G(remote_config_state), DDOG_CHARSLICE_C(config), zend_string_copy(new_str)); \
}

INI_CHANGE_DYNAMIC_CONFIG(DD_TRACE_HEADER_TAGS, "datadog.trace.header_tags")
INI_CHANGE_DYNAMIC_CONFIG(DD_TRACE_SAMPLE_RATE, "datadog.trace.sample_rate")
INI_CHANGE_DYNAMIC_CONFIG(DD_TRACE_LOGS_ENABLED, "datadog.logs_injection")
INI_CHANGE_DYNAMIC_CONFIG(DD_CODE_ORIGIN_FOR_SPANS_ENABLED, "datadog.code_origin_for_spans_enabled")
Expand Down
5 changes: 5 additions & 0 deletions tracer/serializer.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@

ZEND_EXTERN_MODULE_GLOBALS(datadog);

#define DD_TAG_HTTP_REQH_ENDPOINT_SCAN "http.request.headers.x-datadog-endpoint-scan"
#define DD_TAG_HTTP_REQH_SECURITY_TEST "http.request.headers.x-datadog-security-test"

extern void (*profiling_notify_trace_finished)(uint64_t local_root_span_id,
zai_str span_type,
zai_str resource);
Expand Down Expand Up @@ -1834,6 +1837,8 @@ ddog_SpanBytes *ddtrace_serialize_span_to_rust_span(ddtrace_span_data *span, ddo
transfer_meta_data(rust_span, serialized_inferred_span, "_dd.p.dm", true);
transfer_meta_data(rust_span, serialized_inferred_span, "_dd.p.ksr", false);
transfer_meta_data(rust_span, serialized_inferred_span, "_dd.p.tid", true);
transfer_meta_data(rust_span, serialized_inferred_span, DD_TAG_HTTP_REQH_ENDPOINT_SCAN, false);
transfer_meta_data(rust_span, serialized_inferred_span, DD_TAG_HTTP_REQH_SECURITY_TEST, false);
Comment thread
christophe-papazian marked this conversation as resolved.

ddog_set_span_error(serialized_inferred_span, ddog_get_span_error(rust_span));
}
Expand Down
Loading