Skip to content

Commit

Permalink
Release 1.17.0-alpha3 (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anilm3 authored Apr 8, 2024
1 parent 1ba0674 commit 6af4b64
Show file tree
Hide file tree
Showing 10 changed files with 483 additions and 76 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# libddwaf release
### v1.17.0-alpha3 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics))
#### Changes
- Action semantics and related improvements ([#277](https://github.com/DataDog/libddwaf/pull/277))

#### Miscellaneous
- LFI detector fuzzer ([#274](https://github.com/DataDog/libddwaf/pull/274))

### v1.17.0-alpha2 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics))
#### Changes
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ if (LIBDDWAF_TESTING)
add_subdirectory(benchmark EXCLUDE_FROM_ALL)
add_subdirectory(fuzzer EXCLUDE_FROM_ALL)
add_subdirectory(tools EXCLUDE_FROM_ALL)
add_subdirectory(examples EXCLUDE_FROM_ALL)

include(cmake/clang-tidy.cmake)
include(cmake/clang-format.cmake)
Expand Down
259 changes: 187 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![Build](https://github.com/DataDog/libddwaf/actions/workflows/build.yml/badge.svg)](https://github.com/DataDog/libddwaf/actions/workflows/build.yml)

# Datadog's WAF
# 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.

Expand Down Expand Up @@ -65,116 +65,231 @@ The general process is as follows:

### Example

```c
The full example can be found [here](examples/example.cpp).

```cpp
#include <yaml-cpp/yaml.h>
#include "ddwaf.h"

int main(void)
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 main()
{
YAML::Node doc = YAML::Load(waf_rule.data());

YAML::Node doc = YAML::Load(R"({version: '0.1', events: [{id: 1, tags: {type: flow1}, conditions: [{operation: match_regex, parameters: {inputs: [arg1], regex: .*}},{operation: match_regex, parameters: {inputs: [arg2], regex: .*}}], action: record}]})");

ddwaf_object rule = doc.as<ddwaf_object>();//= convert_yaml_to_args(doc);
auto rule = doc.as<ddwaf_object>();//= convert_yaml_to_args(doc);
ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr);
ddwaf_object_free(&rule);
if (handle == nullptr) {
exit(EXIT_FAILURE);
return EXIT_FAILURE;
}

ddwaf_context context = ddwaf_context_init(handle);
if (handle == nullptr) {
if (context == nullptr) {
ddwaf_destroy(handle);
exit(EXIT_FAILURE);
return EXIT_FAILURE;
}

ddwaf_object param1, param2, tmp;
ddwaf_object_map(&param1);
ddwaf_object_map(&param2);
ddwaf_object_map_add(&param1, "arg1", ddwaf_object_string(&tmp, "string 1"));
ddwaf_object_map_add(&param2, "arg2", ddwaf_object_string(&tmp, "string 2"));
ddwaf_object root, tmp;
ddwaf_object_map(&root);
ddwaf_object_map_add(&root, "arg1", ddwaf_object_string(&tmp, "string 1"));
ddwaf_object_map_add(&root, "arg2", ddwaf_object_string(&tmp, "string 2"));

ddwaf_result ret;
auto code = ddwaf_run(context, &param1, nullptr, &ret, LONG_TIME);
printf("Output first run: %d\n", code);
ddwaf_result_free(&ret);
auto code = ddwaf_run(context, &root, nullptr, &ret, 1000000 /* microseconds */);
std::cout << "Output second run: " << code << '\n';
if (code == DDWAF_MATCH) {
YAML::Emitter out(std::cout);
out.SetIndent(2);
out.SetMapFormat(YAML::Block);
out.SetSeqFormat(YAML::Block);
out << object_to_yaml(ret.events);
out << object_to_yaml(ret.actions);
}

code = ddwaf_run(context, &param2, nullptr, &ret, LONG_TIME);
printf("Output second run: %d - %s\n", ret.action, ret.data);
ddwaf_result_free(&ret);

ddwaf_context_destroy(context);
ddwaf_destroy(handle);

return EXIT_SUCCESS;
}
```

### YAML to ddwaf::object converter example
#### YAML to ddwaf::object converter example

```cpp
namespace YAML
{
class parsing_error : public std::exception
{
public:
parsing_error(const std::string& what) : what_(what) {}
const char* what() { return what_.c_str(); }

protected:
const std::string what_;
};
namespace YAML {

ddwaf_object node_to_arg(const Node& node)
template <>
struct as_if<ddwaf_object, void>
{
switch (node.Type())
explicit as_if(const Node& node_) : node(node_) {}

static ddwaf_object yaml_to_object_helper(const Node& node)
{
case NodeType::Sequence:
ddwaf_object arg;
switch (node.Type())
{
ddwaf_object arg;
ddwaf_object_array(&arg);
for (auto it = node.begin(); it != node.end(); ++it)
{
ddwaf_object child = node_to_arg(*it);
ddwaf_object_array_add(&arg, &child);
}
return arg;
case NodeType::Sequence:
ddwaf_object_array(&arg);
break;
case NodeType::Map:
ddwaf_object_map(&arg);
break;
case NodeType::Scalar:
ddwaf_object_string(&arg, node.Scalar().c_str());
break;
case NodeType::Null:
ddwaf_object_null(&arg);
break;
case NodeType::Undefined:
default:
ddwaf_object_invalid(&arg);
break;
}
case NodeType::Map:
{
ddwaf_object arg;
ddwaf_object_map(&arg);
for (auto it = node.begin(); it != node.end(); ++it)
{
std::string key = it->first.as<std::string>();
ddwaf_object child = node_to_arg(it->second);
ddwaf_object_map_addl(&arg, key.c_str(), key.size(), &child);
}
return arg;
return arg;
}

ddwaf_object operator()() const {
std::list<std::tuple<ddwaf_object&, YAML::Node, YAML::Node::const_iterator>> stack;

ddwaf_object root = yaml_to_object_helper(node);
if (root.type == DDWAF_OBJ_MAP || root.type == DDWAF_OBJ_ARRAY) {
stack.emplace_back(root, node, node.begin());
}
case NodeType::Scalar:
{
const std::string& value = node.Scalar();
ddwaf_object arg;
ddwaf_object_stringl(&arg, value.c_str(), value.size());
return arg;

while (!stack.empty()) {
auto current_depth = stack.size();
auto &[parent_obj, parent_node, it] = stack.back();

for (;it != parent_node.end(); ++it) {
YAML::Node child_node = parent_node.IsMap() ? it->second : *it;
auto child_obj = yaml_to_object_helper(child_node);
if (parent_obj.type == DDWAF_OBJ_MAP) {
auto key = it->first.as<std::string>();
ddwaf_object_map_add(&parent_obj, key.c_str(), &child_obj);
} else if (parent_obj.type == DDWAF_OBJ_ARRAY) {
ddwaf_object_array_add(&parent_obj, &child_obj);
}

if (child_obj.type == DDWAF_OBJ_MAP || child_obj.type == DDWAF_OBJ_ARRAY) {
auto &child_ptr = parent_obj.array[parent_obj.nbEntries - 1];
stack.emplace_back(child_ptr, child_node, child_node.begin());
++it;
break;
}
}

if (current_depth == stack.size()) { stack.pop_back(); }
}
case NodeType::Null:
case NodeType::Undefined:
// Perhaps this should return an invalid pwarg
ddwaf_object arg;
ddwaf_object_invalid(&arg);
return arg;
return root;
}

throw parsing_error("Invalid YAML node type");
}
template <>
struct as_if<ddwaf_object, void> {
explicit as_if(const Node& node_) : node(node_) {}
ddwaf_object operator()() const { return node_to_arg(node); }
const Node& node;
};

} // namespace YAML

```
#### ddwaf::object to YAML converter example
```cpp
namespace {
YAML::Node object_to_yaml_helper(const ddwaf_object &obj)
{
YAML::Node output;
switch (obj.type) {
case DDWAF_OBJ_BOOL:
output = obj.boolean;
break;
case DDWAF_OBJ_SIGNED:
output = obj.intValue;
break;
case DDWAF_OBJ_UNSIGNED:
output = obj.uintValue;
break;
case DDWAF_OBJ_FLOAT:
output = obj.f64;
break;
case DDWAF_OBJ_STRING:
output = std::string{obj.stringValue, obj.nbEntries};
break;
case DDWAF_OBJ_MAP:
output = YAML::Load("{}");
break;
case DDWAF_OBJ_ARRAY:
output = YAML::Load("[]");
break;
case DDWAF_OBJ_INVALID:
case DDWAF_OBJ_NULL:
output = YAML::Null;
break;
};
return output;
}
} // namespace
YAML::Node object_to_yaml(const ddwaf_object &obj)
{
std::list<std::tuple<const ddwaf_object&, YAML::Node, std::size_t>> stack;
YAML::Node root = object_to_yaml_helper(obj);
if (obj.type == DDWAF_OBJ_MAP || obj.type == DDWAF_OBJ_ARRAY) {
stack.emplace_back(obj, root, 0);
}
while (!stack.empty()) {
auto current_depth = stack.size();
auto &[parent_obj, parent_node, index] = stack.back();
for (;index <parent_obj.nbEntries; ++index) {
auto &child_obj = parent_obj.array[index];
auto child_node = object_to_yaml_helper(child_obj);
if (parent_obj.type == DDWAF_OBJ_MAP) {
std::string key{child_obj.parameterName, child_obj.parameterNameLength};
parent_node[key] = child_node;
} else if (parent_obj.type == DDWAF_OBJ_ARRAY) {
parent_node.push_back(child_node);
}
if (child_obj.type == DDWAF_OBJ_MAP || child_obj.type == DDWAF_OBJ_ARRAY) {
stack.emplace_back(child_obj, child_node, 0);
++index;
break;
}
}
if (current_depth == stack.size()) {
stack.pop_back();
}
}
return root;
}
```

Expand Down
52 changes: 52 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,57 @@
# Upgrading libddwaf

## Upgrading from `1.16.x` to `1.17.0`

### Action semantics

In order to support non-blocking actions, and specifically those with dynamic parameters, a number changes have been introduced in this version.

The first change introduced is that users must now provide action definitions during the initialisation or update process. This information is used internally to understand the nature of an action and, more specifically, the nature of the side-effects of a rule. While this version doesn't yet take advantage of this information for rule scheduling, it does so in order to dynamically generate stack IDs when the `stack_trace` action is produced by a rule. As a reminder, action definitions have the following rough schema:

```json
{
"actions": [{
"id": "<id: string>",
"type": "<type: string>",
"parameters": { "<kv map of parameters>" }
}]
}
```

Secondly, since the definition of each action is now available internally, the schema of `ddwaf_result.actions` has been updated from an array of IDs to a map of action types, each containing its own set of parameters:

```json
{
"block_request": {
"status_code": 403,
"type": "auto"
}
}
```

This means the caller no longer has to translate action IDs to their relevant definition and any blocking action conflicts are resolved internally following a simple set of rules:
- When multiple actions of the same type are present, the first one produced has precedence.
- An action of type`redirect_request` has priority over an action of type `block_request`.

In addition, specific action types can now have dynamic parameters, such as the `generate_stack` action type, which requires the inclusion of a stack trace UUID in both the action parameters and the relevant event:

```json
{
"generate_stack": {
"stack_id": "f96a33a2-f5c1-11ee-99aa-9bdcccee26aa"
}
}
```

Finally the following set of default actions are included:
- `block`: of type `block_request`, requires the caller to block the request and provides the following default parameters:
- `status_code`: `403`
- `type`: `auto`
- `grpc_status_code`: `10`
- `stack_trace`: of type `generate_stack`, requires the user to generate a stack trace with the UUID provided in the `stack_id` parameter.
- `extract_schema`: of type `generate_schema`, instructs the user to call the WAF again with the relevant parameters required for schema generation.
- `monitor`: an internal reserved action.

## Upgrading from `1.14.0` to `1.15.0`

### Interface changes
Expand Down
Loading

0 comments on commit 6af4b64

Please sign in to comment.