diff --git a/appsec/tests/extension/inc/distributed_tracing.inc b/appsec/tests/extension/inc/distributed_tracing.inc new file mode 100644 index 0000000000..6d9124b9e4 --- /dev/null +++ b/appsec/tests/extension/inc/distributed_tracing.inc @@ -0,0 +1,26 @@ + $value) { + $headers[strtolower($key)] = mb_convert_encoding($value, 'ISO-8859-1', 'UTF-8'); + } + return $headers; +} + +function dt_dump_headers_from_httpbin(array $headers, array $whitelist) +{ + foreach ($headers as $key => $value) { + if (!in_array($key, $whitelist, true)) { + continue; + } + echo $key . ': ' . $value . PHP_EOL; + } +} diff --git a/appsec/tests/extension/inc/mock_helper.php b/appsec/tests/extension/inc/mock_helper.php index 7a9a75594a..7a5b7212dd 100644 --- a/appsec/tests/extension/inc/mock_helper.php +++ b/appsec/tests/extension/inc/mock_helper.php @@ -293,6 +293,22 @@ function response_config_sync() { return response("config_sync", []); } +function request_without_events() +{ + return [ + response_list(response_request_init([[['ok', []]], [], true])), + response_list(response_request_shutdown([[['ok', []]], [], true])), + ]; +} + +function request_with_events() +{ + return [ + response_list(response_request_init([[['record', []]],['{"found":"attack"}','{"another":"attack"}'],true])), + response_list(response_request_shutdown([[['record', []]], ['{"yet another":"attack"}'], true])) + ]; +} + // vim: set et sw=4 ts=4: ?> diff --git a/appsec/tests/extension/inc/request_replayer.inc b/appsec/tests/extension/inc/request_replayer.inc new file mode 100644 index 0000000000..fa6cf42d40 --- /dev/null +++ b/appsec/tests/extension/inc/request_replayer.inc @@ -0,0 +1,151 @@ +endpoint = sprintf( + 'http://%s:%d', + getenv('DD_AGENT_HOST') ?: 'request-replayer', + getenv('DD_TRACE_AGENT_PORT') ?: '80' + ); + + $this->flushInterval = getenv('DD_TRACE_AGENT_FLUSH_INTERVAL') + ? (int) getenv('DD_TRACE_AGENT_FLUSH_INTERVAL') * 100 + : 50000; + + $this->maxIteration = (strncasecmp(PHP_OS, "WIN", 3) === 0) ? 500 : 200; + } + + public function waitForFlush() + { + usleep($this->flushInterval * 2); + } + + public function waitForRequest($matcher) + { + $i = 0; + do { + if ($i++ == $this->maxIteration) { + throw new Exception("wait for replay timeout"); + } + usleep($this->flushInterval); + + $requests = $this->replayAllRequests(); + if (is_array($requests)) { + foreach ($requests as $request) { + if ($matcher($request)) { + return $request; + } + } + } + } while (true); + } + + public function waitForDataAndReplay($ignoreTelemetry = true) + { + $i = 0; + do { + if ($i++ == $this->maxIteration) { + throw new Exception("wait for replay timeout"); + } + usleep($this->flushInterval); + } while (empty($data = $this->replayRequest($ignoreTelemetry))); + return $data; + } + + public function replayRequest($ignoreTelemetry = false) + { + // Request replayer now returns as many requests as were sent during a session. + // For the scope of the tests, we are returning the very first one. + $allRequests = $this->replayAllRequests(); + if ($allRequests && $ignoreTelemetry) { + $allRequests = array_values(array_filter($allRequests, function ($v) { return $v["uri"] != '/telemetry/proxy/api/v2/apmtelemetry'; })); + } + return $allRequests ? $allRequests[0] : []; + } + + public function replayAllRequests() + { + return json_decode(file_get_contents($this->endpoint . '/replay', false, stream_context_create([ + "http" => [ + "header" => "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ], + ])), true); + } + + public function replayHeaders($showOnly = []) + { + $request = $this->waitForDataAndReplay(); + if (!isset($request['headers'])) { + return []; + } + + ksort($request['headers']); + + $headers = []; + foreach ($request['headers'] as $name => $value) { + $name = strtolower($name); + if ($showOnly && !in_array($name, $showOnly, true)) { + continue; + } + $headers[$name] = $value; + } + return $headers; + } + + public function setResponse($array) { + file_get_contents($this->endpoint . '/next-response', false, stream_context_create([ + "http" => [ + "method" => "POST", + "content" => json_encode($array), + "header" => [ + "Content-Type: application/json", + "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ] + ], + ])); + } + + public static function launchUnixProxy($socketPath) { + @unlink($socketPath); + $code = str_replace("\n", "", ' +ignore_user_abort(true); /* prevent bailout... */ +$server = stream_socket_server("unix://' . $socketPath . '"); +print "1\n"; /* ready marker */ +if (!$client = stream_socket_accept($server, 5)) { + return; +} +$replayer = stream_socket_client("request-replayer:80"); +$all = $read = [$client, $replayer]; +foreach ($read as $fp) stream_set_blocking($fp, false); +while (stream_select($read, $w, $e, null)) { + $data = fread($fp = reset($read), 4096); + if ($data == "") { + return; + } + fwrite($fp == $replayer ? $client : $replayer, $data); + $read = $all; +} +'); + + static $unix_proxy_process_reference; + $unix_proxy_process_reference = popen(PHP_BINARY . " -r '$code'", 'r'); + fread($unix_proxy_process_reference, 1); // ready + } +} diff --git a/appsec/tests/extension/standalone/appsec_upstream_no_attack.phpt b/appsec/tests/extension/standalone/appsec_upstream_no_attack.phpt new file mode 100644 index 0000000000..0274cd28ab --- /dev/null +++ b/appsec/tests/extension/standalone/appsec_upstream_no_attack.phpt @@ -0,0 +1,49 @@ +--TEST-- +Appsec upstream and no attack, should leave the priorit as it comes on the upstream +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_AGENT_FLUSH_INTERVAL=333 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=0 +DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED=1 +DD_APPSEC_ENABLED=1 +DD_TRACE_TRACED_INTERNAL_FUNCTIONS=curl_exec +HTTP_X_DATADOG_TRACE_ID=42 +HTTP_X_DATADOG_TAGS=_dd.p.appsec=1 +HTTP_X_DATADOG_SAMPLING_PRIORITY=2 +--INI-- +extension=ddtrace.so +datadog.trace.agent_test_session_token=background-sender/agent_sampling_c +--SKIPIF-- + + +--FILE-- + +--EXPECTF-- +string(24) "Appsec should be present" +bool(true) +string(29) "Sampling priority should be 2" +int(2) +string(22) "Apm should be disabled" +int(0) +string(40) "Propagated sampling priority should be 2" +string(1) "2" +string(31) "Appsec tag should be propagated" +bool(true) \ No newline at end of file diff --git a/appsec/tests/extension/standalone/no_appsec_upstream_no_attack.phpt b/appsec/tests/extension/standalone/no_appsec_upstream_no_attack.phpt new file mode 100644 index 0000000000..42777aa99e --- /dev/null +++ b/appsec/tests/extension/standalone/no_appsec_upstream_no_attack.phpt @@ -0,0 +1,63 @@ +--TEST-- +No appsec upstream and no attack, set priority to auto reject +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_AGENT_FLUSH_INTERVAL=333 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=0 +DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED=1 +DD_APPSEC_ENABLED=1 +DD_TRACE_TRACED_INTERNAL_FUNCTIONS=curl_exec +HTTP_X_DATADOG_TRACE_ID=42 +HTTP_X_DATADOG_TAGS=_dd.p.other=1 +--INI-- +extension=ddtrace.so +datadog.trace.agent_test_session_token=background-sender/agent_sampling_a +--SKIPIF-- + + +--FILE-- + +--EXPECTF-- +string(40) "First call: Appsec should not be present" +bool(false) +string(41) "First call: Sampling priority should be 1" +int(1) +string(34) "First call: Apm should be disabled" +int(0) +string(50) "First call: There should not be header propagation" +array(0) { +} +string(41) "Second call: Appsec should not be present" +bool(false) +string(42) "Second call: Sampling priority should be 0" +int(0) +string(35) "Second call: Apm should be disabled" +int(0) +string(51) "Second call: There should not be header propagation" +array(0) { +} \ No newline at end of file diff --git a/appsec/tests/extension/standalone/no_appsec_upstream_with_attack.phpt b/appsec/tests/extension/standalone/no_appsec_upstream_with_attack.phpt new file mode 100644 index 0000000000..7b80bc26b5 --- /dev/null +++ b/appsec/tests/extension/standalone/no_appsec_upstream_with_attack.phpt @@ -0,0 +1,45 @@ +--TEST-- +No appsec upstream with attack, set priority to keep +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_AGENT_FLUSH_INTERVAL=333 +DD_TRACE_GENERATE_ROOT_SPAN=1 +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=0 +DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED=1 +DD_APPSEC_ENABLED=1 +DD_TRACE_TRACED_INTERNAL_FUNCTIONS=curl_exec +HTTP_X_DATADOG_TRACE_ID=42 +HTTP_X_DATADOG_TAGS=_dd.p.other=1 +--INI-- +extension=ddtrace.so +datadog.trace.agent_test_session_token=background-sender/agent_sampling_b +--FILE-- + +--EXPECTF-- +string(24) "Appsec should be present" +bool(true) +string(29) "Sampling priority should be 2" +int(2) +string(22) "Apm should be disabled" +int(0) +string(40) "Propagated sampling priority should be 2" +string(1) "2" +string(31) "Appsec tag should be propagated" +bool(true) \ No newline at end of file diff --git a/appsec/tests/extension/standalone/simulate_request.inc b/appsec/tests/extension/standalone/simulate_request.inc new file mode 100644 index 0000000000..b95592d2be --- /dev/null +++ b/appsec/tests/extension/standalone/simulate_request.inc @@ -0,0 +1,35 @@ +waitForDataAndReplay()["body"], true); + return ['spans' => $root["chunks"][0]["spans"] ?? $root[0], 'curl_headers' => $curl_headers]; +} +