Skip to content

Commit 7476225

Browse files
authored
chore(iast): packages tests (#7856)
This PR introduces base structure to build packages tests. ## 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/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## 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. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that.
1 parent c610103 commit 7476225

File tree

9 files changed

+130
-17
lines changed

9 files changed

+130
-17
lines changed

tests/appsec/integrations/app.py renamed to tests/appsec/app.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" This Flask application is imported on tests.appsec.appsec_utils.gunicorn_server
22
"""
3+
34
import subprocess # nosec
45

56
from flask import Flask
@@ -8,9 +9,11 @@
89

910

1011
import ddtrace.auto # noqa: F401 # isort: skip
12+
from tests.appsec.iast_packages.packages.pkg_requests import pkg_requests
1113

1214

1315
app = Flask(__name__)
16+
app.register_blueprint(pkg_requests)
1417

1518

1619
@app.route("/")
@@ -43,4 +46,4 @@ def iast_cmdi_vulnerability():
4346

4447

4548
if __name__ == "__main__":
46-
app.run(debug=True, port=8000)
49+
app.run(debug=False, port=8000)

tests/appsec/appsec_utils.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def _build_env():
2424

2525
@contextmanager
2626
def gunicorn_server(appsec_enabled="true", remote_configuration_enabled="true", tracer_enabled="true", token=None):
27-
cmd = ["gunicorn", "-w", "3", "-b", "0.0.0.0:8000", "tests.appsec.integrations.app:app"]
27+
cmd = ["gunicorn", "-w", "3", "-b", "0.0.0.0:8000", "tests.appsec.app:app"]
2828
yield from appsec_application_server(
2929
cmd,
3030
appsec_enabled=appsec_enabled,
@@ -38,7 +38,7 @@ def gunicorn_server(appsec_enabled="true", remote_configuration_enabled="true",
3838
def flask_server(
3939
appsec_enabled="true", remote_configuration_enabled="true", iast_enabled="false", tracer_enabled="true", token=None
4040
):
41-
cmd = ["python", "tests/appsec/integrations/app.py", "--no-reload"]
41+
cmd = ["python", "tests/appsec/app.py", "--no-reload"]
4242
yield from appsec_application_server(
4343
cmd,
4444
appsec_enabled=appsec_enabled,
@@ -66,6 +66,8 @@ def appsec_application_server(
6666
env["DD_APPSEC_ENABLED"] = appsec_enabled
6767
if iast_enabled is not None and iast_enabled != "false":
6868
env["DD_IAST_ENABLED"] = iast_enabled
69+
env["DD_IAST_REQUEST_SAMPLING"] = "100"
70+
env["_DD_APPSEC_DEDUPLICATION_ENABLED"] = "false"
6971
if tracer_enabled is not None:
7072
env["DD_TRACE_ENABLED"] = tracer_enabled
7173
env["DD_TRACE_AGENT_URL"] = os.environ.get("DD_TRACE_AGENT_URL", "")
@@ -91,6 +93,13 @@ def appsec_application_server(
9193
"\n=== Captured STDERR ===\n%s=== End of captured STDERR ==="
9294
% (server_process.stdout, server_process.stderr)
9395
)
96+
except Exception:
97+
raise AssertionError(
98+
"Server FAILED, see stdout and stderr logs"
99+
"\n=== Captured STDOUT ===\n%s=== End of captured STDOUT ==="
100+
"\n=== Captured STDERR ===\n%s=== End of captured STDERR ==="
101+
% (server_process.stdout, server_process.stderr)
102+
)
94103
# If we run a Gunicorn application, we want to get the child's pid, see test_remoteconfiguration_e2e.py
95104
parent = psutil.Process(server_process.pid)
96105
children = parent.children(recursive=True)

tests/appsec/iast_packages/__init__.py

Whitespace-only changes.

tests/appsec/iast_packages/packages/__init__.py

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""
2+
requests==2.31.0
3+
4+
https://pypi.org/project/requests/
5+
"""
6+
from flask import Blueprint
7+
from flask import request
8+
import requests
9+
10+
from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted
11+
12+
13+
pkg_requests = Blueprint("package_requests", __name__)
14+
15+
16+
@pkg_requests.route("/requests")
17+
def pkg_requests_view():
18+
package_param = request.args.get("package_param")
19+
try:
20+
requests.get("http://" + package_param)
21+
except Exception:
22+
pass
23+
return {"param": package_param, "params_are_tainted": is_pyobject_tainted(package_param)}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
[package_name]==[version]
3+
4+
https://pypi.org/project/[package_name]/
5+
6+
[Description]
7+
"""
8+
from flask import Blueprint, request
9+
from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted
10+
11+
pkg_[package_name] = Blueprint('package_[package_name]', __name__)
12+
13+
14+
@pkg_[package_name].route('/[package_name]')
15+
def pkg_[package_name]_view():+
16+
import [package_name]
17+
package_param = request.args.get("package_param")
18+
19+
[CODE]
20+
21+
return {
22+
"param": package_param,
23+
"params_are_tainted": is_pyobject_tainted(package_param)
24+
}
25+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import json
2+
3+
import pytest
4+
5+
from tests.appsec.appsec_utils import flask_server
6+
7+
8+
class PackageForTesting:
9+
package_name = ""
10+
package_version = ""
11+
url_to_test = ""
12+
expected_param = "test1234"
13+
xfail = False
14+
15+
def __init__(self, name):
16+
self.package_name = name
17+
18+
@property
19+
def url(self):
20+
return f"/{self.package_name}?package_param={self.expected_param}"
21+
22+
def __repr__(self):
23+
return f"{self.package_name}: {self.url_to_test}"
24+
25+
26+
PACKAGES = [
27+
PackageForTesting("requests"),
28+
]
29+
30+
31+
def setup():
32+
for package in PACKAGES:
33+
with flask_server(
34+
iast_enabled="false", tracer_enabled="true", remote_configuration_enabled="false", token=None
35+
) as context:
36+
_, client, pid = context
37+
38+
try:
39+
response = client.get(package.url)
40+
41+
assert response.status_code == 200
42+
content = json.loads(response.content)
43+
assert content["param"] == package.expected_param
44+
assert content["params_are_tainted"] is False
45+
except Exception:
46+
package.xfail = True
47+
48+
49+
@pytest.mark.parametrize("package", PACKAGES)
50+
def test_patched(package):
51+
if package.xfail:
52+
pytest.xfail("Initial test failed for package: {}".format(package))
53+
54+
with flask_server(iast_enabled="true", remote_configuration_enabled="false", token=None) as context:
55+
_, client, pid = context
56+
57+
response = client.get(package.url)
58+
59+
assert response.status_code == 200
60+
content = json.loads(response.content)
61+
assert content["param"] == package.expected_param
62+
assert content["params_are_tainted"] is True

tests/appsec/integrations/test_handlers.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55

66
from tests.appsec.appsec_utils import flask_server
77
from tests.appsec.appsec_utils import gunicorn_server
8-
from tests.utils import flaky
98

109

1110
@pytest.mark.parametrize("appsec_enabled", ("true", "false"))
1211
@pytest.mark.parametrize("tracer_enabled", ("true", "false"))
1312
@pytest.mark.parametrize("server", ((gunicorn_server, flask_server)))
14-
@flaky(until=1704067200)
1513
def test_when_appsec_reads_chunked_requests(appsec_enabled, tracer_enabled, server):
1614
def read_in_chunks(filepath, chunk_size=1024):
1715
file_object = open(filepath, "rb")
@@ -73,7 +71,6 @@ def test_corner_case_when_appsec_reads_chunked_request_with_no_body(appsec_enabl
7371
@pytest.mark.parametrize("appsec_enabled", ("true", "false"))
7472
@pytest.mark.parametrize("tracer_enabled", ("true", "false"))
7573
@pytest.mark.parametrize("server", ((gunicorn_server, flask_server)))
76-
@flaky(until=1704067200)
7774
def test_when_appsec_reads_empty_body_no_hang(appsec_enabled, tracer_enabled, server):
7875
"""A bug was detected when running a Flask application locally
7976

tests/appsec/integrations/test_psycopg2.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import psycopg2.extensions as ext
2-
import pytest
32

3+
from ddtrace.appsec._iast import oce
4+
from ddtrace.appsec._iast._taint_tracking import OriginType
5+
from ddtrace.appsec._iast._taint_tracking import create_context
6+
from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted
7+
from ddtrace.appsec._iast._taint_utils import LazyTaintList
48
from tests.utils import override_global_config
59

610

7-
try:
8-
from ddtrace.appsec._iast import oce
9-
from ddtrace.appsec._iast._taint_tracking import OriginType
10-
from ddtrace.appsec._iast._taint_tracking import create_context
11-
from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted
12-
from ddtrace.appsec._iast._taint_utils import LazyTaintList
13-
except (ImportError, AttributeError):
14-
pytest.skip("IAST not supported for this Python version", allow_module_level=True)
15-
16-
1711
def setup():
1812
create_context()
1913
oce._enabled = True

0 commit comments

Comments
 (0)