From 19f4c58252311693a72aae42cfa9b19b215a1b50 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Tue, 5 Nov 2024 20:21:19 +0100 Subject: [PATCH] chore(iast): restore, update and improve the native import smoke_test (#11278) ## Description The previous smoke test for importing ddtrace IAST native module (both positive and negative routes) was removed on a previous PR. This restores it, adapting it to the new version (now the `_native` module doesn't produce an `ImportError` if `DD_IAST_ENABLED=0` but a warning log message). It will also run both positive and negative tests in a subprocess so the `.so` module doesn't have to be restarted or reloaded, which is not trivial for native modules. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - 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) --------- Signed-off-by: Juanjo Alvarez --- tests/smoke_test.py | 84 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/tests/smoke_test.py b/tests/smoke_test.py index a4fbe3d5a39..cbf5ebc8e61 100644 --- a/tests/smoke_test.py +++ b/tests/smoke_test.py @@ -1,8 +1,9 @@ +import copy +import os from platform import system +import subprocess import sys - -import ddtrace.appsec._ddwaf -import ddtrace.bootstrap.sitecustomize as module +import textwrap def mac_supported_iast_version(): @@ -14,13 +15,74 @@ def mac_supported_iast_version(): return True +# Code need to be run in a separate subprocess to reload since reloading .so files doesn't +# work like normal Python ones +test_native_load_code = """ +import os +import sys +import logging + +log_messages = [] + +class ListHandler(logging.Handler): + def emit(self, record): + log_messages.append(record.getMessage()) + +# the _native module will produce the "IAST not enabled..." message using the root logger (logger.warning) +# so we have to configure the capture handler on the root logger +root_logger = logging.getLogger() +root_logger.addHandler(ListHandler()) +root_logger.setLevel(logging.WARNING) + +try: + from ddtrace.appsec._iast._taint_tracking._native import ops + + if os.environ.get("DD_IAST_ENABLED") == "False": + assert any( + "IAST not enabled but native module is being loaded" in message + for message in log_messages + ) + else: + assert ops + assert len(log_messages) == 0 +except ImportError as e: + assert False, "Importing the native module failed, _native probably not compiled correctly: %s" % str(e) +""" + if __name__ == "__main__": + # ASM IAST smoke test + if sys.version_info >= (3, 6, 0) and system() != "Windows" and mac_supported_iast_version(): + print("Running native IAST module load test...") + test_code = textwrap.dedent(test_native_load_code) + cmd = [sys.executable, "-c", test_code] + orig_env = os.environ.copy() + copied_env = copy.deepcopy(orig_env) + + try: + print("Running native module load test with DD_IAST_ENABLED=False...") + copied_env["DD_IAST_ENABLED"] = "False" + result = subprocess.run(cmd, env=copied_env, capture_output=True, text=True) + assert result.returncode == 0, "Failed with DD_IAST_ENABLED=0: %s, %s" % (result.stdout, result.stderr) + + print("Running native module load test with DD_IAST_ENABLED=True...") + copied_env["DD_IAST_ENABLED"] = "True" + result = subprocess.run(cmd, env=copied_env, capture_output=True, text=True) + assert result.returncode == 0, "Failed with DD_IAST_ENABLED=1: %s, %s" % (result.stdout, result.stderr) + print("IAST module load tests completed successfully") + finally: + os.environ = orig_env + # ASM WAF smoke test - if system() == "Linux": - if not sys.maxsize > 2**32: - # 32-bit linux DDWAF not ready yet. - sys.exit(0) - - ddtrace.appsec._ddwaf.version() - assert ddtrace.appsec._ddwaf._DDWAF_LOADED - assert module.loaded + if system() != "Linux" or sys.maxsize > 2**32: + import ddtrace.appsec._ddwaf + import ddtrace.bootstrap.sitecustomize as module + + print("Running WAF module load test...") + # Proceed with the WAF module load test + ddtrace.appsec._ddwaf.version() + assert ddtrace.appsec._ddwaf._DDWAF_LOADED + assert module.loaded + print("WAF module load test completed successfully") + else: + # Skip the test for 32-bit Linux systems + print("Skipping test, 32-bit DDWAF not ready yet")