|
15 | 15 | from ddtrace.internal import _context |
16 | 16 | from ddtrace.internal.logger import get_logger |
17 | 17 | from ddtrace.internal.processor import SpanProcessor |
| 18 | +from ddtrace.internal.rate_limiter import RateLimiter |
18 | 19 |
|
19 | 20 |
|
20 | 21 | if TYPE_CHECKING: |
|
24 | 25 |
|
25 | 26 | ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) |
26 | 27 | DEFAULT_RULES = os.path.join(ROOT_DIR, "rules.json") |
| 28 | +DEFAULT_TRACE_RATE_LIMIT = 100 |
| 29 | +DEFAULT_WAF_TIMEOUT = 20 # ms |
27 | 30 |
|
28 | 31 | log = get_logger(__name__) |
29 | 32 |
|
@@ -81,12 +84,24 @@ def _set_headers(span, headers): |
81 | 84 | span._set_str_tag(_normalize_tag_name("request", k), headers[k]) |
82 | 85 |
|
83 | 86 |
|
| 87 | +def _get_rate_limiter(): |
| 88 | + # type: () -> RateLimiter |
| 89 | + return RateLimiter(int(os.getenv("DD_APPSEC_TRACE_RATE_LIMIT", DEFAULT_TRACE_RATE_LIMIT))) |
| 90 | + |
| 91 | + |
| 92 | +def _get_waf_timeout(): |
| 93 | + # type: () -> int |
| 94 | + return int(os.getenv("DD_APPSEC_WAF_TIMEOUT", DEFAULT_WAF_TIMEOUT)) |
| 95 | + |
| 96 | + |
84 | 97 | @attr.s(eq=False) |
85 | 98 | class AppSecSpanProcessor(SpanProcessor): |
86 | 99 |
|
87 | 100 | rules = attr.ib(type=str, factory=get_rules) |
88 | 101 | _ddwaf = attr.ib(type=DDWaf, default=None) |
89 | 102 | _addresses_to_keep = attr.ib(type=Set[str], factory=set) |
| 103 | + _rate_limiter = attr.ib(type=RateLimiter, factory=_get_rate_limiter) |
| 104 | + _waf_timeout = attr.ib(type=int, factory=_get_waf_timeout) |
90 | 105 |
|
91 | 106 | @property |
92 | 107 | def enabled(self): |
@@ -188,8 +203,14 @@ def on_span_finish(self, span): |
188 | 203 | data[_Addresses.SERVER_RESPONSE_HEADERS_NO_COOKIES] = _no_cookies(response_headers) |
189 | 204 |
|
190 | 205 | log.debug("[DDAS-001-00] Executing AppSec In-App WAF with parameters: %s", data) |
191 | | - res = self._ddwaf.run(data) # res is a serialized json |
| 206 | + res = self._ddwaf.run(data, self._waf_timeout) # res is a serialized json |
192 | 207 | if res is not None: |
| 208 | + # We run the rate limiter only if there is an attack, its goal is to limit the number of collected asm |
| 209 | + # events |
| 210 | + allowed = self._rate_limiter.is_allowed(span.start_ns) |
| 211 | + if not allowed: |
| 212 | + # TODO: add metric collection to keep an eye (when it's name is clarified) |
| 213 | + return |
193 | 214 | if _Addresses.SERVER_REQUEST_HEADERS_NO_COOKIES in data: |
194 | 215 | _set_headers(span, data[_Addresses.SERVER_REQUEST_HEADERS_NO_COOKIES]) |
195 | 216 | # Partial DDAS-011-00 |
|
0 commit comments