Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5efc50c
Add new function to add endpoint
estringana Aug 18, 2025
314df92
Aproaching solution
estringana Aug 21, 2025
1a621d1
Use shared cache instead of local
estringana Aug 22, 2025
b9d0f1b
wip
estringana Aug 25, 2025
de543ad
Used last push endpoints
estringana Aug 26, 2025
205c68e
Tests passing
estringana Aug 27, 2025
0c6e73c
Fix rebase errors
estringana Aug 27, 2025
97dabd1
wip
estringana Sep 25, 2025
fdeb3cd
Update generated files
estringana Sep 25, 2025
8b58d11
Make string vectors work
estringana Sep 25, 2025
7086cfd
wip
estringana Sep 26, 2025
fb4fc56
Add i32 vector
estringana Sep 29, 2025
1526a3c
Add authentication vector
estringana Sep 29, 2025
9fb1ce2
Add serde json
estringana Sep 29, 2025
a728897
Remove debugging lines
estringana Sep 29, 2025
1c12aec
Replace char_c for slices
estringana Sep 29, 2025
8e0a9d6
Amend PR comments
estringana Sep 29, 2025
ceb48de
Strip invalid utf8 chars
estringana Sep 30, 2025
00ef539
Send Laravel endpoints
estringana Oct 6, 2025
90b5bec
Point to latest
estringana Oct 6, 2025
843ff5f
Fix add-routes-collection (#3446)
estringana Oct 15, 2025
9e48c84
Point to latest libdatadog
estringana Oct 16, 2025
d2665ba
Remove response_code
estringana Oct 17, 2025
1c491e6
Remove non used fields
estringana Oct 17, 2025
eead0d7
Point to latest
estringana Oct 17, 2025
ef1561e
Generate cbindgen
estringana Oct 17, 2025
3db5e4e
Test Laravel integration
estringana Oct 21, 2025
a754d27
Refactor telemetry collection on appsec integration tests
estringana Oct 21, 2025
b66e5b7
Point to latest
estringana Oct 21, 2025
689da43
Remove non necessary file
estringana Oct 21, 2025
0161972
Update php stub
estringana Oct 21, 2025
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
175 changes: 124 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions appsec/tests/integration/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ def buildTracerTask = { String version, String variant ->
}

def buildAppSecTask = { String version, String variant ->
def buildType = variant.contains('debug') ? 'Debug' : 'RelWithDebInfo'
buildRunInDockerTask(
baseName: 'buildAppsec',
baseTag: 'php',
Expand All @@ -334,17 +335,17 @@ def buildAppSecTask = { String version, String variant ->
],
command: [
'-e', '-c',
'''
"""
git config --global --add safe.directory '*'
cd /appsec
test -f CMakeCache.txt || \\
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \\
cmake -DCMAKE_BUILD_TYPE=$buildType \\
-DCMAKE_INSTALL_PREFIX=/appsec \\
-DDD_APPSEC_ENABLE_PATCHELF_LIBC=ON \\
-DDD_APPSEC_TESTING=ON /project/appsec
make -j extension ddappsec-helper && \\
touch ddappsec.so libddappsec-helper.so
'''
"""
]
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.datadog.appsec.php

import groovy.transform.Canonical
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import com.datadog.appsec.php.docker.AppSecContainer
import static java.net.http.HttpResponse.BodyHandlers.ofString

/**
* @link https://github.com/DataDog/instrumentation-telemetry-api-docs/blob/main/GeneratedDocumentation/ApiDocs/v2/producing-telemetry.md
Expand All @@ -22,6 +26,29 @@ class TelemetryHelpers {
}
}

static class AppEndpoints {
static names = ['app-endpoints']
List<Endpoint> endpoints

AppEndpoints(Map m) {
endpoints = m.endpoints.collect { new Endpoint(it as Map) }
}
}

static class Endpoint {
String method
String operationName
String path
String resourceName

Endpoint(Map m) {
method = m.method
operationName = m.operation_name
path = m.path
resourceName = m.resource_name
}
}

static class Metric {
String namespace
String name
Expand Down Expand Up @@ -137,4 +164,43 @@ class TelemetryHelpers {
autoEnabled = m.autoEnabled
}
}

public static <T> List<T> waitForTelemetryData(AppSecContainer container, int timeoutSec, Closure<Boolean> cl, Class<T> cls) {
List<T> messages = []
def deadline = System.currentTimeSeconds() + timeoutSec
def lastHttpReq = System.currentTimeSeconds() - 6
while (System.currentTimeSeconds() < deadline) {
if (System.currentTimeSeconds() - lastHttpReq > 5) {
lastHttpReq = System.currentTimeSeconds()
// used to flush global (not request-bound) telemetry metrics
def request = container.buildReq('/hello.php').GET().build()
def trace = container.traceFromRequest(request, ofString()) { HttpResponse<String> resp ->
assert resp.body().size() > 0
}
}
def telData = container.drainTelemetry(500)
messages.addAll(
TelemetryHelpers.filterMessages(telData, cls))
if (cl.call(messages)) {
break
}
}
messages
}

public static List<AppEndpoints> waitForAppEndpoints(AppSecContainer container, int timeoutSec, Closure<Boolean> cl) {
waitForTelemetryData(container, timeoutSec, cl, AppEndpoints)
}

public static List<GenerateMetrics> waitForMetrics(AppSecContainer container, int timeoutSec, Closure<Boolean> cl) {
waitForTelemetryData(container, timeoutSec, cl, GenerateMetrics)
}

public static List<WithIntegrations> waitForIntegrations(AppSecContainer container, int timeoutSec, Closure<Boolean> cl) {
waitForTelemetryData(container, timeoutSec, cl, WithIntegrations)
}

public static List<Logs> waitForLogs(AppSecContainer container, int timeoutSec, Closure<Boolean> cl) {
waitForTelemetryData(container, timeoutSec, cl, Logs)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import java.net.http.HttpResponse

import static com.datadog.appsec.php.integration.TestParams.getPhpVersion
import static com.datadog.appsec.php.integration.TestParams.getVariant
import com.datadog.appsec.php.TelemetryHelpers
import static java.net.http.HttpResponse.BodyHandlers.ofString

@Testcontainers
Expand Down Expand Up @@ -103,4 +104,28 @@ class Laravel8xTests {
assert span.meta."_dd.appsec.event_rules.version" != ''
assert span.meta."appsec.blocked" == "true"
}

@Test
void 'Endpoints are sended'() {
def trace = container.traceFromRequest('/') { HttpResponse<InputStream> resp ->
assert resp.statusCode() == 200
}

assert trace.traceId != null

List<TelemetryHelpers.Endpoint> endpoints

TelemetryHelpers.waitForAppEndpoints(container, 30, { List<TelemetryHelpers.Endpoint> messages ->
endpoints = messages.collectMany { it.endpoints }
endpoints.size() > 0
})

assert endpoints.size() == 6
assert endpoints.find { it.path == '/' && it.method == 'GET' && it.operationName == 'http.request' && it.resourceName == 'GET /' } != null
assert endpoints.find { it.path == 'authenticate' && it.method == 'GET' && it.operationName == 'http.request' && it.resourceName == 'GET authenticate' } != null
assert endpoints.find { it.path == 'register' && it.method == 'GET' && it.operationName == 'http.request' && it.resourceName == 'GET register' } != null
assert endpoints.find { it.path == 'dynamic-path/{param01}' && it.method == 'GET' && it.operationName == 'http.request' && it.resourceName == 'GET dynamic-path/{param01}' } != null
assert endpoints.find { it.path == 'sanctum/csrf-cookie' && it.method == 'GET' && it.operationName == 'http.request' && it.resourceName == 'GET sanctum/csrf-cookie' } != null
assert endpoints.find { it.path == 'api/user' && it.method == 'GET' && it.operationName == 'http.request' && it.resourceName == 'GET api/user' } != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class TelemetryTests {
TelemetryHelpers.Metric wafReq1
TelemetryHelpers.Metric wafReq2

waitForMetrics(30) { List<TelemetryHelpers.GenerateMetrics> messages ->
TelemetryHelpers.waitForMetrics(CONTAINER, 30) { List<TelemetryHelpers.GenerateMetrics> messages ->
def allSeries = messages.collectMany { it.series }
wafInit = allSeries.find { it.name == 'waf.init' }
wafReq1 = allSeries.find { it.name == 'waf.requests' && it.tags.size() == 2 }
Expand Down Expand Up @@ -195,7 +195,7 @@ class TelemetryTests {
]
])

def messages = waitForMetrics(30) { List<TelemetryHelpers.GenerateMetrics> messages ->
def messages = TelemetryHelpers.waitForMetrics(CONTAINER, 30) { List<TelemetryHelpers.GenerateMetrics> messages ->
def allSeries = messages
.collectMany { it.series }
.findAll {
Expand Down Expand Up @@ -281,7 +281,7 @@ class TelemetryTests {
]
])

def messages = waitForTelemetryLogs(30) { List<TelemetryHelpers.Logs> logs ->
def messages = TelemetryHelpers.waitForLogs(CONTAINER, 30) { List<TelemetryHelpers.Logs> logs ->
def relevantLogs = logs.collectMany { it.logs.findAll { it.tags.contains('log_type:rc::') } }
relevantLogs.size() >= 3
}.collectMany { it.logs }
Expand Down Expand Up @@ -327,7 +327,7 @@ class TelemetryTests {

List<TelemetryHelpers.IntegrationEntry> allIntegrations = []
boolean foundRedis = false
waitForIntegrations(30) { List<TelemetryHelpers.WithIntegrations> messages ->
TelemetryHelpers.waitForIntegrations(CONTAINER, 30) { List<TelemetryHelpers.WithIntegrations> messages ->
allIntegrations.addAll(messages.collectMany { it.integrations })
foundRedis = allIntegrations.find { it.name == 'phpredis' && it.enabled == Boolean.TRUE } != null
}
Expand All @@ -339,7 +339,7 @@ class TelemetryTests {
allIntegrations = []
foundRedis = false
boolean foundExec = false
waitForIntegrations(15) { List<TelemetryHelpers.WithIntegrations> messages ->
TelemetryHelpers.waitForIntegrations(CONTAINER, 15) { List<TelemetryHelpers.WithIntegrations> messages ->
allIntegrations.addAll(messages.collectMany { it.integrations })
foundRedis = allIntegrations.find { it.name == 'phpredis' && it.enabled == Boolean.TRUE } != null
foundExec = allIntegrations.find { it.name == 'exec' && it.enabled == Boolean.TRUE } != null
Expand All @@ -349,41 +349,6 @@ class TelemetryTests {
assert foundExec
}

private static List<TelemetryHelpers.GenerateMetrics> waitForMetrics(int timeoutSec, Closure<Boolean> cl) {
waitForTelemetryData(timeoutSec, cl, TelemetryHelpers.GenerateMetrics)
}

private static List<TelemetryHelpers.WithIntegrations> waitForIntegrations(int timeoutSec, Closure<Boolean> cl) {
waitForTelemetryData(timeoutSec, cl, TelemetryHelpers.WithIntegrations)
}

private static List<TelemetryHelpers.Logs> waitForTelemetryLogs(int timeoutSec, Closure<Boolean> cl) {
waitForTelemetryData(timeoutSec, cl, TelemetryHelpers.Logs)
}

private static <T> List<T> waitForTelemetryData(int timeoutSec, Closure<Boolean> cl, Class<T> cls) {
List<T> messages = []
def deadline = System.currentTimeSeconds() + timeoutSec
def lastHttpReq = System.currentTimeSeconds() - 6
while (System.currentTimeSeconds() < deadline) {
if (System.currentTimeSeconds() - lastHttpReq > 5) {
lastHttpReq = System.currentTimeSeconds()
// used to flush global (not request-bound) telemetry metrics
def request = CONTAINER.buildReq('/hello.php').GET().build()
def trace = CONTAINER.traceFromRequest(request, ofString()) { HttpResponse<String> resp ->
assert resp.body().size() > 0
}
}
def telData = CONTAINER.drainTelemetry(500)
messages.addAll(
TelemetryHelpers.filterMessages(telData, cls))
if (cl.call(messages)) {
break
}
}
messages
}

/**
* This test takes a long time (around 10-12 seconds) because the metric
* interval is hardcoded to 10 seconds in the metrics.rs.
Expand Down Expand Up @@ -423,7 +388,7 @@ class TelemetryTests {
TelemetryHelpers.Metric lfiTimeout
TelemetryHelpers.Metric ssrfTimeout

waitForMetrics(30) { List<TelemetryHelpers.GenerateMetrics> messages ->
TelemetryHelpers.waitForMetrics(CONTAINER, 30) { List<TelemetryHelpers.GenerateMetrics> messages ->
def allSeries = messages.collectMany { it.series }
wafReq1 = allSeries.find { it.name == 'waf.requests' && it.tags.size() == 2 }
lfiEval = allSeries.find{ it.name == 'rasp.rule.eval' && 'rule_type:lfi' in it.tags}
Expand Down Expand Up @@ -514,7 +479,7 @@ class TelemetryTests {
TelemetryHelpers.Metric loginSuccess
TelemetryHelpers.Metric loginFailure

waitForMetrics(30) { List<TelemetryHelpers.GenerateMetrics> messages ->
TelemetryHelpers.waitForMetrics(CONTAINER, 30) { List<TelemetryHelpers.GenerateMetrics> messages ->
def allSeries = messages.collectMany { it.series }
println allSeries
loginSuccess = allSeries.find{ it.name == 'sdk.event' && 'event_type:login_success' in it.tags}
Expand Down
29 changes: 26 additions & 3 deletions components-rs/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,17 +430,27 @@ typedef struct ddog_SidecarTransport ddog_SidecarTransport;
* Holds the raw parts of a Rust Vec; it should only be created from Rust,
* never from C.
*/
typedef struct ddog_Vec_CChar {
const char *ptr;
typedef struct ddog_Vec_CharSlice {
const ddog_CharSlice *ptr;
uintptr_t len;
uintptr_t capacity;
} ddog_Vec_CChar;
} ddog_Vec_CharSlice;

typedef struct ddog_Tag {
ddog_CharSlice name;
const struct ddog_DslString *value;
} ddog_Tag;

/**
* Holds the raw parts of a Rust Vec; it should only be created from Rust,
* never from C.
*/
typedef struct ddog_Vec_CChar {
const char *ptr;
uintptr_t len;
uintptr_t capacity;
} ddog_Vec_CChar;

typedef struct ddog_Vec_CChar *(*ddog_DynamicConfigUpdate)(ddog_CharSlice config,
ddog_CharSlice value,
bool return_old);
Expand Down Expand Up @@ -966,6 +976,19 @@ typedef struct ddog_AttributeAnyValueBytes ddog_AttributeAnyValueBytes;
typedef struct ddog_AttributeArrayValueBytes ddog_AttributeArrayValueBytes;


typedef enum ddog_Method {
DDOG_METHOD_GET = 0,
DDOG_METHOD_POST = 1,
DDOG_METHOD_PUT = 2,
DDOG_METHOD_DELETE = 3,
DDOG_METHOD_PATCH = 4,
DDOG_METHOD_HEAD = 5,
DDOG_METHOD_OPTIONS = 6,
DDOG_METHOD_TRACE = 7,
DDOG_METHOD_CONNECT = 8,
DDOG_METHOD_OTHER = 9,
} ddog_Method;

typedef struct ddog_AgentInfoReader ddog_AgentInfoReader;

typedef struct ddog_AgentRemoteConfigReader ddog_AgentRemoteConfigReader;
Expand Down
10 changes: 10 additions & 0 deletions components-rs/crashtracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,16 @@ DDOG_CHECK_RETURN
struct ddog_VoidResult ddog_crasht_CrashInfo_resolve_names(struct ddog_crasht_Handle_CrashInfo *crash_info,
uint32_t pid);

/**
* # Safety
* The `crash_info` can be null, but if non-null it must point to a Builder made by this module,
* which has not previously been dropped.
* This function will:
*/
DDOG_CHECK_RETURN
struct ddog_VoidResult ddog_crasht_CrashInfo_enrich_callstacks(struct ddog_crasht_Handle_CrashInfo *crash_info,
uint32_t pid);

/**
* # Safety
* The `crash_info` can be null, but if non-null it must point to a Builder made by this module,
Expand Down
6 changes: 5 additions & 1 deletion components-rs/ddtrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ bool ddog_type_can_be_instrumented(const struct ddog_RemoteConfigState *remote_c

bool ddog_global_log_probe_limiter_inc(const struct ddog_RemoteConfigState *remote_config);

struct ddog_Vec_CChar *ddog_CharSlice_to_owned(ddog_CharSlice str);
struct ddog_Vec_CharSlice *ddog_CharSlice_to_owned(ddog_CharSlice str);

bool ddog_remote_configs_service_env_change(struct ddog_RemoteConfigState *remote_config,
ddog_CharSlice service,
Expand Down Expand Up @@ -182,6 +182,10 @@ ddog_MaybeError ddog_sidecar_telemetry_filter_flush(struct ddog_SidecarTransport
ddog_CharSlice service,
ddog_CharSlice env);

bool ddog_sidecar_telemetry_are_endpoints_collected(ddog_ShmCacheMap *cache,
ddog_CharSlice service,
ddog_CharSlice env);

void ddog_init_span_func(void (*free_func)(struct _zend_string*),
void (*addref_func)(struct _zend_string*));

Expand Down
5 changes: 3 additions & 2 deletions components-rs/remote_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,9 @@ pub extern "C" fn ddog_global_log_probe_limiter_inc(remote_config: &RemoteConfig
}

#[no_mangle]
pub unsafe extern "C" fn ddog_CharSlice_to_owned(str: CharSlice) -> *mut Vec<c_char> {
Box::into_raw(Box::new(str.as_slice().into()))
pub unsafe extern "C" fn ddog_CharSlice_to_owned(str: CharSlice) -> *mut ddcommon_ffi::Vec<CharSlice> {
let std_vec: Vec<CharSlice> = vec![str];
Box::into_raw(Box::new(std_vec.into()))
}

#[no_mangle]
Expand Down
11 changes: 11 additions & 0 deletions components-rs/sidecar.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ ddog_MaybeError ddog_sidecar_telemetry_enqueueConfig(struct ddog_SidecarTranspor
enum ddog_ConfigurationOrigin origin,
ddog_CharSlice config_id);

/**
* Reports an endpoint to the telemetry.
*/
ddog_MaybeError ddog_sidecar_telemetry_addEndpoint(struct ddog_SidecarTransport **transport,
const struct ddog_InstanceId *instance_id,
const ddog_QueueId *queue_id,
enum ddog_Method method,
ddog_CharSlice path,
ddog_CharSlice operation_name,
ddog_CharSlice resource_name);

/**
* Reports a dependency to the telemetry.
*/
Expand Down
Loading
Loading