Files
awoooi/scripts/security/security-asset-control-ledger.py
Your Name 81a60226bb
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m45s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
feat(iwooos): 新增資安資產控制總帳
2026-06-18 13:56:38 +08:00

504 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""IwoooS 資安資產控制總帳產生器。
本工具只彙整 repo 內已提交的資安清冊、snapshot 與規範,將主機、
公開入口、版本來源、workflow、Wazuh、Kali、備份、監控、AI provider、
產品 runtime 與自動化資產沉澱狀態收斂成 P0-A 可驗證總帳。
它不連線主機、不讀 live log、不呼叫 Wazuh / Kali / Gitea / GitHub、
不做 active scan、不讀 secret、不修改 Nginx / firewall / K8s / workflow。
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
TAIPEI = timezone(timedelta(hours=8))
ASSET_GROUPS = [
{
"group_id": "public_gateway_nginx",
"label": "Nginx / Public Gateway / Route",
"priority": "P0",
"tier": "C0",
"scope": "公開入口、API、WebSocket、ACME、admin route、Ollama proxy",
"evidence_refs": [
"docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md",
"docs/security/public-gateway-preflight-inventory.snapshot.json",
"docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md",
"docs/security/public-gateway-post-incident-readback-plan.snapshot.json",
],
"next_owner_action": "補 owner-provided live conf、rendered diff、nginx -t、route smoke、rollback owner。",
},
{
"group_id": "dns_tls_certbot",
"label": "DNS / TLS / Certbot",
"priority": "P0",
"tier": "C0",
"scope": "domain、certificate path、ACME、renewal owner、TLS route",
"evidence_refs": [
"docs/security/DOMAIN-TLS-CERTBOT-INVENTORY.md",
"docs/security/domain-tls-certbot-inventory.snapshot.json",
"docs/security/DOMAIN-TLS-CERTBOT-OWNER-RESPONSE-ACCEPTANCE.md",
"docs/security/domain-tls-certbot-owner-response-acceptance.snapshot.json",
],
"next_owner_action": "補 SAN / wildcard 覆蓋依據、expiry metadata、renewal owner、ACME route owner。",
},
{
"group_id": "host_service_runtime",
"label": "Docker / systemd / Host Service",
"priority": "P0",
"tier": "C0",
"scope": "Docker Compose、systemd、repair-bot、port binding、process / persistence baseline",
"evidence_refs": [
"docs/security/HOST-SERVICE-CONFIG-INVENTORY.md",
"docs/security/host-service-config-inventory.snapshot.json",
"docs/security/HOST-SERVICE-POST-INCIDENT-READBACK-PLAN.md",
"docs/security/host-service-post-incident-readback-plan.snapshot.json",
],
"next_owner_action": "補 live hash metadata、incident readback、restart window、rollback owner、post-check。",
},
{
"group_id": "ssh_firewall_network",
"label": "SSH / Firewall / WireGuard / NodePort",
"priority": "P0",
"tier": "C0",
"scope": "SSH target、known_hosts、sudoers、firewall、WireGuard、NetworkPolicy、NodePort",
"evidence_refs": [
"docs/security/SSH-NETWORK-ACCESS-INVENTORY.md",
"docs/security/ssh-network-access-inventory.snapshot.json",
"docs/security/SSH-NETWORK-POST-INCIDENT-READBACK-PLAN.md",
"docs/security/ssh-network-post-incident-readback-plan.snapshot.json",
],
"next_owner_action": "補 actor、before / after state、impact、operator notification、restoration evidence。",
},
{
"group_id": "k8s_argocd_gitops",
"label": "K8s / ArgoCD / GitOps",
"priority": "P0",
"tier": "C0",
"scope": "K8s manifests、ArgoCD app、RBAC、NetworkPolicy、CronJob、Velero",
"evidence_refs": [
"docs/security/K8S-ARGOCD-MANIFEST-INVENTORY.md",
"docs/security/k8s-argocd-manifest-inventory.snapshot.json",
"docs/security/K8S-ARGOCD-POST-INCIDENT-READBACK-PLAN.md",
"docs/security/k8s-argocd-post-incident-readback-plan.snapshot.json",
],
"next_owner_action": "補 ArgoCD revision、health / sync、rendered manifest diff、rollback revision、postcheck owner。",
},
{
"group_id": "workflow_runner_secret",
"label": "Gitea Workflow / Runner / Secret Metadata",
"priority": "P0",
"tier": "C0",
"scope": "workflow、runner label、deploy key、webhook、secret name parity、redaction guard",
"evidence_refs": [
"docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-INVENTORY.md",
"docs/security/source-control-workflow-secret-name-inventory.snapshot.json",
"docs/security/CD-RUNNER-SECRET-INJECTION-POST-INCIDENT-READBACK-PLAN.md",
"docs/security/cd-runner-secret-injection-post-incident-readback-plan.snapshot.json",
],
"next_owner_action": "補 runner attestation、workspace cleanup、secret injection route、log redaction、Gitea run readback。",
},
{
"group_id": "source_control_repo_visibility",
"label": "Gitea / GitHub / Source Control",
"priority": "P0",
"tier": "C0",
"scope": "repo visibility、canonical refs、GitHub primary readiness、branch / tag / workflow boundary",
"evidence_refs": [
"docs/security/GITEA-GITHUB-MIGRATION-INVENTORY.md",
"docs/security/SOURCE-CONTROL-PRIMARY-READINESS-GATE.md",
"docs/security/GITHUB-TARGET-OWNER-DECISION-RESPONSE.md",
"docs/security/source-control-primary-readiness-gate.snapshot.json",
],
"next_owner_action": "補 owner response不得建立 repo、改 visibility、sync refs 或切 primary。",
},
{
"group_id": "wazuh_endpoint_siem",
"label": "Wazuh / Endpoint / SIEM",
"priority": "P0",
"tier": "C0",
"scope": "Wazuh manager、agent、FIM、rule / decoder、event ref、active response dry-run boundary",
"evidence_refs": [
"docs/security/WAZUH-IWOOOS-INTRUSION-READBACK-PLAN.md",
"docs/security/wazuh-iwooos-intrusion-readback-plan.snapshot.json",
"docs/security/SOC-SIEM-KALI-WAZUH-INTEGRATION-CONTROL.md",
"docs/security/soc-siem-kali-wazuh-integration-control.snapshot.json",
],
"next_owner_action": "補 Wazuh health refs、agent refs、event refs、rule / decoder readback 與 no-raw-payload attestation。",
},
{
"group_id": "kali_112_assessment",
"label": "Kali 112 / Assessment Tooling",
"priority": "P0",
"tier": "C0",
"scope": "Kali health、tool version、scope、finding envelope、maintenance window",
"evidence_refs": [
"docs/security/KALI-INTEGRATION-STATUS.md",
"docs/security/kali-integration-status.snapshot.json",
"docs/security/KALI-112-MAINTENANCE-WINDOW-DRAFT.md",
"docs/security/kali-112-maintenance-window-draft.snapshot.json",
],
"next_owner_action": "補 scope ref、health ref、normalized finding envelopeactive scan 與 /execute 仍獨立批准。",
},
{
"group_id": "monitoring_alerting_observability",
"label": "Monitoring / Alerting / Observability",
"priority": "P0",
"tier": "C0",
"scope": "Prometheus、Alertmanager、Telegram route、SigNoz、Sentry、Langfuse、no-false-green",
"evidence_refs": [
"docs/security/MONITORING-ALERTING-OBSERVABILITY-INVENTORY.md",
"docs/security/monitoring-alerting-observability-inventory.snapshot.json",
"docs/security/MONITORING-POST-INCIDENT-READBACK-PLAN.md",
"docs/security/monitoring-post-incident-readback-plan.snapshot.json",
],
"next_owner_action": "補 route owner、receiver diff、receipt evidence、noise budget、reload owner 與 no-false-green。",
},
{
"group_id": "backup_restore_dr",
"label": "Backup / Restore / DR / Escrow",
"priority": "P0",
"tier": "C0",
"scope": "backup scripts、restic、offsite、credential escrow、Velero、restore drill、retention",
"evidence_refs": [
"docs/security/BACKUP-RESTORE-ESCROW-INVENTORY.md",
"docs/security/backup-restore-escrow-inventory.snapshot.json",
"docs/security/BACKUP-RESTORE-POST-INCIDENT-READBACK-PLAN.md",
"docs/security/backup-restore-post-incident-readback-plan.snapshot.json",
],
"next_owner_action": "補 restore drill、offsite sync ref、escrow non-secret proof、retention runway、DR scorecard。",
},
{
"group_id": "container_registry_supply_chain",
"label": "Harbor / Registry / SBOM / Supply Chain",
"priority": "P0",
"tier": "C0",
"scope": "Harbor、registry、image tag、SBOM、Cosign、SLSA、dependency drift、CVE / KEV",
"evidence_refs": [
"docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md",
"docs/security/SECURITY-SUPPLY-CHAIN-CONTRACT-MANIFEST.md",
"docs/security/security-supply-chain-contract-manifest.snapshot.json",
"docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json",
],
"next_owner_action": "補 SBOM / VEX / provenance intake、image signing、KEV / EPSS / exposure SLA。",
},
{
"group_id": "public_admin_api_runtime",
"label": "Public / Admin / API / Frontend Runtime",
"priority": "P0",
"tier": "C0",
"scope": "public URL、CORS、auth boundary、middleware、webhook、frontend env、i18n redaction",
"evidence_refs": [
"docs/security/PUBLIC-RUNTIME-CONFIG-CHANGE-EVIDENCE-ACCEPTANCE.md",
"docs/security/public-runtime-config-change-evidence-acceptance.snapshot.json",
"docs/security/public-frontend-sensitive-surface-guard.snapshot.json",
"docs/HARD_RULES.md",
],
"next_owner_action": "補 route owner、API contract readback、CORS diff、desktop / mobile smoke、bundle sensitive scan。",
},
{
"group_id": "ai_provider_agent_runtime",
"label": "AI Provider / Model Router / Agent Runtime",
"priority": "P0",
"tier": "C0",
"scope": "OpenClaw、Ollama、NemoTron、Hermes、Gemini、MCP / A2A、tool allowlist、cost / privacy",
"evidence_refs": [
"docs/security/AI-PROVIDER-OWNER-RESPONSE-ACCEPTANCE.md",
"docs/security/ai-provider-owner-response-acceptance.snapshot.json",
"docs/ai",
"docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md",
],
"next_owner_action": "補 dry-run、benchmark、cost review、privacy review、fallback order 與 rollback owner。",
},
{
"group_id": "product_surface_runtime_routes",
"label": "Product Surface / Runtime Route",
"priority": "P1",
"tier": "C1",
"scope": "AWOOOI、AwoooP、IwoooS、VibeWork、agent-bounty-protocol、StockPlatform、Tsenyang、Bitan",
"evidence_refs": [
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
"docs/security/VIBEWORK-IWOOOS-ONBOARDING-HANDOFF.md",
"docs/security/AGENT-BOUNTY-IWOOOS-ONBOARDING-HANDOFF.md",
"docs/security/agent-bounty-iwooos-onboarding-handoff.snapshot.json",
],
"next_owner_action": "補逐產品 owner、route、admin、API、backup、webhook、rollback 與 validation 指標。",
},
{
"group_id": "automation_asset_sedimentation",
"label": "KM / PlayBook / Script / Schedule / Verifier",
"priority": "P1",
"tier": "C1",
"scope": "incident、approval、repair candidate、manual handoff 的自動化資產沉澱",
"evidence_refs": [
"docs/LOGBOOK.md",
"apps/api/src/services/repair_candidate_service.py",
"apps/api/src/services/telegram_gateway.py",
"apps/web/src/app/[locale]/awooop/work-items/page.tsx",
],
"next_owner_action": "補 incident / approval / manual handoff writeback contract讓資產總帳從可視化進入可追蹤閉環。",
},
]
REQUIRED_OWNER_FIELDS = [
"asset_group_id",
"asset_alias",
"owner_role",
"owner_team",
"business_impact",
"technical_scope",
"affected_routes_or_services",
"data_classification",
"redacted_evidence_refs",
"source_of_truth_ref",
"live_state_ref",
"config_diff_ref",
"monitoring_signal_ref",
"wazuh_or_siem_ref",
"kali_scope_ref",
"backup_restore_ref",
"supply_chain_ref",
"secret_absence_attestation",
"raw_payload_absence_attestation",
"maintenance_window",
"rollback_owner",
"postcheck_owner",
"followup_owner",
"decision_reason",
]
REVIEWER_CHECKS = [
"asset alias 不得暴露個人 namespace",
"owner role / team 必填",
"source-of-truth 必須可追溯",
"live evidence 只能收脫敏 ref",
"secret value / hash / partial token 一律拒收",
"raw Wazuh / raw Kali output 一律拒收",
"Nginx / firewall / K8s / workflow 變更需獨立 runtime approval",
"route 200 不得當資安完成",
"dashboard 可見不得當 remediation 完成",
"backup 存在不得當 restore drill 完成",
"Kali 納入範圍不得當 active scan 授權",
"Wazuh event 可見不得當 active response 授權",
"repo snapshot 不得當 live host truth",
"事故後回讀需含 actor、before / after、impact、postcheck",
"no-false-green 必須明確標示",
"owner response received / accepted 必須維持 0除非有實際收件",
"runtime gate 必須維持 0",
"action button 必須維持 0",
"Gitea / GitHub refs 不得自動同步",
"GitHub primary 不得自動切換",
"Telegram 不得實發",
"SOAR 不得 auto block",
"AI agent 不得讀 secret 或 host write",
"LOGBOOK 不得包含內部對話逐字稿",
]
OUTCOME_LANES = [
"waiting_asset_owner_packet",
"request_gateway_supplement",
"request_host_service_supplement",
"request_network_supplement",
"request_wazuh_kali_supplement",
"request_backup_restore_supplement",
"request_supply_chain_supplement",
"request_product_surface_supplement",
"quarantine_secret_or_raw_payload",
"ready_for_security_reviewer_review",
]
BLOCKED_ACTIONS = [
"ssh_to_host",
"read_live_host_log",
"write_host_file",
"nginx_reload",
"certbot_renew",
"dns_change",
"firewall_change",
"wireguard_change",
"nodeport_change",
"networkpolicy_apply",
"kubectl_action",
"argocd_sync",
"helm_upgrade",
"docker_restart",
"docker_compose_up",
"systemctl_restart",
"repair_bot_execute",
"ansible_apply",
"wazuh_active_response",
"wazuh_rule_change",
"kali_active_scan",
"kali_execute",
"nmap_scan",
"nuclei_scan",
"trivy_live_scan",
"package_upgrade",
"workflow_modify",
"workflow_dispatch",
"runner_restart",
"secret_read",
"secret_rotate",
"webhook_modify",
"deploy_key_modify",
"refs_sync",
"force_push",
"github_primary_switch",
"backup_run",
"restore_run",
"offsite_sync",
"remote_delete",
"telegram_send",
"soar_case_create",
"auto_block",
"production_write",
]
def get_git_commit(root: Path) -> str:
try:
return subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], cwd=root, text=True).strip()
except Exception:
return "unknown"
def build_snapshot(root: Path, generated_at: str) -> dict[str, Any]:
enriched_groups = []
evidence_refs: set[str] = set()
existing_refs: set[str] = set()
missing_refs: set[str] = set()
for group in ASSET_GROUPS:
refs = group["evidence_refs"]
evidence_refs.update(refs)
ref_states = []
for ref in refs:
exists = (root / ref).exists()
ref_states.append({"ref": ref, "exists": exists})
if exists:
existing_refs.add(ref)
else:
missing_refs.add(ref)
enriched_groups.append(
{
**group,
"owner_packet_status": "waiting_asset_owner_packet",
"runtime_gate": 0,
"action_button": 0,
"owner_response_received": 0,
"owner_response_accepted": 0,
"live_evidence_accepted": 0,
"redacted_evidence_refs_required": True,
"secret_value_collection_allowed": False,
"raw_payload_allowed": False,
"evidence_ref_states": ref_states,
}
)
summary = {
"asset_group_count": len(enriched_groups),
"p0_asset_group_count": sum(1 for group in enriched_groups if group["priority"] == "P0"),
"p1_asset_group_count": sum(1 for group in enriched_groups if group["priority"] == "P1"),
"c0_asset_group_count": sum(1 for group in enriched_groups if group["tier"] == "C0"),
"c1_asset_group_count": sum(1 for group in enriched_groups if group["tier"] == "C1"),
"evidence_ref_count": len(evidence_refs),
"existing_evidence_ref_count": len(existing_refs),
"missing_evidence_ref_count": len(missing_refs),
"required_owner_field_count": len(REQUIRED_OWNER_FIELDS),
"reviewer_check_count": len(REVIEWER_CHECKS),
"outcome_lane_count": len(OUTCOME_LANES),
"blocked_action_count": len(BLOCKED_ACTIONS),
"owner_packet_required_count": len(enriched_groups),
"owner_response_received_count": 0,
"owner_response_accepted_count": 0,
"live_evidence_accepted_count": 0,
"runtime_gate_count": 0,
"action_button_count": 0,
"host_write_authorized_count": 0,
"active_scan_authorized_count": 0,
"wazuh_active_response_authorized_count": 0,
"kali_execute_authorized_count": 0,
"soar_action_authorized_count": 0,
"auto_block_authorized_count": 0,
"secret_value_collected_count": 0,
"raw_payload_stored_count": 0,
"security_asset_control_ledger_completion_percent": 100,
"iwooos_headline_progress_percent": 64,
}
return {
"schema_version": "security_asset_control_ledger_v1",
"status": "security_asset_control_ledger_ready_no_runtime_action",
"generated_at": generated_at,
"git_commit": get_git_commit(root),
"summary": summary,
"asset_groups": enriched_groups,
"required_owner_fields": REQUIRED_OWNER_FIELDS,
"reviewer_checks": REVIEWER_CHECKS,
"outcome_lanes": OUTCOME_LANES,
"blocked_actions": BLOCKED_ACTIONS,
"execution_boundaries": {
"not_authorization": True,
"runtime_execution_authorized": False,
"host_write_authorized": False,
"ssh_read_authorized": False,
"nginx_reload_authorized": False,
"firewall_change_authorized": False,
"argocd_sync_authorized": False,
"workflow_modification_authorized": False,
"secret_value_collection_allowed": False,
"wazuh_active_response_authorized": False,
"kali_active_scan_authorized": False,
"kali_execute_authorized": False,
"telegram_send_authorized": False,
"soar_action_authorized": False,
"auto_block_authorized": False,
"production_write_authorized": False,
"action_buttons_allowed": False,
},
}
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", default=".")
parser.add_argument("--output")
parser.add_argument("--generated-at")
args = parser.parse_args(argv)
root = Path(args.root).resolve()
generated_at = args.generated_at or datetime.now(TAIPEI).replace(microsecond=0).isoformat()
snapshot = build_snapshot(root, generated_at)
if args.output:
output = Path(args.output)
if not output.is_absolute():
output = root / output
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
else:
print(json.dumps(snapshot, ensure_ascii=False, indent=2, sort_keys=True))
summary = snapshot["summary"]
print(
"SECURITY_ASSET_CONTROL_LEDGER_OK "
f"groups={summary['asset_group_count']} "
f"p0={summary['p0_asset_group_count']} "
f"evidence_refs={summary['evidence_ref_count']} "
f"runtime_gate={summary['runtime_gate_count']}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))