Skip to content

Commit a647a6b

Browse files
authored
When AppSec is enabled; unconditionally add http.client_ip (#184)
1 parent 63506a5 commit a647a6b

File tree

6 files changed

+45
-13
lines changed

6 files changed

+45
-13
lines changed

src/security/collection.cpp

+6-11
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@
44

55
#include <cassert>
66
#include <cctype>
7-
#include <charconv>
87
#include <cstring>
9-
#include <functional>
108
#include <string_view>
119
#include <unordered_map>
1210

1311
#include "../string_util.h"
14-
#include "client_ip.h"
1512
#include "ddwaf_obj.h"
1613
#include "decode.h"
17-
#include "library.h"
1814
#include "util.h"
1915

2016
extern "C" {
@@ -54,7 +50,8 @@ class ReqSerializer {
5450
public:
5551
explicit ReqSerializer(dnsec::DdwafMemres &memres) : memres_{memres} {}
5652

57-
ddwaf_object *serialize(const ngx_http_request_t &request) {
53+
ddwaf_object *serialize(const ngx_http_request_t &request,
54+
const std::optional<std::string> &client_ip) {
5855
dnsec::ddwaf_obj *root = memres_.allocate_objects<dnsec::ddwaf_obj>(1);
5956
dnsec::ddwaf_map_obj &root_map = root->make_map(6, memres_);
6057

@@ -63,7 +60,7 @@ class ReqSerializer {
6360
set_request_method(request, root_map.at_unchecked(2));
6461
set_request_headers_nocookies(request, root_map.at_unchecked(3));
6562
set_request_cookie(request, root_map.at_unchecked(4));
66-
set_client_ip(request, root_map.at_unchecked(5));
63+
set_client_ip(client_ip, root_map.at_unchecked(5));
6764

6865
return root;
6966
}
@@ -312,11 +309,8 @@ class ReqSerializer {
312309
set_value_from_iter(iter, slot);
313310
}
314311

315-
void set_client_ip(const ngx_http_request_t &request,
312+
void set_client_ip(const std::optional<std::string> &cl_ip,
316313
dnsec::ddwaf_obj &slot) {
317-
dnsec::ClientIp client_ip{dnsec::Library::custom_ip_header(), request};
318-
std::optional<std::string> cl_ip = client_ip.resolve();
319-
320314
slot.set_key(kClientIp);
321315
if (!cl_ip) {
322316
slot.make_null();
@@ -381,9 +375,10 @@ class ReqSerializer {
381375
namespace datadog::nginx::security {
382376

383377
ddwaf_object *collect_request_data(const ngx_http_request_t &request,
378+
const std::optional<std::string> &client_ip,
384379
DdwafMemres &memres) {
385380
ReqSerializer rs{memres};
386-
return rs.serialize(request);
381+
return rs.serialize(request, client_ip);
387382
}
388383

389384
ddwaf_object *collect_response_data(const ngx_http_request_t &request,

src/security/collection.h

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include <ddwaf.h>
44

5+
#include <optional>
6+
57
#include "ddwaf_memres.h"
68

79
extern "C" {
@@ -11,6 +13,7 @@ extern "C" {
1113
namespace datadog::nginx::security {
1214

1315
ddwaf_object *collect_request_data(const ngx_http_request_t &request,
16+
const std::optional<std::string> &client_ip,
1417
DdwafMemres &memres);
1518
ddwaf_object *collect_response_data(const ngx_http_request_t &request,
1619
DdwafMemres &memres);

src/security/context.cpp

+16-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "../ngx_http_datadog_module.h"
1414
#include "blocking.h"
1515
#include "body_parse/body_parsing.h"
16+
#include "client_ip.h"
1617
#include "collection.h"
1718
#include "ddwaf_obj.h"
1819
#include "header_tags.h"
@@ -679,7 +680,12 @@ std::optional<BlockSpecification> Context::run_waf_start(
679680
static const std::string_view libddwaf_version{ddwaf_get_version()};
680681
span.set_tag("_dd.appsec.waf.version", libddwaf_version);
681682

682-
ddwaf_object *data = collect_request_data(req, memres_);
683+
dnsec::ClientIp ip_resolver{dnsec::Library::custom_ip_header(), req};
684+
auto client_ip = ip_resolver.resolve();
685+
686+
ddwaf_object *data = collect_request_data(req, client_ip, memres_);
687+
688+
client_ip_ = std::move(client_ip);
683689

684690
ddwaf_result result;
685691
auto code =
@@ -1632,6 +1638,7 @@ void Context::do_on_main_log_request(ngx_http_request_t &request,
16321638

16331639
set_header_tags(has_matches(), request, span);
16341640
report_matches(request, span);
1641+
report_client_ip(span);
16351642
}
16361643

16371644
bool Context::has_matches() const noexcept { return !results_.empty(); }
@@ -1645,4 +1652,12 @@ void Context::report_matches(ngx_http_request_t &request, dd::Span &span) {
16451652
results_.clear();
16461653
}
16471654

1655+
void Context::report_client_ip(dd::Span &span) const {
1656+
if (!client_ip_) {
1657+
return;
1658+
}
1659+
1660+
span.set_tag("http.client_ip"sv, *client_ip_);
1661+
}
1662+
16481663
} // namespace datadog::nginx::security

src/security/context.h

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class Context {
9898

9999
bool has_matches() const noexcept;
100100
void report_matches(ngx_http_request_t &request, dd::Span &span);
101+
void report_client_ip(dd::Span &span) const;
101102

102103
enum class stage {
103104
/* Set on on_request_start (NGX_HTTP_ACCESS_PHASE) if there's no thread
@@ -191,6 +192,7 @@ class Context {
191192
std::vector<OwnedDdwafResult> results_;
192193
OwnedDdwafContext ctx_{nullptr};
193194
DdwafMemres memres_;
195+
std::optional<std::string> client_ip_;
194196

195197
static inline constexpr std::size_t kMaxFilterData = 40 * 1024;
196198
static inline constexpr std::size_t kDefaultMaxSavedOutputData = 256 * 1024;

src/security/library.h

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
#include <atomic>
66
#include <memory>
7-
#include <string>
87
#include <string_view>
98

109
#include "../datadog_conf.h"

test/cases/sec_addresses/test_sec_addresses.py

+18
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,24 @@ def test_client_ip_13(self):
347347
result['triggers'][0]['rule_matches'][0]['parameters'][0]['value'],
348348
'fe80::1')
349349

350+
def test_client_ip_meta(self):
351+
status, _, _ = self.orch.send_nginx_http_request(
352+
'/http', 80, {'cf-connecting-ip': '10.10.10.10'})
353+
self.assertEqual(status, 200)
354+
355+
self.orch.reload_nginx()
356+
log_lines = self.orch.sync_service("agent")
357+
traces = [
358+
json.loads(line) for line in log_lines if line.startswith("[[{")
359+
]
360+
found = any(
361+
span[0].get("meta", {}).get("http.client_ip") == "10.10.10.10"
362+
for trace in traces for span in trace)
363+
364+
if not found:
365+
self.fail("No trace found with http.client_ip=10.10.10.10 in " +
366+
str(traces))
367+
350368
def test_client_ip_prio_1(self):
351369
result = self.do_request_headers({
352370
'x-real-ip': '8.8.8.8',

0 commit comments

Comments
 (0)