diff options
author | Bernát Gábor <bgabor8@bloomberg.net> | 2021-08-02 16:28:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-02 16:28:20 +0100 |
commit | 5e33fda1a40ffb4973de3d607a572891eb3cb2d2 (patch) | |
tree | ff7eb657564d3be61bf77387fa460b6fdbf04139 /src | |
parent | 7e1950cc4521c3ccb9b7b0bb7a357bc024f51725 (diff) | |
download | tox-git-5e33fda1a40ffb4973de3d607a572891eb3cb2d2.tar.gz |
Add support for allowlist_externals (#2134)
* Add support for allowlist_externals
Signed-off-by: Bernát Gábor <gaborjbernat@gmail.com>
* Apply suggestions from code review
Co-authored-by: Jürgen Gmach <juergen.gmach@googlemail.com>
Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
Co-authored-by: Jürgen Gmach <juergen.gmach@googlemail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/tox/execute/local_sub_process/__init__.py | 15 | ||||
-rw-r--r-- | src/tox/execute/request.py | 13 | ||||
-rw-r--r-- | src/tox/tox_env/api.py | 14 | ||||
-rw-r--r-- | src/tox/util/spinner.py | 2 |
4 files changed, 39 insertions, 5 deletions
diff --git a/src/tox/execute/local_sub_process/__init__.py b/src/tox/execute/local_sub_process/__init__.py index 5fb21d4b..7b8177b3 100644 --- a/src/tox/execute/local_sub_process/__init__.py +++ b/src/tox/execute/local_sub_process/__init__.py @@ -1,4 +1,5 @@ """Execute that runs on local file system via subprocess-es""" +import fnmatch import logging import os import shutil @@ -8,6 +9,8 @@ from subprocess import DEVNULL, PIPE, TimeoutExpired from types import TracebackType from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type +from tox.tox_env.errors import Fail + from ..api import Execute, ExecuteInstance, ExecuteStatus from ..request import ExecuteRequest, StdinSource from ..stream import SyncWrite @@ -176,10 +179,20 @@ class LocalSubProcessExecuteInstance(ExecuteInstance): @property def cmd(self) -> Sequence[str]: if self._cmd is None: - executable = shutil.which(self.request.cmd[0], path=self.request.env["PATH"]) + base = self.request.cmd[0] + executable = shutil.which(base, path=self.request.env["PATH"]) if executable is None: cmd = self.request.cmd # if failed to find leave as it is else: + if self.request.allow is not None: + for allow in self.request.allow: + # 1. allow matches just the original name of the executable + # 2. allow matches the entire resolved path + if fnmatch.fnmatch(self.request.cmd[0], allow) or fnmatch.fnmatch(executable, allow): + break + else: + msg = f"{base} (resolves to {executable})" if base == executable else base + raise Fail(f"{msg} is not allowed, use allowlist_externals to allow it") # else use expanded format cmd = [executable, *self.request.cmd[1:]] self._cmd = cmd diff --git a/src/tox/execute/request.py b/src/tox/execute/request.py index 8cccbf9a..91d2e996 100644 --- a/src/tox/execute/request.py +++ b/src/tox/execute/request.py @@ -2,7 +2,7 @@ import sys from enum import Enum from pathlib import Path -from typing import Dict, List, Sequence, Union +from typing import Dict, List, Optional, Sequence, Union class StdinSource(Enum): @@ -20,7 +20,13 @@ class ExecuteRequest: """Defines a commands execution request""" def __init__( - self, cmd: Sequence[Union[str, Path]], cwd: Path, env: Dict[str, str], stdin: StdinSource, run_id: str + self, + cmd: Sequence[Union[str, Path]], + cwd: Path, + env: Dict[str, str], + stdin: StdinSource, + run_id: str, + allow: Optional[List[str]] = None, ) -> None: """ Create a new execution request. @@ -38,6 +44,9 @@ class ExecuteRequest: self.env = env #: the environment variables to use self.stdin = stdin #: the type of standard input interaction allowed self.run_id = run_id #: an id to identify this run + if allow is not None and "*" in allow: + allow = None # if we allow everything we can just disable the check + self.allow = allow @property def shell_cmd(self) -> str: diff --git a/src/tox/tox_env/api.py b/src/tox/tox_env/api.py index 46709b23..83163df5 100644 --- a/src/tox/tox_env/api.py +++ b/src/tox/tox_env/api.py @@ -128,6 +128,12 @@ class ToxEnv(ABC): default=False, desc="always recreate virtual environment if this option is true, otherwise leave it up to tox", ) + self.conf.add_config( + keys=["allowlist_externals", "whitelist_externals"], + of_type=List[str], + default=[], + desc="external command glob to allow calling", + ) @property def env_dir(self) -> Path: @@ -293,6 +299,12 @@ class ToxEnv(ABC): result = self._make_path() self._env_vars["PATH"] = result + @property + def _allow_externals(self) -> List[str]: + result: List[str] = [f"{i}{os.sep}*" for i in self._paths] + result.extend(i.strip() for i in self.conf["allowlist_externals"]) + return result + def _make_path(self) -> str: values = dict.fromkeys(str(i) for i in self._paths) values.update(dict.fromkeys(os.environ.get("PATH", "").split(os.pathsep))) @@ -338,7 +350,7 @@ class ToxEnv(ABC): cwd = self.core["tox_root"] if show is None: show = self.options.verbosity > 3 - request = ExecuteRequest(cmd, cwd, self._environment_variables, stdin, run_id) + request = ExecuteRequest(cmd, cwd, self._environment_variables, stdin, run_id, allow=self._allow_externals) if _CWD == request.cwd: repr_cwd = "" else: diff --git a/src/tox/util/spinner.py b/src/tox/util/spinner.py index 78edc8fa..ca2dbb06 100644 --- a/src/tox/util/spinner.py +++ b/src/tox/util/spinner.py @@ -93,7 +93,7 @@ class Spinner: self.render_frame() self._stop_spinner = threading.Event() self._spinner_thread = threading.Thread(target=self.render) - self._spinner_thread.setDaemon(True) + self._spinner_thread.daemon = True self._spinner_thread.start() return self |