Datadog's WAF & RASP Engine
libddwaf is Datadog's implementation of a Web Application Firewall (WAF) engine, with a goal of low performance and memory overhead, and embeddability in a wide variety of language runtimes through a C API.
Versioning semantics
libddwaf follows Semantic Versioning 2.0, with a slight twist.
libddwaf is a heir to libsqreen, the latter which was semantically versioned using 0.y.z:
Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
To mark the break between libsqreen and libddwaf (which involved a lot of renaming and changes), it was decided to bump the major version, but some time was needed still to stabilise the public API. Therefore libddwaf's 1.y.z is operating following semver's usual 0.y.z, with minor y meaning "breaking change" and patch z meaning "bugfix".
In addition libddwaf's "unstable" marker on releases means the API may evolve and have breaking changes on minor versions. Nonetheless its codebase and resulting binaries are considered production-ready as the "unstable" marker only applies to libddwaf's public API.
Since libddwaf should not be used directly and is wrapped by binding libraries to various languages, any such low-level C API change is handled by Datadog internally and isolated by the higher level binding code, which aims to provide a much stabler high level language-oriented API. In any case, the binding library dependency is directly consumed by the Datadog tracing client libraries, and should there be a breaking change in the binding API it would be handled as gracefully as technically possible within the tracing client library level, and properly handled using the tracing client library dependency verssion constraints so that it picks only compatible versions of the binding library.
Building
Quick Start
This project is built using cmake.
On Linux and Darwin, the following should produce a static and a dynamic library inside of build:
mkdir -p build && cd build
cmake ..
make -j4
A cross-platform way to achieve the same result (e.g on Windows):
cmake -E make_directory build
cd build
cmake ..
cmake --build . --target all -j4
And a more involved example, with specific targets, building, then running the test suite along with debug information:
cmake -E make_directory build packages
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=.. -DCPACK_PACKAGE_DIRECTORY=../packages ..
cmake --build . --config RelWithDebInfo --verbose --target libddwaf_shared --target libddwaf_static --target waf_test -j
cd ../tests
../build/tests/waf_test
Cargo
The repository is also a Cargo package named libddwaf-src. Its build script
uses CMake and the target C and C++ compilers selected by Cargo to build the
native library. The default static feature builds libddwaf.a; enable the
shared feature with default-features = false to build the shared library.
The static and shared features are mutually exclusive. The crate propagates
the selected native library and its link dependencies to Cargo consumers.
The manual-link feature can be combined with either library feature to
suppress its native link directive. It is intended for sys crates that need to
handle linking or load the selected artifact themselves.
The build script exports root, include, and lib metadata for an immediate
dependent such as libddwaf-sys. Cargo exposes those values as
DEP_DDWAF_SRC_ROOT, DEP_DDWAF_SRC_INCLUDE, and DEP_DDWAF_SRC_LIB.
Usage
The general process is as follows:
- Load processing rules as a
ddwaf_objectdata structure into the WAF engine. This returns a handler. - Start a context with the returned handler.
- Prepare input to test against the rules into a
ddwaf_objectdata structure. - Perform a run against the input in the context.
- Optionally perform subsequent runs against additional input in the same context. Only newly relevant rules are checked against.
- Discard context and/or handler as needed.
Example
The full example can be found here.
constexpr std::string_view waf_rule = R"(
version: "2.1"
rules:
- id: "1"
name: rule 1
tags:
type: flow1
category: test
conditions:
- operator: match_regex
parameters:
inputs:
- address: arg1
regex: .*
- operator: match_regex
parameters:
inputs:
- address: arg2
regex: .*
on_match: [ block ]
)";
int
YAML to ddwaf::object converter example
// namespace YAML
ddwaf::object to YAML converter example
// namespace
YAML::Node
Example rule
The following rule:
version: 2.1
rules:
- id: crs-042-001
name: Detect a script tag
tags:
category: attack_attempt
type: xss
conditions:
- operator: match_regex
parameters:
regex: "^<script>"
inputs:
- address: http.server.query
applied to the http.server.query value http://localhost/?q=<script>alert() hello world produces the following result:
- rule:
id: crs-042-001
name: Detect a script tag
tags:
category: attack_attempt
type: xss
rule_matches:
- operator: match_regex
operator_value: ^<script>
parameters:
- address: http.server.query
key_path:
- q
value: <script>alert() hello world
highlight:
- <script>