feat(security): 新增高價值配置覆蓋矩陣
Some checks failed
CD Pipeline / tests (push) Successful in 1m24s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-11 21:03:49 +08:00
parent d448ae3657
commit 3a3a6283c8
13 changed files with 1795 additions and 0 deletions

View File

@@ -0,0 +1,348 @@
#!/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_drift_detector_ready",
"coverage_percent": 78,
"evidence_refs": [
"docs/security/NGINX-CONFIG-DRIFT-DETECTOR.md",
"docs/security/nginx-config-drift-repo.snapshot.json",
"docs/security/DOMAIN-TLS-CERTBOT-INVENTORY.md",
],
"current_gap": "缺 owner-provided live conf 匯出與 approved maintenance window不得 SSH 擷取或 reload。",
"next_owner_action": "提供脫敏 live conf 匯出檔或確認維護窗口、rollback owner 與 route smoke plan。",
},
"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": "policy_defined_needs_restore_drill_owner",
"coverage_percent": 52,
"evidence_refs": [
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
"docs/runbooks/disaster-recovery",
],
"current_gap": "restore drill、retention、offsite escrow 與 credential owner 尚未形成 S4 owner response。",
"next_owner_action": "補 restore drill approval package、escrow owner、retention 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 responseruntime gate 必須維持 0。",
"next_owner_action": "補 repo owner、external agent owner、treasury owner、runtime gate owner 與 validation plan。",
},
"monitoring_alerting_observability": {
"coverage_status": "policy_ready_needs_drift_evidence",
"coverage_percent": 56,
"evidence_refs": [
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
"docs/security/SERVICE-HEALTH-FAILURE-NOTIFICATION-POLICY.md",
],
"current_gap": "Prometheus / Alertmanager / Grafana / SigNoz / Sentry / Langfuse 設定 drift 尚未集中收證。",
"next_owner_action": "補 rule diff、receiver diff、reload owner、failure-only notification policy 與 route smoke。",
},
"docker_compose_systemd_host_config": {
"coverage_status": "inventory_needed",
"coverage_percent": 42,
"evidence_refs": [
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
"docs/security/DEV-HOSTS-112-111-168-OBSERVE-ONLY-MAPPING.md",
],
"current_gap": "110 / 188 Docker Compose、systemd、port / volume / env 差異仍需只讀 inventory。",
"next_owner_action": "補 compose / systemd owner、restart window、rollback owner 與 post-check 指標。",
},
"ssh_firewall_network_access": {
"coverage_status": "policy_ready_needs_network_matrix",
"coverage_percent": 48,
"evidence_refs": [
"docs/HARD_RULES.md",
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
],
"current_gap": "尚未集中 ingress / egress、WireGuard、NodePort、firewall 與 known_hosts 矩陣。",
"next_owner_action": "補 target whitelist、host key policy、network owner、maintenance window 與 rollback owner。",
},
"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,
"nginx_reload_authorized": False,
"dns_tls_change_authorized": False,
"certbot_renew_authorized": False,
"argocd_sync_authorized": False,
"kubectl_action_authorized": False,
"workflow_modification_authorized": False,
"runner_change_authorized": False,
"refs_sync_authorized": False,
"force_push_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",
"policy_ready_needs_drift_evidence",
"inventory_needed",
"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",
],
"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())