From c09f2e286b8486272086d96f6a9846b20fa14520 Mon Sep 17 00:00:00 2001 From: Brad Keryan Date: Tue, 10 Sep 2024 16:51:53 -0500 Subject: [PATCH] Enable Bandit security linter (#633) * nidaqmx: Add a dependency on the Bandit security linter * nidaqmx: Update poetry.lock * nidaqmx: Address or acknowledge potential security issues * github: Run Bandit in build.yml --- .github/workflows/build.yml | 2 + generated/nidaqmx/_install_daqmx.py | 29 +++-- poetry.lock | 172 +++++++++++++++++++++++++++- pyproject.toml | 6 + src/handwritten/_install_daqmx.py | 29 +++-- 5 files changed, 221 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6f56ac2..9039ae52 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,8 @@ jobs: run: poetry run mypy - name: Run mypy (Windows) run: poetry run mypy --platform win32 + - name: Run Bandit security checks + run: poetry run bandit -c pyproject.toml -r generated/nidaqmx - name: Generate ni-daqmx files run: | rm -fr generated/nidaqmx diff --git a/generated/nidaqmx/_install_daqmx.py b/generated/nidaqmx/_install_daqmx.py index fea3f391..18d12714 100644 --- a/generated/nidaqmx/_install_daqmx.py +++ b/generated/nidaqmx/_install_daqmx.py @@ -7,13 +7,14 @@ import pathlib import re import shutil -import subprocess +import subprocess # nosec: B404 import sys import tempfile import traceback import requests import zipfile from typing import Generator, List, Optional, Tuple +from urllib.parse import urlparse import click @@ -30,6 +31,7 @@ _logger = logging.getLogger(__name__) METADATA_FILE = "_installer_metadata.json" +_NETWORK_TIMEOUT_IN_SECONDS = 60 def _parse_version(version: str) -> Tuple[int, ...]: @@ -96,7 +98,8 @@ def _get_daqmx_installed_version() -> Optional[str]: _logger.debug("Checking for installed NI-DAQmx version") commands_info = LINUX_COMMANDS[distribution] query_command = commands_info.get_daqmx_version - query_output = subprocess.run(query_command, stdout=subprocess.PIPE, text=True).stdout + # Run the package query command defined by _linux_installation_commands.py. + query_output = subprocess.run(query_command, stdout=subprocess.PIPE, text=True).stdout # nosec: B603 if distribution == "ubuntu": version_match = re.search(r"ii\s+ni-daqmx\s+(\d+\.\d+\.\d+)", query_output) @@ -233,16 +236,17 @@ def _install_daqmx_driver_windows_core(download_url: str) -> None: Download and launch NI-DAQmx Driver installation in an interactive mode """ - temp_file = None + _validate_download_url(download_url) try: with _multi_access_temp_file() as temp_file: _logger.info("Downloading Driver to %s", temp_file) - response = requests.get(download_url) + response = requests.get(download_url, timeout=_NETWORK_TIMEOUT_IN_SECONDS) response.raise_for_status() with open(temp_file, 'wb') as f: f.write(response.content) _logger.info("Installing NI-DAQmx") - subprocess.run([temp_file], shell=True, check=True) + # Run the installer that we just downloaded from https://download.ni.com. + subprocess.run([temp_file], shell=True, check=True) # nosec: B602 except subprocess.CalledProcessError as e: _logger.info("Failed to install NI-DAQmx driver.", exc_info=True) raise click.ClickException( @@ -262,10 +266,11 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None: """ if sys.platform.startswith("linux"): + _validate_download_url(download_url) try: with _multi_access_temp_file(suffix=".zip") as temp_file: _logger.info("Downloading Driver to %s", temp_file) - response = requests.get(download_url) + response = requests.get(download_url, timeout=_NETWORK_TIMEOUT_IN_SECONDS) response.raise_for_status() with open(temp_file, 'wb') as f: f.write(response.content) @@ -283,7 +288,10 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None: directory_to_extract_to, distro.id(), distro.version(), release ): print("\nRunning command:", " ".join(command)) - subprocess.run(command, check=True) + # Run the commands defined in + # _linux_installation_commands.py to install the package + # that we just downloaded from https://download.ni.com. + subprocess.run(command, check=True) # nosec: B603 # Check if the installation was successful if not _get_daqmx_installed_version(): @@ -312,6 +320,13 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None: raise NotImplementedError("This function is only supported on Linux.") +def _validate_download_url(download_url: str) -> None: + """Velidate that the download URL uses https and points to a trusted site.""" + parsed_url = urlparse(download_url) + if parsed_url.scheme != "https" or parsed_url.netloc != "download.ni.com": + raise click.ClickException(f"Unsupported download URL: {download_url}") + + def _ask_user_confirmation(user_message: str) -> bool: """ Prompt for user confirmation diff --git a/poetry.lock b/poetry.lock index e3f5b452..d4537e55 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -56,6 +56,31 @@ files = [ [package.extras] tzdata = ["tzdata"] +[[package]] +name = "bandit" +version = "1.7.9" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, + {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" +tomli = {version = ">=1.1.0", optional = true, markers = "python_version < \"3.11\" and extra == \"toml\""} + +[package.extras] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0)"] +yaml = ["PyYAML"] + [[package]] name = "black" version = "24.4.2" @@ -1069,6 +1094,30 @@ babel = ["Babel"] lingua = ["lingua"] testing = ["pytest"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "2.1.5" @@ -1202,6 +1251,17 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mypy" version = "1.10.0" @@ -1436,6 +1496,17 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "pbr" +version = "6.0.0" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, + {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, +] + [[package]] name = "pep8-naming" version = "0.14.1" @@ -1652,7 +1723,7 @@ files = [ name = "pygments" version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, @@ -1802,6 +1873,68 @@ files = [ {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + [[package]] name = "requests" version = "2.32.3" @@ -1823,6 +1956,25 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "setuptools" version = "70.0.0" @@ -2017,6 +2169,20 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] +[[package]] +name = "stevedore" +version = "5.3.0" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, + {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, +] + +[package.dependencies] +pbr = ">=2.0.0" + [[package]] name = "toml" version = "0.10.2" @@ -2190,4 +2356,4 @@ grpc = ["grpcio", "protobuf"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "428e77bd486a39d56ff6c291a5f34bd24dd82d0ec105473d96a65af97d4b8e20" +content-hash = "0d1cebfae61112658e603ee33434e513607b0dfda5d602b1d0f7c9d132a3348b" diff --git a/pyproject.toml b/pyproject.toml index 04a7f642..d903b8be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,7 @@ matplotlib = {version=">=3.9.0", python = ">=3.9"} nptdms = ">=1.9.0" [tool.poetry.group.lint.dependencies] +bandit = { version = ">=1.7", extras = ["toml"] } ni-python-styleguide = ">=0.4.1" mypy = ">=1.0" types-protobuf = "^4.21" @@ -149,3 +150,8 @@ module = [ "nidaqmx.*", ] ignore_missing_imports = true + +[tool.bandit] +skips = [ + "B101", # assert_used +] diff --git a/src/handwritten/_install_daqmx.py b/src/handwritten/_install_daqmx.py index fea3f391..18d12714 100644 --- a/src/handwritten/_install_daqmx.py +++ b/src/handwritten/_install_daqmx.py @@ -7,13 +7,14 @@ import pathlib import re import shutil -import subprocess +import subprocess # nosec: B404 import sys import tempfile import traceback import requests import zipfile from typing import Generator, List, Optional, Tuple +from urllib.parse import urlparse import click @@ -30,6 +31,7 @@ _logger = logging.getLogger(__name__) METADATA_FILE = "_installer_metadata.json" +_NETWORK_TIMEOUT_IN_SECONDS = 60 def _parse_version(version: str) -> Tuple[int, ...]: @@ -96,7 +98,8 @@ def _get_daqmx_installed_version() -> Optional[str]: _logger.debug("Checking for installed NI-DAQmx version") commands_info = LINUX_COMMANDS[distribution] query_command = commands_info.get_daqmx_version - query_output = subprocess.run(query_command, stdout=subprocess.PIPE, text=True).stdout + # Run the package query command defined by _linux_installation_commands.py. + query_output = subprocess.run(query_command, stdout=subprocess.PIPE, text=True).stdout # nosec: B603 if distribution == "ubuntu": version_match = re.search(r"ii\s+ni-daqmx\s+(\d+\.\d+\.\d+)", query_output) @@ -233,16 +236,17 @@ def _install_daqmx_driver_windows_core(download_url: str) -> None: Download and launch NI-DAQmx Driver installation in an interactive mode """ - temp_file = None + _validate_download_url(download_url) try: with _multi_access_temp_file() as temp_file: _logger.info("Downloading Driver to %s", temp_file) - response = requests.get(download_url) + response = requests.get(download_url, timeout=_NETWORK_TIMEOUT_IN_SECONDS) response.raise_for_status() with open(temp_file, 'wb') as f: f.write(response.content) _logger.info("Installing NI-DAQmx") - subprocess.run([temp_file], shell=True, check=True) + # Run the installer that we just downloaded from https://download.ni.com. + subprocess.run([temp_file], shell=True, check=True) # nosec: B602 except subprocess.CalledProcessError as e: _logger.info("Failed to install NI-DAQmx driver.", exc_info=True) raise click.ClickException( @@ -262,10 +266,11 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None: """ if sys.platform.startswith("linux"): + _validate_download_url(download_url) try: with _multi_access_temp_file(suffix=".zip") as temp_file: _logger.info("Downloading Driver to %s", temp_file) - response = requests.get(download_url) + response = requests.get(download_url, timeout=_NETWORK_TIMEOUT_IN_SECONDS) response.raise_for_status() with open(temp_file, 'wb') as f: f.write(response.content) @@ -283,7 +288,10 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None: directory_to_extract_to, distro.id(), distro.version(), release ): print("\nRunning command:", " ".join(command)) - subprocess.run(command, check=True) + # Run the commands defined in + # _linux_installation_commands.py to install the package + # that we just downloaded from https://download.ni.com. + subprocess.run(command, check=True) # nosec: B603 # Check if the installation was successful if not _get_daqmx_installed_version(): @@ -312,6 +320,13 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None: raise NotImplementedError("This function is only supported on Linux.") +def _validate_download_url(download_url: str) -> None: + """Velidate that the download URL uses https and points to a trusted site.""" + parsed_url = urlparse(download_url) + if parsed_url.scheme != "https" or parsed_url.netloc != "download.ni.com": + raise click.ClickException(f"Unsupported download URL: {download_url}") + + def _ask_user_confirmation(user_message: str) -> bool: """ Prompt for user confirmation