84 lines
3.1 KiB
Python
84 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import subprocess
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
def run_ssh(target: str, command: str) -> str:
|
|
result = subprocess.run(
|
|
["ssh", target, command],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
)
|
|
return result.stdout
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as stream:
|
|
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Collect non-secret QuantEngine evidence from a remote host")
|
|
parser.add_argument("--target", required=True, help="SSH target, e.g. kjh2064@178.104.200.7")
|
|
parser.add_argument("--remote-root", default="/home/kjh2064/quantengine_active")
|
|
parser.add_argument("--repo-root", default=None)
|
|
args = parser.parse_args()
|
|
|
|
root = Path(args.repo_root).resolve() if args.repo_root else Path(__file__).resolve().parents[1]
|
|
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
out_dir = root / "Temp" / "evidence" / "remote" / stamp
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# The command reads only public operational output. It never reads the
|
|
# connection string or environment file, so secrets cannot enter evidence.
|
|
journal = run_ssh(
|
|
args.target,
|
|
"journalctl -u quantengine --since '24 hours ago' --no-pager "
|
|
"| grep -E 'daily-collection|Collection run|Collecting ticker|completed|KIS_DOTNET' || true",
|
|
)
|
|
journal_path = out_dir / "quantengine-journal.log"
|
|
journal_path.write_text(journal, encoding="utf-8")
|
|
|
|
artifact = run_ssh(
|
|
args.target,
|
|
"if test -f " + args.remote_root + "/Temp/kis_dotnet_collection_v1.json; then "
|
|
"cat " + args.remote_root + "/Temp/kis_dotnet_collection_v1.json; "
|
|
"else exit 44; fi",
|
|
)
|
|
artifact_path = out_dir / "kis_dotnet_collection_v1.json"
|
|
artifact_path.write_text(artifact, encoding="utf-8")
|
|
json.loads(artifact_path.read_text(encoding="utf-8"))
|
|
|
|
manifest = {
|
|
"formula_id": "REMOTE_WBS_EVIDENCE_MANIFEST_V1",
|
|
"captured_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
"source": {"ssh_target": args.target, "remote_root": args.remote_root},
|
|
"secret_policy": "connection string and environment file were not read or copied",
|
|
"artifacts": [
|
|
{"path": str(journal_path.relative_to(root)), "sha256": sha256(journal_path)},
|
|
{"path": str(artifact_path.relative_to(root)), "sha256": sha256(artifact_path)},
|
|
],
|
|
"postgresql": {
|
|
"status": "NOT_INCLUDED",
|
|
"reason": "Run verifier with QE_WBS_PG_DSN through an approved SSH tunnel; no password is copied.",
|
|
},
|
|
}
|
|
(out_dir / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
print(json.dumps(manifest, indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|