1
1
import functools
2
2
import json
3
3
import re
4
- from typing import TYPE_CHECKING
5
4
from typing import Any
6
5
from typing import Callable
7
6
from typing import Dict
8
7
from typing import List
9
- from typing import Literal # noqa:F401
8
+ from typing import Literal
10
9
from typing import Optional
11
10
from typing import Set
12
11
from typing import Union
13
12
from urllib import parse
14
13
15
14
from ddtrace .appsec ._constants import APPSEC
16
- from ddtrace .appsec ._constants import EXPLOIT_PREVENTION
17
15
from ddtrace .appsec ._constants import SPAN_DATA_NAMES
18
- from ddtrace .appsec ._utils import _observator
16
+ from ddtrace .appsec ._utils import DDWaf_info
17
+ from ddtrace .appsec ._utils import DDWaf_result
18
+ from ddtrace .appsec ._utils import Telemetry_result
19
19
from ddtrace .appsec ._utils import add_context_log
20
20
from ddtrace .appsec ._utils import get_triggers
21
21
from ddtrace .internal import core
26
26
from ddtrace .trace import Span
27
27
28
28
29
- if TYPE_CHECKING :
30
- from ddtrace .appsec ._ddwaf import DDWaf_info
31
- from ddtrace .appsec ._ddwaf import DDWaf_result
32
-
33
29
log = get_logger (__name__ )
34
30
35
31
# Stopgap module for providing ASM context for the blocking features wrapping some contextvars.
42
38
_CONTEXT_CALL : Literal ["context" ] = "context"
43
39
_WAF_CALL : Literal ["waf_run" ] = "waf_run"
44
40
_BLOCK_CALL : Literal ["block" ] = "block"
45
- _TELEMETRY_WAF_RESULTS : Literal ["t_waf_results" ] = "t_waf_results"
46
41
47
42
48
43
GLOBAL_CALLBACKS : Dict [str , List [Callable ]] = {_CONTEXT_CALL : []}
@@ -73,28 +68,10 @@ def __init__(self, span: Optional[Span] = None):
73
68
else :
74
69
self .framework = self .span .name
75
70
self .framework = self .framework .lower ().replace (" " , "_" )
76
- self .waf_info : Optional [Callable [[], " DDWaf_info" ]] = None
71
+ self .waf_info : Optional [Callable [[], DDWaf_info ]] = None
77
72
self .waf_addresses : Dict [str , Any ] = {}
78
73
self .callbacks : Dict [str , Any ] = {_CONTEXT_CALL : []}
79
- self .telemetry : Dict [str , Any ] = {
80
- _TELEMETRY_WAF_RESULTS : {
81
- "blocked" : False ,
82
- "triggered" : False ,
83
- "timeout" : 0 ,
84
- "version" : None ,
85
- "duration" : 0.0 ,
86
- "total_duration" : 0.0 ,
87
- "rasp" : {
88
- "sum_eval" : 0 ,
89
- "duration" : 0.0 ,
90
- "total_duration" : 0.0 ,
91
- "eval" : {t : 0 for _ , t in EXPLOIT_PREVENTION .TYPE },
92
- "match" : {t : 0 for _ , t in EXPLOIT_PREVENTION .TYPE },
93
- "timeout" : {t : 0 for _ , t in EXPLOIT_PREVENTION .TYPE },
94
- },
95
- "truncation" : {"string_length" : [], "container_size" : [], "container_depth" : []},
96
- }
97
- }
74
+ self .telemetry : Telemetry_result = Telemetry_result ()
98
75
self .addresses_sent : Set [str ] = set ()
99
76
self .waf_triggers : List [Dict [str , Any ]] = []
100
77
self .blocked : Optional [Dict [str , Any ]] = None
@@ -183,6 +160,8 @@ def update_span_metrics(span: Span, name: str, value: Union[float, int]) -> None
183
160
184
161
185
162
def flush_waf_triggers (env : ASM_Environment ) -> None :
163
+ from ddtrace .appsec ._metrics import DDWAF_VERSION
164
+
186
165
# Make sure we find a root span to attach the triggers to
187
166
if env .span is None :
188
167
from ddtrace .trace import tracer
@@ -204,35 +183,29 @@ def flush_waf_triggers(env: ASM_Environment) -> None:
204
183
else :
205
184
root_span .set_tag (APPSEC .JSON , json .dumps ({"triggers" : report_list }, separators = ("," , ":" )))
206
185
env .waf_triggers = []
207
- telemetry_results = get_value (_TELEMETRY , _TELEMETRY_WAF_RESULTS )
208
- if telemetry_results :
209
- from ddtrace .appsec ._metrics import DDWAF_VERSION
210
-
211
- root_span .set_tag_str (APPSEC .WAF_VERSION , DDWAF_VERSION )
212
- if telemetry_results ["total_duration" ]:
213
- update_span_metrics (root_span , APPSEC .WAF_DURATION , telemetry_results ["duration" ])
214
- telemetry_results ["duration" ] = 0.0
215
- update_span_metrics (root_span , APPSEC .WAF_DURATION_EXT , telemetry_results ["total_duration" ])
216
- telemetry_results ["total_duration" ] = 0.0
217
- if telemetry_results ["timeout" ]:
218
- update_span_metrics (root_span , APPSEC .WAF_TIMEOUTS , telemetry_results ["timeout" ])
219
- rasp_timeouts = sum (telemetry_results ["rasp" ]["timeout" ].values ())
220
- if rasp_timeouts :
221
- update_span_metrics (root_span , APPSEC .RASP_TIMEOUTS , rasp_timeouts )
222
- if telemetry_results ["rasp" ]["sum_eval" ]:
223
- update_span_metrics (root_span , APPSEC .RASP_DURATION , telemetry_results ["rasp" ]["duration" ])
224
- update_span_metrics (root_span , APPSEC .RASP_DURATION_EXT , telemetry_results ["rasp" ]["total_duration" ])
225
- update_span_metrics (root_span , APPSEC .RASP_RULE_EVAL , telemetry_results ["rasp" ]["sum_eval" ])
226
- if telemetry_results ["truncation" ]["string_length" ]:
227
- root_span .set_metric (APPSEC .TRUNCATION_STRING_LENGTH , max (telemetry_results ["truncation" ]["string_length" ]))
228
- if telemetry_results ["truncation" ]["container_size" ]:
229
- root_span .set_metric (
230
- APPSEC .TRUNCATION_CONTAINER_SIZE , max (telemetry_results ["truncation" ]["container_size" ])
231
- )
232
- if telemetry_results ["truncation" ]["container_depth" ]:
233
- root_span .set_metric (
234
- APPSEC .TRUNCATION_CONTAINER_DEPTH , max (telemetry_results ["truncation" ]["container_depth" ])
235
- )
186
+ telemetry_results : Telemetry_result = env .telemetry
187
+
188
+ root_span .set_tag_str (APPSEC .WAF_VERSION , DDWAF_VERSION )
189
+ if telemetry_results .total_duration :
190
+ update_span_metrics (root_span , APPSEC .WAF_DURATION , telemetry_results .duration )
191
+ telemetry_results .duration = 0.0
192
+ update_span_metrics (root_span , APPSEC .WAF_DURATION_EXT , telemetry_results .total_duration )
193
+ telemetry_results .total_duration = 0.0
194
+ if telemetry_results .timeout :
195
+ update_span_metrics (root_span , APPSEC .WAF_TIMEOUTS , telemetry_results .timeout )
196
+ rasp_timeouts = sum (telemetry_results .rasp .timeout .values ())
197
+ if rasp_timeouts :
198
+ update_span_metrics (root_span , APPSEC .RASP_TIMEOUTS , rasp_timeouts )
199
+ if telemetry_results .rasp .sum_eval :
200
+ update_span_metrics (root_span , APPSEC .RASP_DURATION , telemetry_results .rasp .duration )
201
+ update_span_metrics (root_span , APPSEC .RASP_DURATION_EXT , telemetry_results .rasp .total_duration )
202
+ update_span_metrics (root_span , APPSEC .RASP_RULE_EVAL , telemetry_results .rasp .sum_eval )
203
+ if telemetry_results .truncation .string_length :
204
+ root_span .set_metric (APPSEC .TRUNCATION_STRING_LENGTH , max (telemetry_results .truncation .string_length ))
205
+ if telemetry_results .truncation .container_size :
206
+ root_span .set_metric (APPSEC .TRUNCATION_CONTAINER_SIZE , max (telemetry_results .truncation .container_size ))
207
+ if telemetry_results .truncation .container_depth :
208
+ root_span .set_metric (APPSEC .TRUNCATION_CONTAINER_DEPTH , max (telemetry_results .truncation .container_depth ))
236
209
237
210
238
211
def finalize_asm_env (env : ASM_Environment ) -> None :
@@ -338,7 +311,7 @@ def set_waf_callback(value) -> None:
338
311
set_value (_CALLBACKS , _WAF_CALL , value )
339
312
340
313
341
- def set_waf_info (info : Callable [[], " DDWaf_info" ]) -> None :
314
+ def set_waf_info (info : Callable [[], DDWaf_info ]) -> None :
342
315
env = _get_asm_context ()
343
316
if env is None :
344
317
info_str = add_context_log (log , "appsec.asm_context.warning::set_waf_info::no_active_context" )
@@ -347,7 +320,7 @@ def set_waf_info(info: Callable[[], "DDWaf_info"]) -> None:
347
320
env .waf_info = info
348
321
349
322
350
- def call_waf_callback (custom_data : Optional [Dict [str , Any ]] = None , ** kwargs ) -> Optional [" DDWaf_result" ]:
323
+ def call_waf_callback (custom_data : Optional [Dict [str , Any ]] = None , ** kwargs ) -> Optional [DDWaf_result ]:
351
324
if not asm_config ._asm_enabled :
352
325
return None
353
326
callback = get_value (_CALLBACKS , _WAF_CALL )
@@ -435,44 +408,53 @@ def asm_request_context_set(
435
408
436
409
def set_waf_telemetry_results (
437
410
rules_version : Optional [str ],
438
- is_triggered : bool ,
439
411
is_blocked : bool ,
440
- is_timeout : bool ,
412
+ waf_results : DDWaf_result ,
441
413
rule_type : Optional [str ],
442
- duration : float ,
443
- total_duration : float ,
444
- truncation : _observator ,
414
+ is_sampled : bool ,
445
415
) -> None :
446
- result = get_value (_TELEMETRY , _TELEMETRY_WAF_RESULTS )
416
+ env = _get_asm_context ()
417
+ if env is None :
418
+ return
419
+ result : Telemetry_result = env .telemetry
420
+ is_triggered = bool (waf_results .data )
447
421
from ddtrace .appsec ._metrics import _report_waf_truncations
448
422
449
- _report_waf_truncations (truncation )
450
- if result is not None :
451
- for key in ["container_size" , "container_depth" , "string_length" ]:
452
- res = getattr (truncation , key )
453
- if isinstance (res , int ):
454
- result ["truncation" ][key ].append (res )
455
- if rule_type is None :
456
- # Request Blocking telemetry
457
- result ["triggered" ] |= is_triggered
458
- result ["blocked" ] |= is_blocked
459
- result ["timeout" ] += is_timeout
460
- if rules_version is not None :
461
- result ["version" ] = rules_version
462
- result ["duration" ] += duration
463
- result ["total_duration" ] += total_duration
423
+ result .rate_limited |= is_sampled
424
+ if waf_results .return_code :
425
+ if result .error :
426
+ result .error = max (result .error , waf_results .return_code )
464
427
else :
465
- # Exploit Prevention telemetry
466
- result ["rasp" ]["sum_eval" ] += 1
467
- result ["rasp" ]["eval" ][rule_type ] += 1
468
- result ["rasp" ]["match" ][rule_type ] += int (is_triggered )
469
- result ["rasp" ]["timeout" ][rule_type ] += int (is_timeout )
470
- result ["rasp" ]["duration" ] += duration
471
- result ["rasp" ]["total_duration" ] += total_duration
428
+ result .error = waf_results .return_code
429
+ _report_waf_truncations (waf_results .truncation )
430
+ for key in ["container_size" , "container_depth" , "string_length" ]:
431
+ res = getattr (waf_results .truncation , key )
432
+ if isinstance (res , int ):
433
+ getattr (result .truncation , key ).append (res )
434
+ if rule_type is None :
435
+ # Request Blocking telemetry
436
+ result .triggered |= is_triggered
437
+ result .blocked |= is_blocked
438
+ result .timeout += waf_results .timeout
439
+ if rules_version is not None :
440
+ result .version = rules_version
441
+ result .duration += waf_results .runtime
442
+ result .total_duration += waf_results .total_runtime
443
+ else :
444
+ # Exploit Prevention telemetry
445
+ result .rasp .sum_eval += 1
446
+ result .rasp .eval [rule_type ] += 1
447
+ result .rasp .match [rule_type ] += int (is_triggered )
448
+ result .rasp .timeout [rule_type ] += int (waf_results .timeout )
449
+ result .rasp .duration += waf_results .runtime
450
+ result .rasp .total_duration += waf_results .total_runtime
472
451
473
452
474
- def get_waf_telemetry_results () -> Optional [Dict [str , Any ]]:
475
- return get_value (_TELEMETRY , _TELEMETRY_WAF_RESULTS )
453
+ def get_waf_telemetry_results () -> Optional [Telemetry_result ]:
454
+ env = _get_asm_context ()
455
+ if env :
456
+ return env .telemetry
457
+ return None
476
458
477
459
478
460
def store_waf_results_data (data ) -> None :
0 commit comments