Skip to content

Commit

Permalink
chore(asm): new smoke + patch + e2e package tests (#9529)
Browse files Browse the repository at this point in the history
## Description

Adds a lot more end to end test to the IAST package tests basically
covering almost all of the top 100 modules except the ones that require
some remote service (S3, Azure, etc) that we will need to mock or some
that are not working also unpatched (we will need to investigate these).

Also (needed for this PR):

- Allow to configure a custom port in the `flask_server` appsec fixture
to avoid port conflicts with other appsec tests running in parallel.
- Add the package tests directory to CodeQL ignore file.

## 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] Risks are described (performance impact, potential for breakage,
maintainability)
- [X] Change is maintainable (easy to change, telemetry, documentation)
- [X] [Library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
are followed or label `changelog/no-changelog` is set
- [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))
- [X] If this PR changes the public interface, I've notified
`@DataDog/apm-tees`.

## Reviewer Checklist

- [x] Title is accurate
- [x] All changes are related to the pull request's stated goal
- [x] Description motivates each change
- [x] Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes
- [x] Testing strategy adequately addresses listed risks
- [x] Change is maintainable (easy to change, telemetry, documentation)
- [x] Release note makes sense to a user of the library
- [x] Author has 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)

---------

Signed-off-by: Juanjo Alvarez <[email protected]>
Co-authored-by: Federico Mon <[email protected]>
  • Loading branch information
juanjux and gnufede authored Jun 14, 2024
1 parent 3b81c75 commit 3e2ce17
Show file tree
Hide file tree
Showing 56 changed files with 2,338 additions and 91 deletions.
3 changes: 3 additions & 0 deletions .github/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: "CodeQL config"
paths-ignore:
- 'tests/appsec/iast_packages/packages/**'
1 change: 1 addition & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
config-file: .github/codeql-config.yml

- name: Autobuild
uses: github/codeql-action/autobuild@v2
Expand Down
96 changes: 95 additions & 1 deletion tests/appsec/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" This Flask application is imported on tests.appsec.appsec_utils.gunicorn_server
"""

import os
import subprocess # nosec

from flask import Flask
Expand All @@ -9,62 +10,154 @@


import ddtrace.auto # noqa: F401 # isort: skip
from tests.appsec.iast_packages.packages.pkg_aiohttp import pkg_aiohttp
from tests.appsec.iast_packages.packages.pkg_aiosignal import pkg_aiosignal
from tests.appsec.iast_packages.packages.pkg_annotated_types import pkg_annotated_types
from tests.appsec.iast_packages.packages.pkg_asn1crypto import pkg_asn1crypto
from tests.appsec.iast_packages.packages.pkg_attrs import pkg_attrs
from tests.appsec.iast_packages.packages.pkg_beautifulsoup4 import pkg_beautifulsoup4
from tests.appsec.iast_packages.packages.pkg_cachetools import pkg_cachetools
from tests.appsec.iast_packages.packages.pkg_certifi import pkg_certifi
from tests.appsec.iast_packages.packages.pkg_cffi import pkg_cffi
from tests.appsec.iast_packages.packages.pkg_chartset_normalizer import pkg_chartset_normalizer
from tests.appsec.iast_packages.packages.pkg_click import pkg_click
from tests.appsec.iast_packages.packages.pkg_cryptography import pkg_cryptography
from tests.appsec.iast_packages.packages.pkg_decorator import pkg_decorator
from tests.appsec.iast_packages.packages.pkg_distlib import pkg_distlib
from tests.appsec.iast_packages.packages.pkg_docutils import pkg_docutils
from tests.appsec.iast_packages.packages.pkg_exceptiongroup import pkg_exceptiongroup
from tests.appsec.iast_packages.packages.pkg_filelock import pkg_filelock
from tests.appsec.iast_packages.packages.pkg_frozenlist import pkg_frozenlist
from tests.appsec.iast_packages.packages.pkg_fsspec import pkg_fsspec
from tests.appsec.iast_packages.packages.pkg_google_api_core import pkg_google_api_core
from tests.appsec.iast_packages.packages.pkg_google_api_python_client import pkg_google_api_python_client
from tests.appsec.iast_packages.packages.pkg_idna import pkg_idna
from tests.appsec.iast_packages.packages.pkg_importlib_resources import pkg_importlib_resources
from tests.appsec.iast_packages.packages.pkg_iniconfig import pkg_iniconfig
from tests.appsec.iast_packages.packages.pkg_isodate import pkg_isodate
from tests.appsec.iast_packages.packages.pkg_itsdangerous import pkg_itsdangerous
from tests.appsec.iast_packages.packages.pkg_jinja2 import pkg_jinja2
from tests.appsec.iast_packages.packages.pkg_jmespath import pkg_jmespath
from tests.appsec.iast_packages.packages.pkg_jsonschema import pkg_jsonschema
from tests.appsec.iast_packages.packages.pkg_lxml import pkg_lxml
from tests.appsec.iast_packages.packages.pkg_markupsafe import pkg_markupsafe
from tests.appsec.iast_packages.packages.pkg_more_itertools import pkg_more_itertools
from tests.appsec.iast_packages.packages.pkg_multidict import pkg_multidict
from tests.appsec.iast_packages.packages.pkg_numpy import pkg_numpy
from tests.appsec.iast_packages.packages.pkg_oauthlib import pkg_oauthlib
from tests.appsec.iast_packages.packages.pkg_openpyxl import pkg_openpyxl
from tests.appsec.iast_packages.packages.pkg_packaging import pkg_packaging
from tests.appsec.iast_packages.packages.pkg_pandas import pkg_pandas
from tests.appsec.iast_packages.packages.pkg_pillow import pkg_pillow
from tests.appsec.iast_packages.packages.pkg_platformdirs import pkg_platformdirs
from tests.appsec.iast_packages.packages.pkg_pluggy import pkg_pluggy
from tests.appsec.iast_packages.packages.pkg_psutil import pkg_psutil
from tests.appsec.iast_packages.packages.pkg_pyarrow import pkg_pyarrow
from tests.appsec.iast_packages.packages.pkg_pyasn1 import pkg_pyasn1
from tests.appsec.iast_packages.packages.pkg_pycparser import pkg_pycparser
from tests.appsec.iast_packages.packages.pkg_pydantic import pkg_pydantic
from tests.appsec.iast_packages.packages.pkg_pygments import pkg_pygments
from tests.appsec.iast_packages.packages.pkg_pyjwt import pkg_pyjwt
from tests.appsec.iast_packages.packages.pkg_pynacl import pkg_pynacl
from tests.appsec.iast_packages.packages.pkg_pyopenssl import pkg_pyopenssl
from tests.appsec.iast_packages.packages.pkg_pyparsing import pkg_pyparsing
from tests.appsec.iast_packages.packages.pkg_python_dateutil import pkg_python_dateutil
from tests.appsec.iast_packages.packages.pkg_pytz import pkg_pytz
from tests.appsec.iast_packages.packages.pkg_pyyaml import pkg_pyyaml
from tests.appsec.iast_packages.packages.pkg_requests import pkg_requests
from tests.appsec.iast_packages.packages.pkg_requests_toolbelt import pkg_requests_toolbelt
from tests.appsec.iast_packages.packages.pkg_rsa import pkg_rsa
from tests.appsec.iast_packages.packages.pkg_s3fs import pkg_s3fs
from tests.appsec.iast_packages.packages.pkg_s3transfer import pkg_s3transfer
from tests.appsec.iast_packages.packages.pkg_scipy import pkg_scipy
from tests.appsec.iast_packages.packages.pkg_setuptools import pkg_setuptools
from tests.appsec.iast_packages.packages.pkg_six import pkg_six
from tests.appsec.iast_packages.packages.pkg_soupsieve import pkg_soupsieve
from tests.appsec.iast_packages.packages.pkg_sqlalchemy import pkg_sqlalchemy
from tests.appsec.iast_packages.packages.pkg_tomli import pkg_tomli
from tests.appsec.iast_packages.packages.pkg_tomlkit import pkg_tomlkit
from tests.appsec.iast_packages.packages.pkg_urllib3 import pkg_urllib3
from tests.appsec.iast_packages.packages.pkg_virtualenv import pkg_virtualenv
from tests.appsec.iast_packages.packages.pkg_werkzeug import pkg_werkzeug
from tests.appsec.iast_packages.packages.pkg_wrapt import pkg_wrapt
from tests.appsec.iast_packages.packages.pkg_yarl import pkg_yarl
from tests.appsec.iast_packages.packages.pkg_zipp import pkg_zipp
import tests.appsec.integrations.module_with_import_errors as module_with_import_errors


app = Flask(__name__)
app.register_blueprint(pkg_aiohttp)
app.register_blueprint(pkg_aiosignal)
app.register_blueprint(pkg_annotated_types)
app.register_blueprint(pkg_asn1crypto)
app.register_blueprint(pkg_attrs)
app.register_blueprint(pkg_beautifulsoup4)
app.register_blueprint(pkg_cachetools)
app.register_blueprint(pkg_certifi)
app.register_blueprint(pkg_cffi)
app.register_blueprint(pkg_chartset_normalizer)
app.register_blueprint(pkg_click)
app.register_blueprint(pkg_cryptography)
app.register_blueprint(pkg_decorator)
app.register_blueprint(pkg_distlib)
app.register_blueprint(pkg_docutils)
app.register_blueprint(pkg_exceptiongroup)
app.register_blueprint(pkg_filelock)
app.register_blueprint(pkg_frozenlist)
app.register_blueprint(pkg_fsspec)
app.register_blueprint(pkg_google_api_core)
app.register_blueprint(pkg_google_api_python_client)
app.register_blueprint(pkg_idna)
app.register_blueprint(pkg_importlib_resources)
app.register_blueprint(pkg_iniconfig)
app.register_blueprint(pkg_isodate)
app.register_blueprint(pkg_itsdangerous)
app.register_blueprint(pkg_jinja2)
app.register_blueprint(pkg_jmespath)
app.register_blueprint(pkg_jsonschema)
app.register_blueprint(pkg_lxml)
app.register_blueprint(pkg_markupsafe)
app.register_blueprint(pkg_more_itertools)
app.register_blueprint(pkg_multidict)
app.register_blueprint(pkg_numpy)
app.register_blueprint(pkg_oauthlib)
app.register_blueprint(pkg_openpyxl)
app.register_blueprint(pkg_packaging)
app.register_blueprint(pkg_pandas)
app.register_blueprint(pkg_pillow)
app.register_blueprint(pkg_platformdirs)
app.register_blueprint(pkg_pluggy)
app.register_blueprint(pkg_psutil)
app.register_blueprint(pkg_pyarrow)
app.register_blueprint(pkg_pyasn1)
app.register_blueprint(pkg_pycparser)
app.register_blueprint(pkg_pydantic)
app.register_blueprint(pkg_pygments)
app.register_blueprint(pkg_pyjwt)
app.register_blueprint(pkg_pynacl)
app.register_blueprint(pkg_pyopenssl)
app.register_blueprint(pkg_pyparsing)
app.register_blueprint(pkg_python_dateutil)
app.register_blueprint(pkg_pytz)
app.register_blueprint(pkg_pyyaml)
app.register_blueprint(pkg_requests)
app.register_blueprint(pkg_requests_toolbelt)
app.register_blueprint(pkg_rsa)
app.register_blueprint(pkg_s3fs)
app.register_blueprint(pkg_s3transfer)
app.register_blueprint(pkg_scipy)
app.register_blueprint(pkg_setuptools)
app.register_blueprint(pkg_six)
app.register_blueprint(pkg_soupsieve)
app.register_blueprint(pkg_sqlalchemy)
app.register_blueprint(pkg_tomli)
app.register_blueprint(pkg_tomlkit)
app.register_blueprint(pkg_urllib3)
app.register_blueprint(pkg_virtualenv)
app.register_blueprint(pkg_werkzeug)
app.register_blueprint(pkg_wrapt)
app.register_blueprint(pkg_yarl)
app.register_blueprint(pkg_zipp)


@app.route("/")
Expand Down Expand Up @@ -102,4 +195,5 @@ def iast_ast_patching_import_error():


if __name__ == "__main__":
app.run(debug=False, port=8000)
env_port = os.getenv("FLASK_RUN_PORT", 8000)
app.run(debug=False, port=env_port)
10 changes: 8 additions & 2 deletions tests/appsec/appsec_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@ def gunicorn_server(
tracer_enabled="true",
appsec_standalone_enabled=None,
token=None,
port=8000,
):
cmd = ["gunicorn", "-w", "3", "-b", "0.0.0.0:8000", "tests.appsec.app:app"]
cmd = ["gunicorn", "-w", "3", "-b", "0.0.0.0:%s" % port, "tests.appsec.app:app"]
yield from appsec_application_server(
cmd,
appsec_enabled=appsec_enabled,
appsec_standalone_enabled=appsec_standalone_enabled,
remote_configuration_enabled=remote_configuration_enabled,
tracer_enabled=tracer_enabled,
token=token,
port=port,
)


Expand All @@ -57,6 +59,7 @@ def flask_server(
token=None,
app="tests/appsec/app.py",
env=None,
port=8000,
):
cmd = [python_cmd, app, "--no-reload"]
yield from appsec_application_server(
Expand All @@ -68,6 +71,7 @@ def flask_server(
tracer_enabled=tracer_enabled,
token=token,
env=env,
port=port,
)


Expand All @@ -80,6 +84,7 @@ def appsec_application_server(
appsec_standalone_enabled=None,
token=None,
env=None,
port=8000,
):
env = _build_env(env)
env["DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS"] = "0.5"
Expand All @@ -99,6 +104,7 @@ def appsec_application_server(
if tracer_enabled is not None:
env["DD_TRACE_ENABLED"] = tracer_enabled
env["DD_TRACE_AGENT_URL"] = os.environ.get("DD_TRACE_AGENT_URL", "")
env["FLASK_RUN_PORT"] = str(port)

server_process = subprocess.Popen(
cmd,
Expand All @@ -108,7 +114,7 @@ def appsec_application_server(
start_new_session=True,
)
try:
client = Client("http://0.0.0.0:8000")
client = Client("http://0.0.0.0:%s" % port)

try:
print("Waiting for server to start")
Expand Down
42 changes: 42 additions & 0 deletions tests/appsec/iast_packages/packages/pkg_aiohttp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
aiohttp==3.9.5
https://pypi.org/project/aiohttp/
"""
from flask import Blueprint
from flask import jsonify
from flask import request

from .utils import ResultResponse


pkg_aiohttp = Blueprint("package_aiohttp", __name__)


@pkg_aiohttp.route("/aiohttp")
def pkg_aiohttp_view():
import asyncio

response = ResultResponse(request.args.get("package_param"))

async def fetch(url):
import aiohttp

async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()

try:
url = request.args.get("package_param", "https://example.com")

try:
# Use asyncio to run the async function
result_output = asyncio.run(fetch(url))
except Exception as e:
result_output = f"Error: {str(e)}"

response.result1 = result_output
except Exception as e:
response.result1 = f"Error: {str(e)}"

return jsonify(response.json())
53 changes: 53 additions & 0 deletions tests/appsec/iast_packages/packages/pkg_aiosignal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
aiosignal==1.2.0
https://pypi.org/project/aiosignal/
"""
import asyncio

from flask import Blueprint
from flask import jsonify
from flask import request

from .utils import ResultResponse


pkg_aiosignal = Blueprint("package_aiosignal", __name__)


@pkg_aiosignal.route("/aiosignal")
def pkg_aiosignal_view():
from aiosignal import Signal

response = ResultResponse(request.args.get("package_param"))

async def handler_1(sender, **kwargs):
return "Handler 1 called"

async def handler_2(sender, **kwargs):
return "Handler 2 called"

try:
param_value = request.args.get("package_param", "default_value")

try:
signal = Signal(owner=None)
signal.append(handler_1)
signal.append(handler_2)
signal.freeze() # Freeze the signal to allow sending

async def emit_signal():
results = await signal.send(param_value)
return results

# Use asyncio to run the async function and gather results
results = asyncio.run(emit_signal())
result_output = f"Signal handlers results: {results}"
except Exception as e:
result_output = f"Error: {str(e)}"

response.result1 = result_output
except Exception as e:
response.result1 = f"Error: {str(e)}"

return jsonify(response.json())
42 changes: 42 additions & 0 deletions tests/appsec/iast_packages/packages/pkg_annotated_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
annotated-types==0.7.0
https://pypi.org/project/annotated-types/
"""

from flask import Blueprint
from flask import jsonify
from flask import request

from .utils import ResultResponse


pkg_annotated_types = Blueprint("package_annotated_types", __name__)


@pkg_annotated_types.route("/annotated-types")
def pkg_annotated_types_view():
from typing import Annotated

from annotated_types import Gt

response = ResultResponse(request.args.get("package_param"))

def process_value(value: Annotated[int, Gt(10)]):
return f"Processed value: {value}"

try:
param_value = int(request.args.get("package_param", "15"))

try:
result_output = process_value(param_value)
except ValueError as e:
result_output = f"Error: Value must be greater than 10. {str(e)}"
except Exception as e:
result_output = f"Error: {str(e)}"

response.result1 = result_output.replace("\n", "\\n").replace('"', '\\"').replace("'", "\\'")
except Exception as e:
response.result1 = f"Error: {str(e)}"

return jsonify(response.json())
Loading

0 comments on commit 3e2ce17

Please sign in to comment.