Skip to content

Commit d004542

Browse files
authored
Merge pull request #4037 from DataDog/glopes/embed-ruleset
Embed default ruleset in helper-rust
2 parents 50a327e + 1e4e714 commit d004542

6 files changed

Lines changed: 44 additions & 71 deletions

File tree

appsec/cmake/update_helper_rust_stamp.cmake

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
cmake_minimum_required(VERSION 3.14)
22
# Called at build time via cmake -P with HELPER_RUST_DIR and STAMP_FILE
3-
# defined on the command line. Touches STAMP_FILE only when at least one Rust
4-
# source file (*.rs, Cargo.toml, Cargo.lock) is newer than the stamp, so that
5-
# cargo is not re-run on every build when nothing has changed.
3+
# defined on the command line. Touches STAMP_FILE only when at least one helper
4+
# input is newer than the stamp, so that cargo is not re-run on every build when
5+
# nothing has changed.
66
set(_LIBDDWAF_RUST_DIR "${HELPER_RUST_DIR}/../third_party/libddwaf-rust")
77
file(GLOB_RECURSE _sources
88
"${HELPER_RUST_DIR}/*.rs"
99
"${HELPER_RUST_DIR}/Cargo.toml"
1010
"${HELPER_RUST_DIR}/Cargo.lock"
11+
"${HELPER_RUST_DIR}/../recommended.json"
1112
"${_LIBDDWAF_RUST_DIR}/*.rs"
1213
"${_LIBDDWAF_RUST_DIR}/Cargo.toml"
1314
"${_LIBDDWAF_RUST_DIR}/Cargo.lock"

appsec/helper-rust/src/service.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,7 @@ impl Service {
258258
waf_ruleset::WafRuleset::from_file(PathBuf::from(path))
259259
.with_context(|| format!("Error loading WAF ruleset from file {:?}", path))
260260
} else {
261-
waf_ruleset::WafRuleset::from_default_file()
262-
.with_context(|| "Error loading WAF ruleset from default file")
261+
Ok(waf_ruleset::WafRuleset::from_default())
263262
};
264263

265264
let ruleset = match maybe_ruleset {
Lines changed: 23 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use crate::client::log;
22
use std::{
33
fs::File,
4-
io::{BufRead, BufReader, Read, Seek},
5-
path::{Path, PathBuf},
4+
io::{BufReader, Read, Seek},
5+
path::Path,
66
};
77

8-
use anyhow::{anyhow, Context};
8+
use anyhow::Context;
9+
10+
const DEFAULT_RULESET: &[u8] =
11+
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../recommended.json"));
912

1013
pub struct WafRuleset {
1114
doc: libddwaf::object::WafObject,
@@ -36,9 +39,13 @@ impl WafRuleset {
3639
Ok(WafRuleset::new(doc, rules_version))
3740
}
3841

39-
pub fn from_default_file() -> anyhow::Result<WafRuleset> {
40-
let file = get_default_rules_file()?;
41-
WafRuleset::from_file(&file)
42+
pub fn from_default() -> WafRuleset {
43+
let ruleset = WafRuleset::from_slice(DEFAULT_RULESET)
44+
.expect("embedded default ruleset is valid JSON");
45+
46+
log::info!("Loaded embedded default WAF ruleset");
47+
48+
ruleset
4249
}
4350

4451
pub fn from_slice(slice: &[u8]) -> anyhow::Result<WafRuleset> {
@@ -72,61 +79,17 @@ fn extract_rules_version<R: Read>(reader: R) -> Option<String> {
7279
parsed.metadata?.rules_version
7380
}
7481

75-
fn get_default_rules_file() -> anyhow::Result<PathBuf> {
76-
let helper_path = get_helper_path();
77-
78-
let base_path: PathBuf = if let Ok(helper_path) = helper_path {
79-
helper_path
80-
.parent()
81-
.ok_or(anyhow!("No parent for {:?}", helper_path))?
82-
.to_path_buf()
83-
} else {
84-
get_self_path().with_context(|| "Could find neither lib path nor self exe path")?
85-
};
86-
87-
let file = base_path.join("../etc/recommended.json");
88-
if file.exists() {
89-
return Ok(file);
90-
}
82+
#[cfg(test)]
83+
mod tests {
84+
use super::*;
9185

92-
let file_legacy = base_path.join("../etc/dd-appsec/recommended.json");
93-
if file_legacy.exists() {
94-
return Ok(file_legacy);
95-
}
86+
#[test]
87+
fn default_ruleset_is_embedded() {
88+
let ruleset = WafRuleset::from_default();
89+
let rules_version = ruleset
90+
.rules_version()
91+
.expect("default ruleset should expose its version");
9692

97-
Err(anyhow!(
98-
"Could not find recommended.json in either ../etc/ or ../etc/dd-appsec/"
99-
))
100-
}
101-
102-
fn get_helper_path() -> anyhow::Result<PathBuf> {
103-
// Match both libddappsec-helper.so (C++ helper) and
104-
// libddappsec-helper-rust.so (Rust helper)
105-
const LIBNAME_PREFIX: &str = "/libddappsec-helper";
106-
const MAPS_PATH: &str = "/proc/self/maps";
107-
108-
let file = File::open(MAPS_PATH)?;
109-
let reader = BufReader::new(file);
110-
111-
for line in reader.lines() {
112-
let line = line?;
113-
if line.contains(LIBNAME_PREFIX) {
114-
if let Some(pos) = line.find('/') {
115-
return Ok(PathBuf::from(&line[pos..]));
116-
} else {
117-
return Err(anyhow!("Should not happen"));
118-
}
119-
}
93+
assert!(!rules_version.is_empty());
12094
}
121-
122-
Err(anyhow!(
123-
"Could not find libddappsec-helper*.so in /proc/self/maps"
124-
))
125-
}
126-
127-
pub fn get_self_path() -> anyhow::Result<PathBuf> {
128-
const SELF_EXE: &str = "/proc/self/exe";
129-
130-
let path = std::fs::read_link(SELF_EXE)?;
131-
Ok(path)
13295
}

appsec/tests/integration/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,7 @@ def helperRustInputs = [
924924
'../../helper-rust/coverage_init.c',
925925
'../../helper-rust/outline_atomics.c',
926926
'../../helper-rust/.cargo/config.toml',
927+
'../../recommended.json',
927928
],
928929
]
929930

appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/TelemetryTests.groovy

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.junit.jupiter.api.Order
1414
import org.junit.jupiter.api.Test
1515
import org.junit.jupiter.api.TestMethodOrder
1616
import org.junit.jupiter.api.condition.DisabledIf
17+
import org.testcontainers.containers.BindMode
1718
import org.testcontainers.junit.jupiter.Container
1819
import org.testcontainers.junit.jupiter.Testcontainers
1920

@@ -49,6 +50,12 @@ class TelemetryTests {
4950
void configure() {
5051
super.configure()
5152
withEnv('RUST_LIB_BACKTRACE', '1')
53+
// This class strips appsec.rules from php.ini (see beforeAll) to exercise
54+
// the helpers' default-ruleset path, so both helpers must see the real
55+
// production ruleset here instead of the test fixture at
56+
// src/test/waf/recommended.json.
57+
setBinds(binds.findAll { it.volume.path != '/etc/recommended.json' })
58+
withFileSystemBind('../../recommended.json', '/etc/recommended.json', BindMode.READ_ONLY)
5259
}
5360
}
5461

@@ -935,10 +942,11 @@ class TelemetryTests {
935942
}
936943
assert requestSup.get() != null
937944

938-
// Blocking request: 80.80.80.80 hits the recommended.json IP blocklist rule
939-
// (on_match: ["block"]). The WAF returns a block_request action.
945+
// Blocking request: this User-Agent hits the recommended.json Datadog test
946+
// scanner rule (ua0-600-56x, on_match: ["block"]). The WAF returns a
947+
// block_request action.
940948
HttpRequest req = CONTAINER.buildReq('/hello.php')
941-
.header('X-Forwarded-For', '80.80.80.80').GET().build()
949+
.header('User-Agent', 'dd-test-scanner-log-block').GET().build()
942950
CONTAINER.traceFromRequest(req, ofString()) { HttpResponse<String> resp ->
943951
assert resp.statusCode() == 403
944952
}
@@ -1163,7 +1171,7 @@ class TelemetryTests {
11631171
],
11641172
'datadog/2/ASM/rasp_lfi_block_override/config': [
11651173
rules_override: [[
1166-
rules_target: [[rule_id: 'rasp-001-001']],
1174+
rules_target: [[rule_id: 'rasp-930-100']],
11671175
on_match: ['block']
11681176
]]
11691177
]

datadog-setup.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2365,8 +2365,9 @@ function get_ini_settings($sourcesDir, $appsecHelperPath, $appsecRulesPath)
23652365
'default' => $appsecRulesPath,
23662366
'commented' => true,
23672367
'description' => [
2368-
'The path to the rules json file. The sidecar process must be able to read the',
2369-
'file. This ini setting is configured by the installer',
2368+
'Optional path to a custom rules json file. When this setting is not configured,',
2369+
'the Rust helper uses its embedded default rules and the C++ helper uses the',
2370+
'default rules file installed next to the helper.',
23702371
],
23712372
],
23722373
[

0 commit comments

Comments
 (0)