audit-code install-tools.sh:
- Buffer the opengrep release JSON before grep -m1; curl died with (23)
under pipefail when grep quit early.
- Use ${m}: in the PowerShell block; $m: parsed as a scope-qualified var.
- On Arch, skip paru/yay when pacman -Q shows every package installed,
since --needed still invokes sudo.
- Add --check-only (fast, installs nothing, non-zero naming missing tools)
and --user-only (no system package managers, no sudo).
log-run.py (both skills): put the skill dir on sys.path so running it as
a script from any cwd no longer raises ModuleNotFoundError.
audit-terraform: move deps from requirements.txt into pyproject
dependency groups and add scripts/install-tools.sh (uv sync --group tools,
then check trivy, tflint, tofu, terragrunt, gh).
Both SKILL.md files gain a 0.5 Preflight step and call scripts through
uv run --project ${SKILL_DIR}. tools_unavailable is now a map of tool to
exact install command; audit-terraform skips trivy when absent and stops
with an install hint instead of crashing when tofu/terragrunt is missing.
96 lines
2.7 KiB
Python
Executable File
96 lines
2.7 KiB
Python
Executable File
"""refresh-controls.py — populate data/controls/{fsbp,cis,meta}.json."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from datetime import datetime, timezone, UTC
|
|
from pathlib import Path
|
|
|
|
import requests
|
|
|
|
_HERE = Path(__file__).resolve().parent
|
|
if str(_HERE.parent) not in sys.path:
|
|
sys.path.insert(0, str(_HERE.parent))
|
|
|
|
from scripts.controls_schema import Control, ControlsFile
|
|
from scripts.scrape_cis import CIS_URL, parse_cis_page
|
|
from scripts.scrape_fsbp import FSBP_INDEX_URL, parse_fsbp_index
|
|
|
|
|
|
_TIMEOUT = 30
|
|
_USER_AGENT = "audit-terraform-controls-refresh/1.0"
|
|
|
|
|
|
def _fetch(url: str) -> str:
|
|
r = requests.get(url, headers={"User-Agent": _USER_AGENT}, timeout=_TIMEOUT)
|
|
r.raise_for_status()
|
|
return r.text
|
|
|
|
|
|
def _now() -> datetime:
|
|
return datetime.now(UTC)
|
|
|
|
|
|
def _build_fsbp() -> ControlsFile:
|
|
html = _fetch(FSBP_INDEX_URL)
|
|
rows = parse_fsbp_index(html, base_url=FSBP_INDEX_URL)
|
|
controls = [
|
|
Control(
|
|
control_id=f"FSBP {r.control_id}",
|
|
title=r.title,
|
|
severity=r.severity,
|
|
resource_types=r.resource_types,
|
|
requirement=r.requirement or r.title,
|
|
source_url=r.detail_url,
|
|
)
|
|
for r in rows
|
|
]
|
|
return ControlsFile(source="fsbp", fetched_at=_now(), controls=controls)
|
|
|
|
|
|
def _build_cis() -> ControlsFile:
|
|
html = _fetch(CIS_URL)
|
|
rows = parse_cis_page(html, source_url=CIS_URL)
|
|
controls = [
|
|
Control(
|
|
control_id=r.control_id,
|
|
title=r.title,
|
|
severity=r.severity,
|
|
resource_types=r.resource_types,
|
|
requirement=r.requirement,
|
|
source_url=r.source_url,
|
|
)
|
|
for r in rows
|
|
]
|
|
return ControlsFile(source="cis", fetched_at=_now(), controls=controls)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--output-dir", default=str(_HERE.parent / "data" / "controls"))
|
|
args = parser.parse_args(argv)
|
|
|
|
out_dir = Path(args.output_dir).resolve()
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
fsbp = _build_fsbp()
|
|
cis = _build_cis()
|
|
|
|
(out_dir / "fsbp.json").write_text(fsbp.to_json())
|
|
(out_dir / "cis.json").write_text(cis.to_json())
|
|
|
|
meta = {
|
|
"fsbp": {"url": FSBP_INDEX_URL, "fetched_at": fsbp.fetched_at.isoformat()},
|
|
"cis": {"url": CIS_URL, "fetched_at": cis.fetched_at.isoformat()},
|
|
}
|
|
(out_dir / "meta.json").write_text(json.dumps(meta, indent=2))
|
|
|
|
print(f"Wrote {len(fsbp.controls)} FSBP controls, {len(cis.controls)} CIS controls "
|
|
f"to {out_dir}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|