autoscan / core /bootstrap.py
Chris4K's picture
Upload 384 files
a2a5bfd verified
Raw History Blame Contribute Delete
12.1 kB
"""Download gitleaks and hadolint binaries into a local bin dir on import."""
import os
import platform
import shutil
import sys
import tarfile
import zipfile
from pathlib import Path
from urllib.parse import urlparse
import requests
from .helpers import have_binary
GITLEAKS_VERSION = "8.18.4"
HADOLINT_VERSION = "2.12.0"
TRIVY_VERSION = "0.57.1"
TRUFFLEHOG_VERSION = "3.82.6"
SYFT_VERSION = "1.14.0"
GRYPE_VERSION = "0.82.0"
_IS_WINDOWS = platform.system() == "Windows"
# Allowlist: only these domains may be used for binary downloads.
_ALLOWED_DOWNLOAD_DOMAINS = frozenset({"github.com", "objects.githubusercontent.com"})
# On Windows put binaries next to the Python executable (venv Scripts/)
# so shutil.which() finds them without PATH manipulation.
# On Linux/macOS use ~/.local/bin as before.
BIN_DIR = (
Path(sys.executable).parent # .venv\Scripts\
if _IS_WINDOWS
else Path.home() / ".local" / "bin" # pragma: no cover
)
def _stream_download(url: str, dest_path: Path, timeout: int = 120) -> None:
"""Download *url* to *dest_path*. Only HTTPS from approved domains is allowed."""
parsed = urlparse(url)
if parsed.scheme != "https":
raise ValueError(f"Only HTTPS downloads are permitted (got: {parsed.scheme}://)")
if parsed.netloc not in _ALLOWED_DOWNLOAD_DOMAINS:
raise ValueError(
f"Download from '{parsed.netloc}' is not allowed. "
f"Permitted domains: {sorted(_ALLOWED_DOWNLOAD_DOMAINS)}"
)
r = requests.get(url, stream=True, timeout=timeout) # noqa: AGENT-026 # nosemgrep: requests-no-timeout
r.raise_for_status()
with open(dest_path, "wb") as f:
for chunk in r.iter_content(64 * 1024):
if chunk:
f.write(chunk)
def install_gitleaks() -> str:
if have_binary("gitleaks"):
return "already installed"
try:
BIN_DIR.mkdir(parents=True, exist_ok=True)
if _IS_WINDOWS:
url = ( # noqa: AGENT-004
f"https://github.com/gitleaks/gitleaks/releases/download/"
f"v{GITLEAKS_VERSION}/gitleaks_{GITLEAKS_VERSION}_windows_x64.zip"
)
zip_path = BIN_DIR / "_gitleaks.zip"
_stream_download(url, zip_path)
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if name.lower() in ("gitleaks.exe", "gitleaks"):
z.extract(name, BIN_DIR)
extracted = BIN_DIR / name
target = BIN_DIR / "gitleaks.exe"
if extracted != target:
extracted.rename(target)
break
zip_path.unlink(missing_ok=True)
else:
url = ( # noqa: AGENT-004
f"https://github.com/gitleaks/gitleaks/releases/download/"
f"v{GITLEAKS_VERSION}/gitleaks_{GITLEAKS_VERSION}_linux_x64.tar.gz"
)
tar_path = BIN_DIR / "_gitleaks.tar.gz"
_stream_download(url, tar_path)
with tarfile.open(tar_path) as tar:
for member in tar.getmembers():
if member.name == "gitleaks":
tar.extract(member, BIN_DIR)
break
(BIN_DIR / "gitleaks").chmod(0o755)
tar_path.unlink(missing_ok=True)
return "installed"
except Exception as e:
return f"install failed: {e}"
def install_hadolint() -> str:
if have_binary("hadolint"):
return "already installed"
try:
BIN_DIR.mkdir(parents=True, exist_ok=True)
if _IS_WINDOWS:
url = ( # noqa: AGENT-004
f"https://github.com/hadolint/hadolint/releases/download/"
f"v{HADOLINT_VERSION}/hadolint-Windows-x86_64.exe"
)
bin_path = BIN_DIR / "hadolint.exe"
else:
url = ( # noqa: AGENT-004
f"https://github.com/hadolint/hadolint/releases/download/"
f"v{HADOLINT_VERSION}/hadolint-Linux-x86_64"
)
bin_path = BIN_DIR / "hadolint"
_stream_download(url, bin_path)
if not _IS_WINDOWS:
bin_path.chmod(0o755)
return "installed"
except Exception as e:
return f"install failed: {e}"
def bootstrap_binaries() -> dict:
"""Install missing binaries and ensure BIN_DIR is on PATH. Idempotent."""
BIN_DIR.mkdir(parents=True, exist_ok=True)
bin_dir_str = str(BIN_DIR)
path = os.environ.get("PATH", "")
if bin_dir_str not in path.split(os.pathsep):
os.environ["PATH"] = bin_dir_str + os.pathsep + path
return {
"gitleaks": install_gitleaks(),
"hadolint": install_hadolint(),
"trivy": install_trivy(),
"trufflehog": install_trufflehog(),
"syft": install_syft(),
"grype": install_grype(),
}
def install_trivy() -> str:
if have_binary("trivy"):
return "already installed"
try:
BIN_DIR.mkdir(parents=True, exist_ok=True)
if _IS_WINDOWS:
url = (
f"https://github.com/aquasecurity/trivy/releases/download/"
f"v{TRIVY_VERSION}/trivy_{TRIVY_VERSION}_windows-64bit.zip"
)
zip_path = BIN_DIR / "_trivy.zip"
_stream_download(url, zip_path)
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if name.lower() in ("trivy.exe", "trivy"):
z.extract(name, BIN_DIR)
extracted = BIN_DIR / name
target = BIN_DIR / "trivy.exe"
if extracted != target:
extracted.rename(target)
break
zip_path.unlink(missing_ok=True)
else:
url = (
f"https://github.com/aquasecurity/trivy/releases/download/"
f"v{TRIVY_VERSION}/trivy_{TRIVY_VERSION}_Linux-64bit.tar.gz"
)
tar_path = BIN_DIR / "_trivy.tar.gz"
_stream_download(url, tar_path)
with tarfile.open(tar_path) as tar:
for member in tar.getmembers():
if member.name == "trivy":
tar.extract(member, BIN_DIR)
break
(BIN_DIR / "trivy").chmod(0o755)
tar_path.unlink(missing_ok=True)
return "installed"
except Exception as e:
return f"install failed: {e}"
def install_trufflehog() -> str:
if have_binary("trufflehog"):
return "already installed"
try:
BIN_DIR.mkdir(parents=True, exist_ok=True)
if _IS_WINDOWS:
url = (
f"https://github.com/trufflesecurity/trufflehog/releases/download/"
f"v{TRUFFLEHOG_VERSION}/trufflehog_{TRUFFLEHOG_VERSION}_windows_amd64.zip"
)
zip_path = BIN_DIR / "_trufflehog.zip"
_stream_download(url, zip_path)
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if "trufflehog" in name.lower() and name.lower().endswith(".exe"):
z.extract(name, BIN_DIR)
extracted = BIN_DIR / name
target = BIN_DIR / "trufflehog.exe"
if extracted != target:
extracted.rename(target)
break
zip_path.unlink(missing_ok=True)
else:
url = (
f"https://github.com/trufflesecurity/trufflehog/releases/download/"
f"v{TRUFFLEHOG_VERSION}/trufflehog_{TRUFFLEHOG_VERSION}_linux_amd64.tar.gz"
)
tar_path = BIN_DIR / "_trufflehog.tar.gz"
_stream_download(url, tar_path)
with tarfile.open(tar_path) as tar:
for member in tar.getmembers():
if "trufflehog" in member.name.lower():
tar.extract(member, BIN_DIR)
extracted = BIN_DIR / member.name
target = BIN_DIR / "trufflehog"
if extracted != target:
extracted.rename(target)
break
(BIN_DIR / "trufflehog").chmod(0o755)
tar_path.unlink(missing_ok=True)
return "installed"
except Exception as e:
return f"install failed: {e}"
def install_syft() -> str:
if have_binary("syft"):
return "already installed"
try:
BIN_DIR.mkdir(parents=True, exist_ok=True)
if _IS_WINDOWS:
url = (
f"https://github.com/anchore/syft/releases/download/"
f"v{SYFT_VERSION}/syft_{SYFT_VERSION}_windows_amd64.zip"
)
zip_path = BIN_DIR / "_syft.zip"
_stream_download(url, zip_path)
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if name.lower() in ("syft.exe", "syft"):
z.extract(name, BIN_DIR)
extracted = BIN_DIR / name
target = BIN_DIR / "syft.exe"
if extracted != target:
extracted.rename(target)
break
zip_path.unlink(missing_ok=True)
else:
url = (
f"https://github.com/anchore/syft/releases/download/"
f"v{SYFT_VERSION}/syft_{SYFT_VERSION}_linux_amd64.tar.gz"
)
tar_path = BIN_DIR / "_syft.tar.gz"
_stream_download(url, tar_path)
with tarfile.open(tar_path) as tar:
for member in tar.getmembers():
if member.name == "syft":
tar.extract(member, BIN_DIR)
break
(BIN_DIR / "syft").chmod(0o755)
tar_path.unlink(missing_ok=True)
return "installed"
except Exception as e:
return f"install failed: {e}"
def install_grype() -> str:
if have_binary("grype"):
return "already installed"
try:
BIN_DIR.mkdir(parents=True, exist_ok=True)
if _IS_WINDOWS:
url = (
f"https://github.com/anchore/grype/releases/download/"
f"v{GRYPE_VERSION}/grype_{GRYPE_VERSION}_windows_amd64.zip"
)
zip_path = BIN_DIR / "_grype.zip"
_stream_download(url, zip_path)
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if name.lower() in ("grype.exe", "grype"):
z.extract(name, BIN_DIR)
extracted = BIN_DIR / name
target = BIN_DIR / "grype.exe"
if extracted != target:
extracted.rename(target)
break
zip_path.unlink(missing_ok=True)
else:
url = (
f"https://github.com/anchore/grype/releases/download/"
f"v{GRYPE_VERSION}/grype_{GRYPE_VERSION}_linux_amd64.tar.gz"
)
tar_path = BIN_DIR / "_grype.tar.gz"
_stream_download(url, tar_path)
with tarfile.open(tar_path) as tar:
for member in tar.getmembers():
if member.name == "grype":
tar.extract(member, BIN_DIR)
break
(BIN_DIR / "grype").chmod(0o755)
tar_path.unlink(missing_ok=True)
return "installed"
except Exception as e:
return f"install failed: {e}"