Skip to content

Commit a1b781c

Browse files
fix(asm): rc must not modify rule file specified by env var [backport #5930 to 1.12] (#5984)
Backport of #5930 to 1.12 When DD_APPSEC_RULES file is specified in the env, RC capabilities that update the rules should be disabled. Update two tests (RC capabilities and RC product registration) as regression tests for this fix. ## 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/)). ## 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 has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment.
1 parent 16e386d commit a1b781c

File tree

5 files changed

+51
-16
lines changed

5 files changed

+51
-16
lines changed

ddtrace/appsec/_remoteconfiguration.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from ddtrace import config
55
from ddtrace.appsec._constants import PRODUCTS
66
from ddtrace.appsec.utils import _appsec_rc_features_is_enabled
7+
from ddtrace.appsec.utils import _appsec_rc_file_is_not_static
78
from ddtrace.constants import APPSEC_ENV
89
from ddtrace.internal.logger import get_logger
910
from ddtrace.internal.remoteconfig.client import RemoteConfigCallBack
@@ -54,7 +55,7 @@ def enable_appsec_rc(test_tracer=None):
5455

5556
RemoteConfig.register(PRODUCTS.ASM_FEATURES, asm_features_callback)
5657

57-
if tracer._appsec_enabled:
58+
if tracer._appsec_enabled and _appsec_rc_file_is_not_static():
5859
from ddtrace.internal.remoteconfig import RemoteConfig
5960

6061
RemoteConfig.register(PRODUCTS.ASM_DATA, asm_callback) # IP Blocking
@@ -138,20 +139,22 @@ def _appsec_1click_activation(self, features):
138139
log.debug("Updating ASM Remote Configuration ASM_FEATURES: %s", rc_appsec_enabled)
139140

140141
if rc_appsec_enabled:
141-
asm_dd_callback = RCASMDDCallBack(self.tracer)
142-
asm_callback = RCAppSecCallBack(self.tracer)
143-
RemoteConfig.register(PRODUCTS.ASM_DATA, asm_callback) # IP Blocking
144-
RemoteConfig.register(PRODUCTS.ASM, asm_callback) # Exclusion Filters & Custom Rules
145-
RemoteConfig.register(PRODUCTS.ASM_DD, asm_dd_callback) # DD Rules
142+
if _appsec_rc_file_is_not_static():
143+
asm_dd_callback = RCASMDDCallBack(self.tracer)
144+
asm_callback = RCAppSecCallBack(self.tracer)
145+
RemoteConfig.register(PRODUCTS.ASM_DATA, asm_callback) # IP Blocking
146+
RemoteConfig.register(PRODUCTS.ASM, asm_callback) # Exclusion Filters & Custom Rules
147+
RemoteConfig.register(PRODUCTS.ASM_DD, asm_dd_callback) # DD Rules
146148
if not self.tracer._appsec_enabled:
147149
self.tracer.configure(appsec_enabled=True)
148150
else:
149151
config._appsec_enabled = True
150152

151153
else:
152-
RemoteConfig.unregister(PRODUCTS.ASM_DATA)
153-
RemoteConfig.unregister(PRODUCTS.ASM)
154-
RemoteConfig.unregister(PRODUCTS.ASM_DD)
154+
if _appsec_rc_file_is_not_static():
155+
RemoteConfig.unregister(PRODUCTS.ASM_DATA)
156+
RemoteConfig.unregister(PRODUCTS.ASM)
157+
RemoteConfig.unregister(PRODUCTS.ASM_DD)
155158
if self.tracer._appsec_enabled:
156159
self.tracer.configure(appsec_enabled=False)
157160
else:

ddtrace/appsec/processor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from ddtrace.appsec._metrics import _set_waf_updates_metric
2424
from ddtrace.appsec.ddwaf import DDWaf
2525
from ddtrace.appsec.ddwaf import version
26+
from ddtrace.appsec.utils import _appsec_rc_file_is_not_static
2627
from ddtrace.constants import MANUAL_KEEP_KEY
2728
from ddtrace.constants import ORIGIN_KEY
2829
from ddtrace.constants import RUNTIME_FAMILY
@@ -197,6 +198,8 @@ def __attrs_post_init__(self):
197198
def _update_rules(self, new_rules):
198199
# type: (Dict[str, Any]) -> bool
199200
result = False
201+
if not _appsec_rc_file_is_not_static():
202+
return result
200203
try:
201204
result = self._ddwaf.update_rules(new_rules)
202205
_set_waf_updates_metric(self._ddwaf.info)

ddtrace/appsec/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def _appsec_rc_features_is_enabled():
2727
return False
2828

2929

30+
def _appsec_rc_file_is_not_static():
31+
return "DD_APPSEC_RULES" not in os.environ
32+
33+
3034
def _appsec_rc_capabilities(test_tracer=None):
3135
# type: (Optional[Tracer]) -> str
3236
r"""return the bit representation of the composed capabilities in base64
@@ -54,7 +58,7 @@ def _appsec_rc_capabilities(test_tracer=None):
5458
if asbool(os.environ.get("DD_REMOTE_CONFIGURATION_ENABLED", "true")):
5559
if _appsec_rc_features_is_enabled():
5660
value |= 1 << 1 # Enable ASM_ACTIVATION
57-
if tracer._appsec_processor:
61+
if tracer._appsec_processor and _appsec_rc_file_is_not_static():
5862
value |= 1 << 2 # Enable ASM_IP_BLOCKING
5963
value |= 1 << 3 # Enable ASM_DD_RULES
6064
value |= 1 << 4 # Enable ASM_EXCLUSIONS
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
ASM: This fix resolves an issue where the WAF rule file specified by DD_APPSEC_RULES was wrongly
5+
updated and modified by remote config.

tests/appsec/test_remoteconfiguration.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from ddtrace.appsec import _asm_request_context
1313
from ddtrace.appsec._constants import APPSEC
14+
from ddtrace.appsec._constants import DEFAULT
1415
from ddtrace.appsec._constants import PRODUCTS
1516
from ddtrace.appsec._remoteconfiguration import RCAppSecCallBack
1617
from ddtrace.appsec._remoteconfiguration import RCAppSecFeaturesCallBack
@@ -139,16 +140,24 @@ def test_rc_capabilities(rc_enabled, appsec_enabled, capability, tracer):
139140
assert _appsec_rc_capabilities(test_tracer=tracer) == capability
140141

141142

142-
def test_rc_activation_capabilities(tracer, remote_config_worker):
143+
@pytest.mark.parametrize(
144+
"env_rules, expected",
145+
[
146+
({}, "Af4="), # All capabilities
147+
({"DD_APPSEC_RULES": DEFAULT.RULES}, "Ag=="), # Only ASM_FEATURES
148+
],
149+
)
150+
def test_rc_activation_capabilities(tracer, remote_config_worker, env_rules, expected):
143151
env = {"DD_REMOTE_CONFIGURATION_ENABLED": "true"}
152+
env.update(env_rules)
144153
with override_env(env), override_global_config(dict(_appsec_enabled=False, api_version="v0.4")):
145154
rc_config = {"asm": {"enabled": True}}
146155

147156
assert not RemoteConfig._worker
148157

149158
RCAppSecFeaturesCallBack(tracer)(None, rc_config)
150159

151-
assert _appsec_rc_capabilities(test_tracer=tracer) == "Af4=" # All capabilities
160+
assert _appsec_rc_capabilities(test_tracer=tracer) == expected
152161

153162

154163
def test_rc_activation_validate_products(tracer, remote_config_worker):
@@ -162,13 +171,24 @@ def test_rc_activation_validate_products(tracer, remote_config_worker):
162171
assert RemoteConfig._worker._client._products["ASM_DATA"]
163172

164173

165-
def test_rc_activation_check_asm_features_product_disables_rest_of_products(tracer, remote_config_worker):
166-
with override_global_config(dict(_appsec_enabled=True, api_version="v0.4")):
174+
@pytest.mark.parametrize(
175+
"env_rules, expected",
176+
[
177+
({}, True), # All capabilities
178+
({"DD_APPSEC_RULES": DEFAULT.RULES}, False), # Only ASM_FEATURES
179+
],
180+
)
181+
def test_rc_activation_check_asm_features_product_disables_rest_of_products(
182+
tracer, remote_config_worker, env_rules, expected
183+
):
184+
env = {"DD_REMOTE_CONFIGURATION_ENABLED": "true"}
185+
env.update(env_rules)
186+
with override_env(env), override_global_config(dict(_appsec_enabled=True, api_version="v0.4")):
167187
tracer.configure(appsec_enabled=True, api_version="v0.4")
168188
enable_appsec_rc(tracer)
169189

170-
assert RemoteConfig._worker._client._products.get(PRODUCTS.ASM_DATA)
171-
assert RemoteConfig._worker._client._products.get(PRODUCTS.ASM)
190+
assert bool(RemoteConfig._worker._client._products.get(PRODUCTS.ASM_DATA)) is expected
191+
assert bool(RemoteConfig._worker._client._products.get(PRODUCTS.ASM)) is expected
172192
assert RemoteConfig._worker._client._products.get(PRODUCTS.ASM_FEATURES)
173193

174194
RCAppSecFeaturesCallBack(tracer)(None, False)

0 commit comments

Comments
 (0)