Skip to content

Commit 3eef763

Browse files
committed
Appsec on sidecar thread
1 parent c8f85f0 commit 3eef763

7 files changed

Lines changed: 142 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.datadog.appsec.php.integration
2+
3+
import com.datadog.appsec.php.docker.AppSecContainer
4+
import com.datadog.appsec.php.docker.FailOnUnmatchedTraces
5+
import com.datadog.appsec.php.docker.InspectContainerHelper
6+
import org.junit.jupiter.api.Test
7+
import org.testcontainers.junit.jupiter.Container
8+
import org.testcontainers.junit.jupiter.Testcontainers
9+
10+
import java.net.http.HttpRequest
11+
import java.net.http.HttpResponse
12+
13+
import static com.datadog.appsec.php.integration.TestParams.getPhpVersion
14+
import static com.datadog.appsec.php.integration.TestParams.getVariant
15+
import static java.net.http.HttpResponse.BodyHandlers.ofString
16+
17+
/**
18+
* Exercises AppSec with the sidecar hosted by an in-process master-listener
19+
* thread. This must remain a separate container from the default-mode suites:
20+
* the connection mode is read during PHP MINIT and cannot be changed safely
21+
* after the web server has started.
22+
*/
23+
@Testcontainers
24+
class SidecarThreadModeTests {
25+
@Container
26+
@FailOnUnmatchedTraces
27+
public static final AppSecContainer CONTAINER =
28+
new AppSecContainer(
29+
workVolume: this.name,
30+
baseTag: 'apache2-mod-php',
31+
phpVersion: phpVersion,
32+
phpVariant: variant,
33+
www: 'base',
34+
) {
35+
@Override
36+
void configure() {
37+
super.configure()
38+
withEnv('DD_TRACE_SIDECAR_CONNECTION_MODE', 'thread')
39+
}
40+
}
41+
42+
static void main(String[] args) {
43+
InspectContainerHelper.run(CONTAINER)
44+
}
45+
46+
@Test
47+
void 'appsec blocks requests in forced sidecar thread mode'() {
48+
HttpRequest request = CONTAINER.buildReq('/phpinfo.php')
49+
.header('Content-type', 'application/json')
50+
.header('Accept', 'application/json')
51+
.header('X-Forwarded-For', '80.80.80.80')
52+
.GET()
53+
.build()
54+
55+
def trace = CONTAINER.traceFromRequest(request, ofString()) {
56+
HttpResponse<String> response ->
57+
assert response.statusCode() == 403
58+
}
59+
60+
def span = trace.first()
61+
assert span.metrics.'_dd.appsec.enabled' == 1.0d
62+
assert span.metrics.'_dd.appsec.waf.duration' > 0.0d
63+
assert span.meta.'appsec.blocked' == 'true'
64+
assert span.meta.'_dd.appsec.json'
65+
}
66+
}

components-rs/datadog.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,18 @@ ddog_MaybeError ddog_send_debugger_diagnostics(const struct ddog_RemoteConfigSta
235235
void ddog_sidecar_enable_appsec(ddog_CharSlice log_file_path,
236236
ddog_CharSlice log_level);
237237

238+
/**
239+
* Starts a thread-mode master listener with the PHP-linked AppSec backend
240+
* registered in the listener's process.
241+
*/
242+
ddog_MaybeError ddog_sidecar_connect_master_php(int32_t pid);
243+
244+
/**
245+
* Ensures the connected sidecar's AppSec backend is started using the
246+
* configuration captured from the PHP extension.
247+
*/
248+
ddog_MaybeError ddog_sidecar_ensure_appsec_started(struct ddog_SidecarTransport **transport);
249+
238250
ddog_MaybeError ddog_sidecar_connect_php(struct ddog_SidecarTransport **connection,
239251
const char *error_path,
240252
ddog_CharSlice log_level,

components-rs/sidecar.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,48 @@ pub extern "C" fn ddog_sidecar_enable_appsec(log_file_path: CharSlice, log_level
156156
});
157157
}
158158

159+
/// Starts a thread-mode master listener with the PHP-linked AppSec backend
160+
/// registered in the listener's process.
161+
#[no_mangle]
162+
pub extern "C" fn ddog_sidecar_connect_master_php(pid: i32) -> MaybeError {
163+
#[cfg(unix)]
164+
ddtrace_sidecar::register_appsec_backend();
165+
166+
datadog_sidecar_ffi::ddog_sidecar_connect_master(pid)
167+
}
168+
169+
/// Ensures the connected sidecar's AppSec backend is started using the
170+
/// configuration captured from the PHP extension.
171+
#[no_mangle]
172+
pub extern "C" fn ddog_sidecar_ensure_appsec_started(
173+
transport: &mut Box<SidecarTransport>,
174+
) -> MaybeError {
175+
let Some(appsec_config) = APPSEC_CONFIG.lock().unwrap().clone() else {
176+
return MaybeError::None;
177+
};
178+
179+
#[cfg(unix)]
180+
{
181+
let started = try_c!(datadog_sidecar::service::blocking::ensure_appsec_started(
182+
transport,
183+
appsec_config.log_file_path.as_os_str().as_bytes().to_vec(),
184+
appsec_config.log_level,
185+
));
186+
if !started {
187+
return MaybeError::Some(libdd_common_ffi::Error::from(
188+
"AppSec backend failed to initialize",
189+
));
190+
}
191+
}
192+
193+
#[cfg(windows)]
194+
{
195+
_ = (transport, appsec_config);
196+
}
197+
198+
MaybeError::None
199+
}
200+
159201
fn sidecar_connect(cfg: config::Config) -> anyhow::Result<Box<SidecarTransport>> {
160202
let mut stream = Box::new(run_sidecar(cfg)?);
161203
// Generally the Send buffer ought to be big enough for instantaneous transmission

ext/sidecar.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ DATADOG_PUBLIC ddog_SidecarTransport **ddtrace_get_sidecar_transport(void) {
118118
#endif
119119

120120
static void dd_sidecar_post_connect(ddog_SidecarTransport **transport, bool is_fork, const char *logpath) {
121+
if (!datadog_ffi_try("Failed starting AppSec in sidecar",
122+
ddog_sidecar_ensure_appsec_started(transport))) {
123+
LOG(WARN, "AppSec sidecar backend is unavailable");
124+
}
125+
121126
ddog_CharSlice session_id = (ddog_CharSlice) {.ptr = (char *) datadog_formatted_session_id, .len = sizeof(datadog_formatted_session_id)};
122127
ddog_CharSlice root_session_id = datadog_is_empty_session_id(datadog_formatted_root_session_id) ? DDOG_CHARSLICE_C("") : (ddog_CharSlice) {.ptr = (char *) datadog_formatted_root_session_id, .len = sizeof(datadog_formatted_root_session_id)};
123128
ddog_CharSlice parent_session_id = datadog_is_empty_session_id(datadog_formatted_parent_session_id) ? DDOG_CHARSLICE_C("") : (ddog_CharSlice) {.ptr = (char *) datadog_formatted_parent_session_id, .len = sizeof(datadog_formatted_parent_session_id)};
@@ -266,7 +271,7 @@ static ddog_SidecarTransport *dd_sidecar_connect(bool as_worker, bool is_fork) {
266271
current_pid, datadog_sidecar_master_pid);
267272
datadog_sidecar_master_pid = current_pid;
268273
if (!datadog_ffi_try("Failed starting sidecar master listener as orphaned child",
269-
ddog_sidecar_connect_master((int32_t)datadog_sidecar_master_pid)) ||
274+
ddog_sidecar_connect_master_php((int32_t)datadog_sidecar_master_pid)) ||
270275
!datadog_ffi_try("Failed connecting to new sidecar master as orphaned child",
271276
ddog_sidecar_connect_worker((int32_t)datadog_sidecar_master_pid, &sidecar_transport))) {
272277
dd_free_endpoints();
@@ -326,7 +331,8 @@ static void datadog_sidecar_setup_thread_mode() {
326331
return;
327332
}
328333

329-
if (!datadog_ffi_try("Failed starting sidecar master listener", ddog_sidecar_connect_master((int32_t)datadog_sidecar_master_pid))) {
334+
if (!datadog_ffi_try("Failed starting sidecar master listener",
335+
ddog_sidecar_connect_master_php((int32_t)datadog_sidecar_master_pid))) {
330336
LOG(WARN, "Failed to start sidecar master listener");
331337
if (datadog_endpoint) {
332338
dd_free_endpoints();
@@ -474,7 +480,7 @@ void datadog_sidecar_minit(void) {
474480

475481
if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD) {
476482
datadog_ffi_try("Starting sidecar master listener in MINIT",
477-
ddog_sidecar_connect_master(datadog_sidecar_master_pid));
483+
ddog_sidecar_connect_master_php(datadog_sidecar_master_pid));
478484
}
479485
}
480486

@@ -511,7 +517,7 @@ void datadog_sidecar_handle_fork(void) {
511517

512518
datadog_sidecar_master_pid = (int32_t)getpid();
513519
if (!datadog_ffi_try("Failed starting sidecar master listener in child process",
514-
ddog_sidecar_connect_master((int32_t)datadog_sidecar_master_pid))) {
520+
ddog_sidecar_connect_master_php((int32_t)datadog_sidecar_master_pid))) {
515521
if (datadog_endpoint) {
516522
dd_free_endpoints();
517523
}

sidecar/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,21 @@ pub fn start_or_connect_to_sidecar(config: Config) -> anyhow::Result<SidecarTran
6262
)
6363
}
6464

65+
/// Registers the AppSec backend linked into the PHP extension.
66+
///
67+
/// Registration is process-local and idempotent. Thread-mode listeners need
68+
/// this to run in the master process before their listener thread starts;
69+
/// subprocess-mode sidecars call it again from their entrypoint.
70+
pub fn register_appsec_backend() {
71+
datadog_sidecar::appsec::register_backend_factory(create_backend);
72+
}
73+
6574
#[no_mangle]
6675
pub extern "C" fn ddtrace_sidecar_entry_point(trampoline_data: &TrampolineData) {
6776
#[cfg(feature = "helper-rust-coverage")]
6877
ddappsec_helper::initialize_coverage();
6978

70-
datadog_sidecar::appsec::register_backend_factory(create_backend);
79+
register_appsec_backend();
7180
datadog_sidecar::ddog_daemon_entry_point(trampoline_data);
7281
}
7382

0 commit comments

Comments
 (0)