Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add better support for the new RC API #2757

Merged
merged 19 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bd6d97d
add file deletion and state/version tracking
christophe-papazian Jul 16, 2024
d07a3b8
Merge remote-tracking branch 'origin/main' into christophe-papazian/u…
christophe-papazian Jul 16, 2024
7a8b392
fix other tests
christophe-papazian Jul 16, 2024
d474ed8
fix more tests
christophe-papazian Jul 16, 2024
597405b
update documentation
christophe-papazian Jul 16, 2024
1cca94c
update manifests, use plain dict for current_states for replay
christophe-papazian Jul 16, 2024
c83bdaf
fix test
christophe-papazian Jul 16, 2024
7dfd66a
Merge branch 'main' into christophe-papazian/upgrade_rc_api
christophe-papazian Jul 16, 2024
95ff85c
Merge branch 'main' into christophe-papazian/upgrade_rc_api
christophe-papazian Jul 16, 2024
05182dc
Merge remote-tracking branch 'origin/main' into christophe-papazian/u…
christophe-papazian Jul 17, 2024
23f4661
replace commands by a uniq rc state
christophe-papazian Jul 17, 2024
a9ab806
update name
christophe-papazian Jul 17, 2024
0da9edf
Merge remote-tracking branch 'origin/main' into christophe-papazian/u…
christophe-papazian Jul 17, 2024
a9315ec
keep 3.9 syntax compatibility
christophe-papazian Jul 17, 2024
e1c6252
update documentation
christophe-papazian Jul 17, 2024
4d96565
Merge remote-tracking branch 'origin/main' into christophe-papazian/u…
christophe-papazian Jul 17, 2024
7b4a999
fix doc
christophe-papazian Jul 18, 2024
f25bd5f
Merge remote-tracking branch 'origin/main' into christophe-papazian/u…
christophe-papazian Jul 18, 2024
76fba8d
Merge branch 'main' into christophe-papazian/upgrade_rc_api
cbeauchesne Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 35 additions & 19 deletions docs/edit/remote-config.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
The RC API is the official way to interact with remote config. It allows to build and send RC payloads to the library durint setup phase, and send request before/after each state change.

## Building RC payload
## Setting RC configuration files

### Example

``` python
from utils import remote_config


command = remote_config.RemoteConfigCommand(version=1)
# will return the global rc state
rc_state = remote_config.rc_state

config = {
"rules_data": [
Expand All @@ -20,35 +21,47 @@ config = {
]
}

command.add_client_config(f"datadog/2/ASM_DATA-base/ASM_DATA-base/config", config)
rc_state.set_config(f"datadog/2/ASM_DATA-base/ASM_DATA-base/config", config)
# send the state to the tracer and wait for the result to be validated
rc_state.apply()
```

### API

#### class `remote_config.RemoteConfigCommand`
#### object `remote_config.rc_state`

This class will be serialized as a valid `ClientGetConfigsResponse`.

* constructor `__init__(self, version: int, client_configs=(), expires=None)`
* `version: int`: `version` property of `signed` object
* `client_configs`[optional]: list of configuration path / config object.
* `expires` [optional]: expiration date of the config (default `3000-01-01T00:00:00Z`)
* `add_client_config(self, path, config) -> ClientConfig:`
* `set_config(self, path, config) -> rc_state`
* `path`: configuration path
* `config`: config object
* `send()`: send the command using the `send_command` function (see below)

*add one configuration in the state*
* `del_config(self, path) -> rc_state`
* `path`: configuration path

*delete one configuration in the state*
* `reset(self) -> rc_state`

*delete all configurations in the state*
* `apply() -> tracer_state`

*send the state using the `send_state` function (see below).*

*return value can be used to check that the state was correctly applied to the tracer.*

Remember that the state is shared among all tests of a scenario.
You need to reset it and apply at the start of each setup.

## Sending command
## Sending states

### Example

Here is an example a scenario activating/deactivating ASM:

1. the library starts in an initial state where ASM is disabled. This state is validated with an assertion on a request containing an attack : the request should not been caught by ASM
2. Then a RC command is sent to activate ASM
2. Then the RC state is sent to activate ASM
3. another request containing an attack is sent, this one must be reported by ASM
4. A second command is sent to deactivate ASM
4. The state is modified and sent to deactivate ASM
5. a thirst request containing an attack is sent, this last one should not be seen


Expand All @@ -57,6 +70,7 @@ Here is the test code performing that test. Please note variables `activate_ASM_
```python
from utils import weblog, interfaces, scenarios, remote_config

rc_state = remote_config.rc_state

@scenarios.asm_deactivated # in this scenario, ASM is deactivated
class Test_RemoteConfigSequence:
Expand All @@ -67,17 +81,19 @@ class Test_RemoteConfigSequence:
self.first_request = weblog.get("/waf/", headers={"User-Agent": "Arachni/v1"})

# this function will send a RC payload to the library, and wait for a confirmation from the library
self.config_states_activation = activate_ASM_command.send()
self.config_states_activation = rc_state.set_config(path, asm_enabled).apply()
self.second_request = weblog.get("/waf/", headers={"User-Agent": "Arachni/v1"})

# now deactivate the WAF, and check that it does not catch anything
self.config_states_deactivation = deactivate_ASM_command.send()
# now deactivate the WAF by deleting the RC file, and check that it does not catch anything
self.config_states_deactivation = rc_state.del_config(path).apply()
self.third_request = weblog.get("/waf/", headers={"User-Agent": "Arachni/v1"})

def test_asm_switch_on_switch_off():
# first check that both config state are ok, otherwise, next assertions will fail with cryptic messages
assert self.config_states_activation[remote_config.RC_STATE] == remote_config.ApplyState.ACKNOWLEDGED
assert self.config_states_deactivation[remote_config.RC_STATE] == remote_config.ApplyState.ACKNOWLEDGED
# for non empty config, you can also check for details of files
assert self.config_states_activation["asm_features_activation"]["apply_state"] == remote_config.ApplyState.ACKNOWLEDGED, self.config_states_activation
assert self.config_states_deactivation["asm_features_activation"]["apply_state"] == remote_config.ApplyState.ACKNOWLEDGED, self.config_states_deactivation

interfaces.library.assert_no_appsec_event(self.first_request)
interfaces.library.assert_waf_attack(self.second_request)
Expand All @@ -88,7 +104,7 @@ To use this feature, you must use an `EndToEndScenario` with `rc_api_enabled=Tru

### API

#### `send_command(raw_payload, *, wait_for_acknowledged_status: bool = True) -> dict[str, dict[str, Any]]`
#### `send_state(raw_payload, *, wait_for_acknowledged_status: bool = True) -> dict[str, dict[str, Any]]`

Sends a remote config payload to the library and waits for the config to be applied.
Then returns a dictionary with the state of each requested file as returned by the library.
Expand Down
1 change: 1 addition & 0 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ tests/:
Test_AppSecRequestBlocking: v2.25.0
test_runtime_activation.py:
Test_RuntimeActivation: v2.16.0
Test_RuntimeDeactivation: v2.16.0
test_shell_execution.py:
Test_ShellExecution: missing_feature
test_traces.py:
Expand Down
1 change: 1 addition & 0 deletions manifests/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ tests/:
Test_AppSecRequestBlocking: v1.50.0-rc.1
test_runtime_activation.py:
Test_RuntimeActivation: missing_feature
Test_RuntimeDeactivation: missing_feature
test_shell_execution.py:
Test_ShellExecution: missing_feature
test_traces.py:
Expand Down
5 changes: 5 additions & 0 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,11 @@ tests/:
akka-http: v1.22.0
play: v1.22.0
spring-boot-3-native: missing_feature (GraalVM. Tracing support only)
Test_RuntimeDeactivation:
'*': v0.115.0
akka-http: v1.22.0
play: v1.22.0
spring-boot-3-native: missing_feature (GraalVM. Tracing support only)
test_shell_execution.py:
Test_ShellExecution:
'*': v1.2.0
Expand Down
1 change: 1 addition & 0 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ tests/:
nextjs: missing_feature (can not block by query param in nextjs yet)
test_runtime_activation.py:
Test_RuntimeActivation: *ref_3_9_0
Test_RuntimeDeactivation: *ref_3_9_0
test_shell_execution.py:
Test_ShellExecution: *ref_5_3_0
test_traces.py:
Expand Down
5 changes: 5 additions & 0 deletions manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ tests/:
test_reports.py:
Test_ExtraTagsFromRule: v0.88.0
Test_Info: v0.68.3 # probably 0.68.2, but was flaky
test_request_blocking.py:
Test_AppSecRequestBlocking: missing_feature # missing version
test_runtime_activation.py:
Test_RuntimeActivation: missing_feature # missing version
Test_RuntimeDeactivation: missing_feature # missing version
test_shell_execution.py:
Test_ShellExecution: v0.95.0
test_traces.py:
Expand Down
1 change: 1 addition & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ tests/:
fastapi: v2.4.0.dev1
test_runtime_activation.py:
Test_RuntimeActivation: v2.0.0
Test_RuntimeDeactivation: v2.0.0
test_shell_execution.py:
Test_ShellExecution: missing_feature
test_traces.py:
Expand Down
1 change: 1 addition & 0 deletions manifests/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ tests/:
Test_AppSecRequestBlocking: v1.11.1
test_runtime_activation.py:
Test_RuntimeActivation: missing_feature
Test_RuntimeDeactivation: missing_feature
test_shell_execution.py:
Test_ShellExecution: missing_feature
test_traces.py:
Expand Down
13 changes: 7 additions & 6 deletions tests/appsec/api_security/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class BaseAppsecApiSecurityRcTest:

def setup_scenario(self):
if BaseAppsecApiSecurityRcTest.states is None:
command = remote_config.RemoteConfigCommand(version=2)
command.add_client_config(
rc_state = remote_config.rc_state
rc_state.set_config(
"datadog/2/ASM/ASM-base/config",
{
"processor_override": [
Expand Down Expand Up @@ -36,7 +36,7 @@ def setup_scenario(self):
],
},
)
command.add_client_config(
rc_state.set_config(
"datadog/2/ASM_DD/ASM_DD-base/config",
{
"version": "2.2",
Expand Down Expand Up @@ -109,7 +109,8 @@ def setup_scenario(self):
"value": {
"operator": "match_regex",
"parameters": {
"regex": "\\b[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*(%40|@)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}\\b",
"regex": "\\b[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*"
"(%40|@)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}\\b",
"options": {"case_sensitive": False, "min_length": 5},
},
},
Expand All @@ -118,9 +119,9 @@ def setup_scenario(self):
],
},
)
command.add_client_config(
rc_state.set_config(
"datadog/2/ASM_FEATURES/ASM_FEATURES-base/config",
{"asm": {"enabled": True}, "api_security": {"request_sample_rate": 1.0}},
)

BaseAppsecApiSecurityRcTest.states = command.send()
BaseAppsecApiSecurityRcTest.states = rc_state.apply()
Loading
Loading