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
7 changes: 4 additions & 3 deletions appsec/cmake/update_helper_rust_stamp.cmake
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
cmake_minimum_required(VERSION 3.14)
# Called at build time via cmake -P with HELPER_RUST_DIR and STAMP_FILE
# defined on the command line. Touches STAMP_FILE only when at least one Rust
# source file (*.rs, Cargo.toml, Cargo.lock) is newer than the stamp, so that
# cargo is not re-run on every build when nothing has changed.
# defined on the command line. Touches STAMP_FILE only when at least one helper
# input is newer than the stamp, so that cargo is not re-run on every build when
# nothing has changed.
set(_LIBDDWAF_RUST_DIR "${HELPER_RUST_DIR}/../third_party/libddwaf-rust")
file(GLOB_RECURSE _sources
"${HELPER_RUST_DIR}/*.rs"
"${HELPER_RUST_DIR}/Cargo.toml"
"${HELPER_RUST_DIR}/Cargo.lock"
"${HELPER_RUST_DIR}/../recommended.json"
"${_LIBDDWAF_RUST_DIR}/*.rs"
"${_LIBDDWAF_RUST_DIR}/Cargo.toml"
"${_LIBDDWAF_RUST_DIR}/Cargo.lock"
Expand Down
3 changes: 1 addition & 2 deletions appsec/helper-rust/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ impl Service {
waf_ruleset::WafRuleset::from_file(PathBuf::from(path))
.with_context(|| format!("Error loading WAF ruleset from file {:?}", path))
} else {
waf_ruleset::WafRuleset::from_default_file()
.with_context(|| "Error loading WAF ruleset from default file")
Ok(waf_ruleset::WafRuleset::from_default())
};

let ruleset = match maybe_ruleset {
Expand Down
83 changes: 23 additions & 60 deletions appsec/helper-rust/src/service/waf_ruleset.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::client::log;
use std::{
fs::File,
io::{BufRead, BufReader, Read, Seek},
path::{Path, PathBuf},
io::{BufReader, Read, Seek},
path::Path,
};

use anyhow::{anyhow, Context};
use anyhow::Context;

const DEFAULT_RULESET: &[u8] =
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../recommended.json"));

pub struct WafRuleset {
doc: libddwaf::object::WafObject,
Expand Down Expand Up @@ -36,9 +39,13 @@ impl WafRuleset {
Ok(WafRuleset::new(doc, rules_version))
}

pub fn from_default_file() -> anyhow::Result<WafRuleset> {
let file = get_default_rules_file()?;
WafRuleset::from_file(&file)
pub fn from_default() -> WafRuleset {
let ruleset = WafRuleset::from_slice(DEFAULT_RULESET)
.expect("embedded default ruleset is valid JSON");

log::info!("Loaded embedded default WAF ruleset");

ruleset
}

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

fn get_default_rules_file() -> anyhow::Result<PathBuf> {
let helper_path = get_helper_path();

let base_path: PathBuf = if let Ok(helper_path) = helper_path {
helper_path
.parent()
.ok_or(anyhow!("No parent for {:?}", helper_path))?
.to_path_buf()
} else {
get_self_path().with_context(|| "Could find neither lib path nor self exe path")?
};

let file = base_path.join("../etc/recommended.json");
if file.exists() {
return Ok(file);
}
#[cfg(test)]
mod tests {
use super::*;

let file_legacy = base_path.join("../etc/dd-appsec/recommended.json");
if file_legacy.exists() {
return Ok(file_legacy);
}
#[test]
fn default_ruleset_is_embedded() {
let ruleset = WafRuleset::from_default();
let rules_version = ruleset
.rules_version()
.expect("default ruleset should expose its version");

Err(anyhow!(
"Could not find recommended.json in either ../etc/ or ../etc/dd-appsec/"
))
}

fn get_helper_path() -> anyhow::Result<PathBuf> {
// Match both libddappsec-helper.so (C++ helper) and
// libddappsec-helper-rust.so (Rust helper)
const LIBNAME_PREFIX: &str = "/libddappsec-helper";
const MAPS_PATH: &str = "/proc/self/maps";

let file = File::open(MAPS_PATH)?;
let reader = BufReader::new(file);

for line in reader.lines() {
let line = line?;
if line.contains(LIBNAME_PREFIX) {
if let Some(pos) = line.find('/') {
return Ok(PathBuf::from(&line[pos..]));
} else {
return Err(anyhow!("Should not happen"));
}
}
assert!(!rules_version.is_empty());
}

Err(anyhow!(
"Could not find libddappsec-helper*.so in /proc/self/maps"
))
}

pub fn get_self_path() -> anyhow::Result<PathBuf> {
const SELF_EXE: &str = "/proc/self/exe";

let path = std::fs::read_link(SELF_EXE)?;
Ok(path)
}
1 change: 1 addition & 0 deletions appsec/tests/integration/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,7 @@ def helperRustInputs = [
'../../helper-rust/coverage_init.c',
'../../helper-rust/outline_atomics.c',
'../../helper-rust/.cargo/config.toml',
'../../recommended.json',
],
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestMethodOrder
import org.junit.jupiter.api.condition.DisabledIf
import org.testcontainers.containers.BindMode
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

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

Expand Down Expand Up @@ -935,10 +942,11 @@ class TelemetryTests {
}
assert requestSup.get() != null

// Blocking request: 80.80.80.80 hits the recommended.json IP blocklist rule
// (on_match: ["block"]). The WAF returns a block_request action.
// Blocking request: this User-Agent hits the recommended.json Datadog test
// scanner rule (ua0-600-56x, on_match: ["block"]). The WAF returns a
// block_request action.
HttpRequest req = CONTAINER.buildReq('/hello.php')
.header('X-Forwarded-For', '80.80.80.80').GET().build()
.header('User-Agent', 'dd-test-scanner-log-block').GET().build()
CONTAINER.traceFromRequest(req, ofString()) { HttpResponse<String> resp ->
assert resp.statusCode() == 403
}
Expand Down Expand Up @@ -1163,7 +1171,7 @@ class TelemetryTests {
],
'datadog/2/ASM/rasp_lfi_block_override/config': [
rules_override: [[
rules_target: [[rule_id: 'rasp-001-001']],
rules_target: [[rule_id: 'rasp-930-100']],
on_match: ['block']
]]
]
Expand Down
5 changes: 3 additions & 2 deletions datadog-setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -2365,8 +2365,9 @@ function get_ini_settings($sourcesDir, $appsecHelperPath, $appsecRulesPath)
'default' => $appsecRulesPath,
'commented' => true,
'description' => [
'The path to the rules json file. The sidecar process must be able to read the',
'file. This ini setting is configured by the installer',
'Optional path to a custom rules json file. When this setting is not configured,',
'the Rust helper uses its embedded default rules and the C++ helper uses the',
'default rules file installed next to the helper.',
],
],
[
Expand Down
Loading