243 lines
8.2 KiB
Python
Executable File
243 lines
8.2 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import tempfile
|
|
import shutil
|
|
|
|
USER_VERSION = "8.27.0"
|
|
FORCE_INSTALL = False
|
|
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
def parse_args():
|
|
global USER_VERSION, FORCE_INSTALL
|
|
args = sys.argv[1:]
|
|
new_args = []
|
|
while args:
|
|
arg = args.pop(0)
|
|
if arg.startswith("--version="):
|
|
USER_VERSION = arg.split("=", 1)[1].lstrip("v")
|
|
elif arg == "--force":
|
|
FORCE_INSTALL = True
|
|
else:
|
|
new_args.append(arg)
|
|
return new_args
|
|
|
|
def is_gitleaks_installed():
|
|
try:
|
|
result = subprocess.run(["gitleaks", "version"], capture_output=True, text=True)
|
|
return result.returncode == 0
|
|
except FileNotFoundError:
|
|
return False
|
|
|
|
def get_installed_version():
|
|
try:
|
|
result = subprocess.run(["gitleaks", "version"], capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
return None
|
|
version = result.stdout.strip()
|
|
if version.startswith("v"):
|
|
version = version[1:]
|
|
return version
|
|
except Exception:
|
|
return None
|
|
|
|
def get_platform_and_arch():
|
|
import platform
|
|
system = platform.system().lower()
|
|
machine = platform.machine().lower()
|
|
|
|
if system.startswith("linux"):
|
|
plat = "linux"
|
|
elif system == "darwin":
|
|
plat = "darwin"
|
|
elif system == "windows":
|
|
plat = "windows"
|
|
elif "mingw" in os.environ.get("MSYSTEM", "").lower():
|
|
plat = "windows"
|
|
else:
|
|
print(f"[!] Unsupported operating system: {system}")
|
|
sys.exit(1)
|
|
|
|
if machine in ["x86_64", "amd64"]:
|
|
arch = "x64"
|
|
elif machine in ["arm64", "aarch64"]:
|
|
arch = "arm64"
|
|
elif machine.startswith("armv7"):
|
|
arch = "armv7"
|
|
elif machine == "i386":
|
|
arch = "x32"
|
|
elif machine == "armv6":
|
|
arch = "armv6"
|
|
else:
|
|
print(f"[!] Unsupported architecture: {machine}")
|
|
sys.exit(1)
|
|
|
|
if plat == "windows" and arch not in ["x32", "x64", "armv6", "armv7"]:
|
|
print(f"[!] Unsupported Windows architecture: {arch}")
|
|
sys.exit(1)
|
|
|
|
return plat, arch
|
|
|
|
def install_gitleaks(version, force):
|
|
installed = is_gitleaks_installed()
|
|
current_version = get_installed_version()
|
|
|
|
if installed and not force:
|
|
if current_version == version:
|
|
return
|
|
else:
|
|
print(f"Gitleaks {current_version} is installed, but version {version} is required.")
|
|
print(" Use --force to reinstall.")
|
|
return
|
|
|
|
print(f"Installing Gitleaks v{version}...")
|
|
plat, arch = get_platform_and_arch()
|
|
|
|
ext = "zip" if plat == "windows" else "tar.gz"
|
|
filename = f"gitleaks_{version}_{plat}_{arch}.{ext}"
|
|
url = f"https://github.com/gitleaks/gitleaks/releases/download/v{version}/{filename}"
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
archive_path = os.path.join(tmpdir, filename)
|
|
print(f"Downloading from: {url}")
|
|
subprocess.run(["curl", "-sSL", "-o", archive_path, url], check=True)
|
|
|
|
if ext == "zip":
|
|
import zipfile
|
|
with zipfile.ZipFile(archive_path, "r") as zip_ref:
|
|
zip_ref.extractall(tmpdir)
|
|
else:
|
|
subprocess.run(["tar", "-xzf", archive_path, "-C", tmpdir], check=True)
|
|
|
|
exe_name = "gitleaks.exe" if plat == "windows" else "gitleaks"
|
|
gitleaks_bin = os.path.join(tmpdir, exe_name)
|
|
|
|
if plat == "windows":
|
|
fallback = os.path.expanduser("~/.local/bin")
|
|
os.makedirs(fallback, exist_ok=True)
|
|
dest_path = os.path.join(fallback, exe_name)
|
|
shutil.move(gitleaks_bin, dest_path)
|
|
print(f"Gitleaks v{version} installed at {dest_path}")
|
|
print("Add ~/.local/bin to your PATH if not already present.")
|
|
else:
|
|
install_path = "/usr/local/bin/gitleaks"
|
|
|
|
elevate_cmd = None
|
|
if shutil.which("sudo"):
|
|
elevate_cmd = "sudo"
|
|
elif shutil.which("doas"):
|
|
elevate_cmd = "doas"
|
|
|
|
if elevate_cmd:
|
|
print(f"Installing with {elevate_cmd} at {install_path}...")
|
|
try:
|
|
subprocess.run([elevate_cmd, "mv", gitleaks_bin, install_path], check=True)
|
|
subprocess.run([elevate_cmd, "chmod", "+x", install_path], check=True)
|
|
print(f"Gitleaks v{version} installed at {install_path}")
|
|
except subprocess.CalledProcessError:
|
|
print(f"Error moving file with {elevate_cmd}. Installing locally...")
|
|
fallback = os.path.expanduser("~/.local/bin")
|
|
os.makedirs(fallback, exist_ok=True)
|
|
shutil.move(gitleaks_bin, os.path.join(fallback, "gitleaks"))
|
|
print(f"Gitleaks v{version} installed at {fallback}")
|
|
print("Ensure ~/.local/bin is in your PATH")
|
|
else:
|
|
print("Neither 'sudo' nor 'doas' available. Installing locally...")
|
|
fallback = os.path.expanduser("~/.local/bin")
|
|
os.makedirs(fallback, exist_ok=True)
|
|
shutil.move(gitleaks_bin, os.path.join(fallback, "gitleaks"))
|
|
print(f"Gitleaks v{version} installed at {fallback}")
|
|
print("Ensure ~/.local/bin is in your PATH")
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
install_gitleaks(USER_VERSION, FORCE_INSTALL)
|
|
|
|
# hook variables
|
|
TEMPLATE_NAME = "leet"
|
|
CONFIG_FILE_NAME = "gitleaks.toml"
|
|
FORMAT = "html"
|
|
REDACT = False
|
|
config_path = None
|
|
use_internal_rules = False
|
|
use_external_rules = False
|
|
|
|
for arg in args:
|
|
if arg.startswith("--template="):
|
|
TEMPLATE_NAME = arg.split("=",1)[1]
|
|
elif arg.startswith("--config="):
|
|
config_path = arg.split("=",1)[1]
|
|
elif arg.startswith("--format="):
|
|
FORMAT = arg.split("=",1)[1]
|
|
elif arg == "--redact":
|
|
REDACT = True
|
|
elif arg == "--rules-internal":
|
|
use_internal_rules = True
|
|
elif arg == "--rules-external":
|
|
use_external_rules = True
|
|
|
|
# By default, don't use any config file unless explicitly specified
|
|
if not (use_internal_rules or use_external_rules):
|
|
config_path = None
|
|
elif not config_path:
|
|
if use_internal_rules:
|
|
config_path = os.path.join(SCRIPT_DIR, "rules", CONFIG_FILE_NAME)
|
|
elif use_external_rules:
|
|
# You might want to specify a default external rules path here
|
|
print("Error: --rules-external requires a config file path with --config=")
|
|
sys.exit(1)
|
|
|
|
template_path = os.path.join(SCRIPT_DIR, "templates", f"{TEMPLATE_NAME}.tmpl")
|
|
report_dir = os.path.join(".pre-commit", "gitleaks", "report")
|
|
os.makedirs(report_dir, exist_ok=True)
|
|
|
|
if FORMAT not in ("html", "json"):
|
|
print(f"Unsupported format: {FORMAT}")
|
|
print(" Supported formats: html, json")
|
|
sys.exit(1)
|
|
|
|
if config_path and not os.path.isfile(config_path):
|
|
print(f"Config file not found: {config_path}")
|
|
sys.exit(1)
|
|
|
|
extra_args = []
|
|
if config_path:
|
|
extra_args.append(f"--config={config_path}")
|
|
if REDACT:
|
|
extra_args.append("--redact")
|
|
|
|
if FORMAT == "html":
|
|
if not os.path.isfile(template_path):
|
|
print(f"Template not found: {template_path}")
|
|
sys.exit(1)
|
|
report_path = os.path.join(report_dir, "index.html")
|
|
cmd = ["gitleaks", "git"] + extra_args + [
|
|
"--report-format=template",
|
|
f"--report-template={template_path}",
|
|
f"--report-path={report_path}"
|
|
]
|
|
else:
|
|
report_path = os.path.join(report_dir, "report.json")
|
|
cmd = ["gitleaks", "git"] + extra_args + [
|
|
"--report-format=json",
|
|
f"--report-path={report_path}"
|
|
]
|
|
|
|
print(f"Running: {' '.join(cmd)}")
|
|
result = subprocess.run(cmd)
|
|
if result.returncode != 0:
|
|
print("Error running gitleaks.")
|
|
sys.exit(result.returncode)
|
|
|
|
if os.path.isfile(report_path):
|
|
print(f"Report generated: {report_path}")
|
|
else:
|
|
print("Failed to generate report.")
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|