Skip to content

Commit 6302bed

Browse files
chore(asm): keep waf context during request (#5171)
To avoid performance issues, this PR defines a way to keep the same WAF context for all calls to the WAF during a single request. ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/contributing.html#Release-Note-Guidelines) are followed. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Author is aware of the performance implications of this PR as reported in the benchmarks PR comment. ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer is aware of, and discussed the performance implications of this PR as reported in the benchmarks PR comment. --------- Co-authored-by: Juanjo Alvarez Martinez <[email protected]>
1 parent ead1604 commit 6302bed

File tree

2 files changed

+37
-21
lines changed

2 files changed

+37
-21
lines changed

ddtrace/appsec/ddwaf/__init__.py

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def __init__(self, ruleset_map, obfuscation_parameter_key_regexp, obfuscation_pa
8484
self._info = ddwaf_ruleset_info()
8585
self._ruleset_map = ddwaf_object.create_without_limits(ruleset_map)
8686
self._handle = ddwaf_init(self._ruleset_map, ctypes.byref(config), ctypes.byref(self._info))
87+
self._ctx = 0
8788
if not self._handle or self._info.failed:
8889
LOGGER.error(
8990
"DDWAF.__init__: invalid rules\n ruleset: %s\nloaded:%s\nerrors:%s\n",
@@ -118,6 +119,18 @@ def update_rules(self, new_rules):
118119
self._handle = result
119120
return False
120121

122+
def _at_request_start(self):
123+
if self._ctx:
124+
ddwaf_context_destroy(self._ctx)
125+
self._ctx = ddwaf_context_init(self._handle)
126+
if self._ctx == 0:
127+
LOGGER.error("DDWaf failure to create the context")
128+
129+
def _at_request_end(self):
130+
if self._ctx:
131+
ddwaf_context_destroy(self._ctx)
132+
self._ctx = 0
133+
121134
def run(
122135
self, # type: DDWaf
123136
data, # type: DDWafRulesType
@@ -126,33 +139,34 @@ def run(
126139
# type: (...) -> DDWaf_result
127140
start = time.time()
128141

129-
ctx = ddwaf_context_init(self._handle)
130-
if ctx == 0:
131-
LOGGER.error("DDWaf failure to create the context")
142+
if self._ctx == 0:
143+
LOGGER.warning("DDWaf failsafe to create the context")
144+
self._ctx = ddwaf_context_init(self._handle)
145+
146+
if self._ctx == 0:
147+
LOGGER.error("DDWaf failure: no context created")
132148
return DDWaf_result(None, [], 0, (time.time() - start) * 1e6)
133149

150+
result = ddwaf_result()
151+
wrapper = ddwaf_object(data)
152+
error = ddwaf_run(self._ctx, wrapper, ctypes.byref(result), timeout_ms * 1000)
153+
if error < 0:
154+
LOGGER.warning("run DDWAF error: %d\ninput %s\nerror %s", error, wrapper.struct, self.info.errors)
134155
try:
135-
result = ddwaf_result()
136-
wrapper = ddwaf_object(data)
137-
error = ddwaf_run(ctx, wrapper, ctypes.byref(result), timeout_ms * 1000)
138-
if error < 0:
139-
LOGGER.warning("run DDWAF error: %d\ninput %s\nerror %s", error, wrapper.struct, self.info.errors)
140-
try:
141-
return DDWaf_result(
142-
result.data.decode("UTF-8", errors="ignore")
143-
if hasattr(result, "data") and result.data
144-
else None,
145-
[result.actions.array[i].decode("UTF-8", errors="ignore") for i in range(result.actions.size)],
146-
result.total_runtime / 1e3,
147-
(time.time() - start) * 1e6,
148-
)
149-
finally:
150-
ddwaf_result_free(ctypes.byref(result))
156+
return DDWaf_result(
157+
result.data.decode("UTF-8", errors="ignore") if hasattr(result, "data") and result.data else None,
158+
[result.actions.array[i].decode("UTF-8", errors="ignore") for i in range(result.actions.size)],
159+
result.total_runtime / 1e3,
160+
(time.time() - start) * 1e6,
161+
)
151162
finally:
152-
ddwaf_context_destroy(ctx)
163+
ddwaf_result_free(ctypes.byref(result))
153164

154165
def __dealloc__(self):
155-
ddwaf_destroy(self._handle)
166+
if self._ctx:
167+
ddwaf_context_destroy(self._ctx)
168+
if self._handle:
169+
ddwaf_destroy(self._handle)
156170

157171
def version():
158172
# type: () -> text_type

ddtrace/appsec/processor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ def on_span_start(self, span):
195195

196196
if span.span_type != SpanTypes.WEB:
197197
return
198+
self._ddwaf._at_request_start()
198199

199200
peer_ip = _asm_request_context.get_ip()
200201
headers = _asm_request_context.get_headers()
@@ -359,3 +360,4 @@ def on_span_finish(self, span):
359360
if span.get_tag(APPSEC.JSON) is None:
360361
log.debug("metrics waf call")
361362
self._waf_action(span)
363+
self._ddwaf._at_request_end()

0 commit comments

Comments
 (0)