#!/usr/bin/env python3 """ IwoooS 高價值配置控管覆蓋矩陣。 本工具讀取 high-value-config-change-gate.py 的 CATEGORIES 定義,產生一份 全域配置控管覆蓋 snapshot。它只整理 source-of-truth、owner gate、 evidence refs、缺口與 0/false 邊界,不讀 live host、不連外、不執行 Nginx / DNS / K8s / workflow / agent runtime 動作。 """ from __future__ import annotations import argparse import json import runpy import subprocess import sys from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any TAIPEI = timezone(timedelta(hours=8)) CONTROL_STATUS_BY_CATEGORY = { "nginx_public_gateway": { "coverage_status": "repo_only_preflight_contract_ready_needs_owner_live_diff", "coverage_percent": 84, "evidence_refs": [ "docs/security/NGINX-CONFIG-DRIFT-DETECTOR.md", "docs/security/nginx-config-drift-repo.snapshot.json", "docs/security/DOMAIN-TLS-CERTBOT-INVENTORY.md", "docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md", "docs/security/public-gateway-preflight-inventory.snapshot.json", "docs/schemas/public_gateway_preflight_inventory_v1.schema.json", ], "current_gap": "已固定 12 個 public gateway preflight gate;owner response、live conf、rendered diff、nginx -t、route smoke、maintenance window 與 rollback owner 仍全部為 0。", "next_owner_action": "補 public gateway owner、owner-provided live conf、source-to-live rendered diff、nginx -t evidence、route smoke、maintenance window 與 rollback owner。", }, "dns_tls_certbot": { "coverage_status": "repo_only_inventory_ready", "coverage_percent": 74, "evidence_refs": [ "docs/security/DOMAIN-TLS-CERTBOT-INVENTORY.md", "docs/security/domain-tls-certbot-inventory.snapshot.json", ], "current_gap": "4 個 certificate path 關係需 owner 確認;live DNS / TLS probe 未執行。", "next_owner_action": "確認 SAN / 共用憑證關係、renewal owner、ACME smoke 與 rollback owner。", }, "k8s_production_gitops": { "coverage_status": "gate_defined_needs_runtime_evidence", "coverage_percent": 58, "evidence_refs": [ "docs/security/HIGH-VALUE-CONFIG-CHANGE-GATE.md", "k8s/awoooi-prod", "k8s/argocd", ], "current_gap": "尚未把 ArgoCD health / sync readback 與 rollback revision 收成 owner packet。", "next_owner_action": "補 GitOps owner、rollback revision、health readback 與 post-deploy validation plan。", }, "secret_metadata": { "coverage_status": "metadata_policy_ready", "coverage_percent": 66, "evidence_refs": [ "docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-INVENTORY.md", "docs/security/source-control-workflow-secret-name-inventory.snapshot.json", "docs/security/SECRETS_REFERENCE.md", ], "current_gap": "只允許 secret name / metadata;仍缺 owner response 與 parity acceptance。", "next_owner_action": "只回覆 secret name owner、rotation owner、injection owner 與 redacted evidence refs。", }, "gitea_workflow_runner_source_control": { "coverage_status": "metadata_inventory_ready", "coverage_percent": 70, "evidence_refs": [ "docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-INVENTORY.md", "docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-OWNER-RESPONSE.md", "docs/security/SOURCE-CONTROL-PRIMARY-READINESS-GATE.md", ], "current_gap": "workflow / runner / deploy key / webhook / branch protection 仍待 owner response;不得改 workflow。", "next_owner_action": "補 runner label、webhook、deploy key、branch protection 與 workflow parity owner metadata。", }, "public_admin_api_runtime_config": { "coverage_status": "policy_ready_needs_change_scoped_smoke", "coverage_percent": 62, "evidence_refs": [ "docs/HARD_RULES.md", "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", ], "current_gap": "每次產品 route / admin / API / frontend config 變更仍需逐次 smoke 與 owner gate。", "next_owner_action": "補 affected route、admin/auth boundary、CORS/public URL 與 desktop/mobile smoke plan。", }, "backup_restore_credential": { "coverage_status": "repo_only_inventory_ready_needs_restore_drill_owner", "coverage_percent": 58, "evidence_refs": [ "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", "docs/security/BACKUP-RESTORE-ESCROW-INVENTORY.md", "docs/security/backup-restore-escrow-inventory.snapshot.json", "docs/schemas/backup_restore_escrow_inventory_v1.schema.json", ], "current_gap": "repo-only 清冊已納入 38 個 backup / restore / escrow / retention surface;restore drill、offsite sync、credential escrow、retention change、live evidence 與 owner response 仍全部為 0。", "next_owner_action": "補 restore drill approval package、offsite owner、escrow owner、retention owner、rollback owner 與 no-secret-value evidence。", }, "agent_bounty_protocol_runtime": { "coverage_status": "onboarding_handoff_ready_needs_runtime_owner", "coverage_percent": 68, "evidence_refs": [ "docs/security/AGENT-BOUNTY-IWOOOS-ONBOARDING-HANDOFF.md", "docs/security/agent-bounty-iwooos-onboarding-handoff.snapshot.json", "docs/security/GITHUB-TARGET-OWNER-DECISION-RESPONSE.md", ], "current_gap": "尚未收到 runtime / MCP / A2A / treasury / payout owner response;runtime gate 必須維持 0。", "next_owner_action": "補 repo owner、external agent owner、treasury owner、runtime gate owner 與 validation plan。", }, "monitoring_alerting_observability": { "coverage_status": "repo_only_inventory_ready_needs_live_route_evidence", "coverage_percent": 62, "evidence_refs": [ "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", "docs/security/MONITORING-ALERTING-OBSERVABILITY-INVENTORY.md", "docs/security/monitoring-alerting-observability-inventory.snapshot.json", "docs/schemas/monitoring_alerting_observability_inventory_v1.schema.json", ], "current_gap": "repo-only 清冊已納入 60 個 monitoring / alerting / observability surface;仍缺 live config hash、rule diff、receiver diff、reload owner、route smoke、receipt proof 與 owner response。", "next_owner_action": "補 Prometheus / Alertmanager / Grafana / SigNoz / Sentry / Langfuse / Telegram owner、live drift evidence、reload window、receiver owner、rollback owner 與 no-secret-value evidence。", }, "docker_compose_systemd_host_config": { "coverage_status": "repo_only_inventory_ready_needs_live_owner_evidence", "coverage_percent": 50, "evidence_refs": [ "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", "docs/security/HOST-SERVICE-CONFIG-INVENTORY.md", "docs/security/host-service-config-inventory.snapshot.json", "docs/security/DEV-HOSTS-112-111-168-OBSERVE-ONLY-MAPPING.md", ], "current_gap": "repo-only 清冊已納入 9 個 surface;仍缺 110 / 188 live hash、restart window、rollback owner 與 post-check 指標。", "next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、restart window、rollback owner 與 post-check 指標。", }, "ssh_firewall_network_access": { "coverage_status": "repo_only_inventory_ready_needs_live_owner_evidence", "coverage_percent": 54, "evidence_refs": [ "docs/HARD_RULES.md", "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", "docs/security/SSH-NETWORK-ACCESS-INVENTORY.md", "docs/security/ssh-network-access-inventory.snapshot.json", ], "current_gap": "repo-only 清冊已納入 16 個 SSH / network access surface;仍缺 live firewall / sudoers / known_hosts / NetworkPolicy / NodePort / WireGuard evidence、network owner 與 rollback owner。", "next_owner_action": "補 owner-provided live hash / disposition、host key pinning、firewall owner、NetworkPolicy / NodePort owner、WireGuard owner、maintenance window、rollback owner 與 post-check 指標。", }, "ai_provider_model_routing": { "coverage_status": "policy_ready_needs_dry_run_pack", "coverage_percent": 60, "evidence_refs": [ "docs/HARD_RULES.md", "docs/ai", ], "current_gap": "模型 / provider / Ollama proxy 切換需 dry-run、benchmark、成本與 privacy review;目前不切 production。", "next_owner_action": "補 provider owner、fallback order、cost review、privacy review、benchmark 與 rollback owner。", }, "product_surface_runtime_routes": { "coverage_status": "scope_inventory_ready", "coverage_percent": 72, "evidence_refs": [ "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", "docs/security/VIBEWORK-IWOOOS-ONBOARDING-HANDOFF.md", "docs/security/AGENT-BOUNTY-IWOOOS-ONBOARDING-HANDOFF.md", ], "current_gap": "跨產品 owner response 尚未 accepted;產品 route / admin / webhook 仍需逐產品補證。", "next_owner_action": "補 AWOOOI、AwoooP、IwoooS、VibeWork、agent-bounty-protocol 與公開網站 owner response。", }, "security_evidence_tooling": { "coverage_status": "guard_ready", "coverage_percent": 86, "evidence_refs": [ "scripts/security/security-mirror-progress-guard.py", "scripts/security/high-value-config-change-gate.py", "scripts/security/high-value-config-owner-packet.py", "docs/security/high-value-config-change-gate.snapshot.json", ], "current_gap": "guard 已可重跑,但尚未接 blocking CI;本階段刻意維持低摩擦。", "next_owner_action": "維持 guard / doc secret sanity;若要 CI blocking 需另開人工批准與 rollout plan。", }, } FALSE_BOUNDARIES = { "runtime_execution_authorized": False, "host_write_authorized": False, "host_live_conf_read_authorized": False, "nginx_test_authorized": False, "public_gateway_reload_authorized": False, "public_route_change_authorized": False, "admin_route_change_authorized": False, "websocket_route_change_authorized": False, "acme_challenge_change_authorized": False, "route_smoke_authorized": False, "rollback_executed": False, "nginx_reload_authorized": False, "dns_tls_change_authorized": False, "certbot_renew_authorized": False, "argocd_sync_authorized": False, "kubectl_action_authorized": False, "backup_run_authorized": False, "restore_run_authorized": False, "restore_drill_authorized": False, "offsite_sync_authorized": False, "offsite_remote_delete_authorized": False, "credential_escrow_marker_write_authorized": False, "retention_change_authorized": False, "restic_prune_authorized": False, "rclone_config_authorized": False, "velero_restore_authorized": False, "workflow_modification_authorized": False, "runner_change_authorized": False, "refs_sync_authorized": False, "force_push_authorized": False, "prometheus_reload_authorized": False, "alertmanager_reload_authorized": False, "grafana_dashboard_apply_authorized": False, "signoz_rule_apply_authorized": False, "sentry_deploy_authorized": False, "langfuse_config_change_authorized": False, "otel_collector_reload_authorized": False, "receiver_route_change_authorized": False, "silence_policy_change_authorized": False, "telegram_send_authorized": False, "notification_route_change_authorized": False, "webhook_receiver_change_authorized": False, "remote_write_change_authorized": False, "exporter_deploy_authorized": False, "live_alert_fire_authorized": False, "alert_chain_smoke_authorized": False, "secret_value_collection_allowed": False, "active_scan_authorized": False, "agent_bounty_runtime_authorized": False, "payout_or_withdrawal_authorized": False, "action_buttons_allowed": False, } def git_short_sha(root: Path) -> str: try: result = subprocess.run( ["git", "rev-parse", "--short", "HEAD"], cwd=root, check=True, capture_output=True, text=True, ) return result.stdout.strip() except Exception: return "unknown" def load_gate_categories(root: Path) -> list[Any]: gate_path = root / "scripts" / "security" / "high-value-config-change-gate.py" namespace = runpy.run_path(str(gate_path)) return list(namespace["CATEGORIES"]) def category_to_dict(category: Any) -> dict[str, Any]: status = CONTROL_STATUS_BY_CATEGORY[category.category_id] return { "category_id": category.category_id, "label": category.label, "priority": category.priority, "control_tier": category.control_tier, "required_gate": category.required_gate, "coverage_status": status["coverage_status"], "coverage_percent": status["coverage_percent"], "patterns": list(category.patterns), "required_validation": list(category.required_validation), "evidence_refs": status["evidence_refs"], "current_gap": status["current_gap"], "next_owner_action": status["next_owner_action"], "owner_response_required": True, "owner_response_received": False, "owner_response_accepted": False, "runtime_gate_open": False, "action_buttons_allowed": False, } def count_by(items: list[dict[str, Any]], key: str, value: Any) -> int: return sum(1 for item in items if item[key] == value) def build_report(root: Path, generated_at: str | None) -> dict[str, Any]: report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") categories = [category_to_dict(category) for category in load_gate_categories(root)] c0_categories = [item for item in categories if item["control_tier"] == "C0"] c1_categories = [item for item in categories if item["control_tier"] == "C1"] needs_live_evidence = [ item for item in categories if item["coverage_status"] in { "gate_defined_needs_runtime_evidence", "policy_defined_needs_restore_drill_owner", "repo_only_inventory_ready_needs_restore_drill_owner", "repo_only_inventory_ready_needs_live_route_evidence", "repo_only_preflight_contract_ready_needs_owner_live_diff", "policy_ready_needs_drift_evidence", "inventory_needed", "repo_only_inventory_ready_needs_live_owner_evidence", "policy_ready_needs_network_matrix", "policy_ready_needs_dry_run_pack", } ] average_coverage = round(sum(item["coverage_percent"] for item in categories) / len(categories)) lowest_categories = sorted(categories, key=lambda item: item["coverage_percent"])[:4] return { "schema_version": "high_value_config_control_coverage_v1", "generated_at": report_time, "git_commit": git_short_sha(root), "source_category_definition": "scripts/security/high-value-config-change-gate.py", "status": "coverage_matrix_ready", "summary": { "category_count": len(categories), "c0_category_count": len(c0_categories), "c1_category_count": len(c1_categories), "c2_category_count": count_by(categories, "control_tier", "C2"), "c3_category_count": count_by(categories, "control_tier", "C3"), "registered_control_count": len(categories), "owner_response_required_count": len(categories), "owner_response_received_count": 0, "owner_response_accepted_count": 0, "runtime_gate_count": 0, "action_button_count": 0, "average_coverage_percent": average_coverage, "needs_live_evidence_count": len(needs_live_evidence), "lowest_coverage_category_count": len(lowest_categories), }, "execution_boundaries": FALSE_BOUNDARIES, "coverage_categories": categories, "lowest_coverage_categories": [ { "category_id": item["category_id"], "label": item["label"], "coverage_percent": item["coverage_percent"], "current_gap": item["current_gap"], "next_owner_action": item["next_owner_action"], } for item in lowest_categories ], "next_collection_order": [ "nginx_public_gateway", "dns_tls_certbot", "secret_metadata", "gitea_workflow_runner_source_control", "agent_bounty_protocol_runtime", "docker_compose_systemd_host_config", "monitoring_alerting_observability", "ssh_firewall_network_access", "backup_restore_credential", ], "operator_interpretation": [ "這是全域配置控管覆蓋矩陣,不是單次 git diff 變更分類。", "所有 category 都已有高價值配置 Gate 註冊與 owner response 欄位,但 owner response received / accepted 仍為 0。", "C0 / C1 coverage percent 只代表只讀框架成熟度,不代表 runtime 可執行。", "缺 live evidence 的項目只能收 owner-provided redacted evidence,不得主動 SSH、reload、scan 或讀 secret value。", ], } def main() -> int: parser = argparse.ArgumentParser(description="IwoooS 高價值配置控管覆蓋矩陣") parser.add_argument("--root", default=".", help="repo root") parser.add_argument("--output", help="寫出 JSON 報告") parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用") args = parser.parse_args() root = Path(args.root).resolve() report = build_report(root, args.generated_at) payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) if args.output: output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) output.write_text(payload + "\n", encoding="utf-8") else: print(payload) summary = report["summary"] print( "HIGH_VALUE_CONFIG_CONTROL_COVERAGE_OK " f"categories={summary['category_count']} " f"c0={summary['c0_category_count']} " f"avg={summary['average_coverage_percent']} " f"runtime_gate={summary['runtime_gate_count']}", file=sys.stderr, ) return 0 if __name__ == "__main__": sys.exit(main())