483 lines
27 KiB
Python
483 lines
27 KiB
Python
#!/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": "emergency_change_backfill_ready_needs_owner_live_diff",
|
||
"coverage_percent": 90,
|
||
"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/security/PUBLIC-GATEWAY-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/public-gateway-owner-response-acceptance.snapshot.json",
|
||
"docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md",
|
||
"docs/security/public-gateway-rendered-diff-acceptance.snapshot.json",
|
||
"docs/schemas/public_gateway_preflight_inventory_v1.schema.json",
|
||
],
|
||
"current_gap": "已固定 owner response acceptance、手動 / 緊急 gateway 變更回補欄位與 rendered diff evidence acceptance 只讀帳本;owner response、live conf、rendered diff evidence、nginx -t evidence、route smoke evidence、maintenance window 與 rollback owner 仍全部為 0。",
|
||
"next_owner_action": "補 public gateway owner 回覆、owner-provided live conf、source-to-live rendered diff ref、nginx -t evidence ref、route smoke evidence ref、change intent / break-glass、route health impact、rollback validation、maintenance window 與 rollback owner。",
|
||
},
|
||
"dns_tls_certbot": {
|
||
"coverage_status": "owner_response_acceptance_ledger_ready_needs_certificate_owner_evidence",
|
||
"coverage_percent": 78,
|
||
"evidence_refs": [
|
||
"docs/security/DOMAIN-TLS-CERTBOT-INVENTORY.md",
|
||
"docs/security/domain-tls-certbot-inventory.snapshot.json",
|
||
"docs/security/DOMAIN-TLS-CERTBOT-OWNER-CONFIRMATION-REQUEST.md",
|
||
"docs/security/domain-tls-certbot-owner-confirmation-request.snapshot.json",
|
||
"docs/security/DOMAIN-TLS-CERTBOT-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/domain-tls-certbot-owner-response-acceptance.snapshot.json",
|
||
],
|
||
"current_gap": "已固定 4 份 DNS / TLS / certbot owner response acceptance candidate;仍缺 owner response、certificate coverage metadata ref、expiry metadata ref、renewal owner、ACME route owner、maintenance window、rollback owner 與 validation plan。",
|
||
"next_owner_action": "補 SAN / wildcard / 共用憑證覆蓋依據 ref、到期 metadata ref、renewal owner、ACME route owner、維護窗口、rollback owner 與 validation plan。",
|
||
},
|
||
"k8s_production_gitops": {
|
||
"coverage_status": "change_evidence_acceptance_ready_needs_gitops_owner_evidence",
|
||
"coverage_percent": 64,
|
||
"evidence_refs": [
|
||
"docs/security/HIGH-VALUE-CONFIG-CHANGE-GATE.md",
|
||
"docs/security/K8S-ARGOCD-MANIFEST-INVENTORY.md",
|
||
"docs/security/k8s-argocd-manifest-inventory.snapshot.json",
|
||
"docs/security/K8S-ARGOCD-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/k8s-argocd-owner-response-acceptance.snapshot.json",
|
||
"docs/security/K8S-ARGOCD-CHANGE-EVIDENCE-ACCEPTANCE.md",
|
||
"docs/security/k8s-argocd-change-evidence-acceptance.snapshot.json",
|
||
"k8s/awoooi-prod",
|
||
"k8s/argocd",
|
||
],
|
||
"current_gap": "已固定 owner response acceptance 與 GitOps 變更證據驗收只讀帳本;proposed commit、rendered manifest diff、ArgoCD app / sync revision、health before / after、rollout、route smoke、metrics / alert、rollback、maintenance window 與 post-check evidence 仍全部為 0。",
|
||
"next_owner_action": "補 GitOps owner 回覆、proposed commit ref、rendered manifest diff ref、ArgoCD app / sync revision ref、health before / after、rollout、route smoke、metrics / alert、blast radius、rollback revision、maintenance window 與 post-check owner。",
|
||
},
|
||
"secret_metadata": {
|
||
"coverage_status": "secret_injection_change_evidence_acceptance_ready_needs_owner_evidence",
|
||
"coverage_percent": 68,
|
||
"evidence_refs": [
|
||
"docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-INVENTORY.md",
|
||
"docs/security/source-control-workflow-secret-name-inventory.snapshot.json",
|
||
"docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-OWNER-RESPONSE.md",
|
||
"docs/security/source-control-workflow-secret-name-owner-response.snapshot.json",
|
||
"docs/security/CD-RUNNER-SECRET-INJECTION-CHANGE-EVIDENCE-ACCEPTANCE.md",
|
||
"docs/security/cd-runner-secret-injection-change-evidence-acceptance.snapshot.json",
|
||
"docs/security/SECRETS_REFERENCE.md",
|
||
],
|
||
"current_gap": "已固定 secret name / injection owner 變更證據驗收帳本;secret value、hash、partial token、secret store read、secret rotation、repo secret change 與 injection path change 仍全部為 0。",
|
||
"next_owner_action": "補 secret name parity ref、injection route owner、rotation owner、guard result ref、rollback owner、post-check evidence 與 redacted evidence refs。",
|
||
},
|
||
"gitea_workflow_runner_source_control": {
|
||
"coverage_status": "cd_runner_secret_injection_change_evidence_acceptance_ready_needs_owner_evidence",
|
||
"coverage_percent": 72,
|
||
"evidence_refs": [
|
||
"docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-INVENTORY.md",
|
||
"docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-OWNER-RESPONSE.md",
|
||
"docs/security/CD-RUNNER-SECRET-INJECTION-CHANGE-EVIDENCE-ACCEPTANCE.md",
|
||
"docs/security/cd-runner-secret-injection-change-evidence-acceptance.snapshot.json",
|
||
"docs/security/SOURCE-CONTROL-PRIMARY-READINESS-GATE.md",
|
||
],
|
||
"current_gap": "已固定 CD / runner / secret injection 變更證據驗收帳本;workflow diff、runner attestation、secret parity、guard result、deploy marker readback、rollback owner 與 post-check evidence 仍全部為 0。",
|
||
"next_owner_action": "補 workflow diff ref、runner owner attestation、secret name parity ref、Gitea run readback、guard result、maintenance window、rollback owner 與 post-check evidence。",
|
||
},
|
||
"public_admin_api_runtime_config": {
|
||
"coverage_status": "change_evidence_acceptance_ready_needs_runtime_config_owner_evidence",
|
||
"coverage_percent": 64,
|
||
"evidence_refs": [
|
||
"docs/HARD_RULES.md",
|
||
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
|
||
"docs/security/PUBLIC-RUNTIME-CONFIG-CHANGE-EVIDENCE-ACCEPTANCE.md",
|
||
"docs/security/public-runtime-config-change-evidence-acceptance.snapshot.json",
|
||
],
|
||
"current_gap": "已固定 Public / Admin / API runtime config 變更證據驗收只讀帳本;affected route、admin/auth boundary、API readback、CORS diff、frontend env diff、i18n redaction、webhook owner、desktop/mobile smoke、sensitive string scan、rollback 與 post-check evidence 仍全部為 0。",
|
||
"next_owner_action": "補 affected route refs、admin/auth boundary、API contract readback、CORS origin diff、frontend env diff、i18n redaction review、webhook/callback owner、desktop/mobile smoke、sensitive string scan、rollback owner 與 post-check evidence。",
|
||
},
|
||
"backup_restore_credential": {
|
||
"coverage_status": "owner_response_acceptance_ledger_ready_needs_restore_drill_owner",
|
||
"coverage_percent": 62,
|
||
"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/security/BACKUP-RESTORE-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/backup-restore-owner-response-acceptance.snapshot.json",
|
||
"docs/schemas/backup_restore_escrow_inventory_v1.schema.json",
|
||
],
|
||
"current_gap": "已固定 owner response acceptance 只讀帳本;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、validation plan 與 no-secret-value evidence。",
|
||
},
|
||
"agent_bounty_protocol_runtime": {
|
||
"coverage_status": "owner_request_draft_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/AGENT-BOUNTY-OWNER-REQUEST-DRAFT.md",
|
||
"docs/security/agent-bounty-owner-request-draft.snapshot.json",
|
||
"docs/security/GITHUB-TARGET-OWNER-DECISION-RESPONSE.md",
|
||
],
|
||
"current_gap": "owner request draft 已固定 11 份草稿;尚未收到 runtime / MCP / A2A / treasury / payout owner response,runtime gate 必須維持 0。",
|
||
"next_owner_action": "補 repo owner、external agent owner、treasury owner、runtime gate owner、maintenance window、rollback owner 與 validation plan。",
|
||
},
|
||
"monitoring_alerting_observability": {
|
||
"coverage_status": "owner_response_acceptance_ledger_ready_needs_live_route_evidence",
|
||
"coverage_percent": 66,
|
||
"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/security/MONITORING-OWNER-REQUEST-DRAFT.md",
|
||
"docs/security/monitoring-owner-request-draft.snapshot.json",
|
||
"docs/security/MONITORING-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/monitoring-owner-response-acceptance.snapshot.json",
|
||
"docs/schemas/monitoring_alerting_observability_inventory_v1.schema.json",
|
||
],
|
||
"current_gap": "已固定 60 份 monitoring / alerting / observability owner response acceptance candidate;仍缺 owner response、live config hash、rule diff、receiver diff、reload owner、route smoke、receipt proof、noise budget owner 與 maintenance window。",
|
||
"next_owner_action": "補 Prometheus / Alertmanager / Grafana / SigNoz / Sentry / Langfuse / Telegram owner、live drift evidence、reload window、receiver owner、route smoke plan、noise budget、rollback owner 與 no-secret-value evidence。",
|
||
},
|
||
"docker_compose_systemd_host_config": {
|
||
"coverage_status": "incident_recovery_backfill_ready_needs_live_owner_evidence",
|
||
"coverage_percent": 58,
|
||
"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/HOST-SERVICE-OWNER-REQUEST-DRAFT.md",
|
||
"docs/security/host-service-owner-request-draft.snapshot.json",
|
||
"docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/host-service-owner-response-acceptance.snapshot.json",
|
||
"docs/security/DEV-HOSTS-112-111-168-OBSERVE-ONLY-MAPPING.md",
|
||
],
|
||
"current_gap": "已固定 9 份 Docker / systemd / host service owner response acceptance candidate,並加入事故恢復、依賴圖、port binding、cold-start sequence、source-of-truth 與 daemon / runner 競爭回補欄位;仍缺 owner response、110 / 188 live hash、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence。",
|
||
"next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、maintenance / restart window、rollback owner、post-check plan、disable switch、source-of-truth、服務依賴圖、port binding、cold-start sequence、incident recovery evidence 與 daemon / runner contention review。",
|
||
},
|
||
"ssh_firewall_network_access": {
|
||
"coverage_status": "incident_change_evidence_acceptance_ready_needs_network_owner_evidence",
|
||
"coverage_percent": 62,
|
||
"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",
|
||
"docs/security/SSH-NETWORK-OWNER-REQUEST-DRAFT.md",
|
||
"docs/security/ssh-network-owner-request-draft.snapshot.json",
|
||
"docs/security/SSH-NETWORK-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/ssh-network-owner-response-acceptance.snapshot.json",
|
||
"docs/security/PORT-FIREWALL-CHANGE-EVIDENCE-ACCEPTANCE.md",
|
||
"docs/security/port-firewall-change-evidence-acceptance.snapshot.json",
|
||
],
|
||
"current_gap": "owner response acceptance 帳本已固定 16 個 SSH / network acceptance candidate,端口 / 防火牆事故型變更證據驗收帳本已固定 14 個 change evidence candidate;仍缺 owner-provided change / incident evidence、actor、before / after state、service health impact、operator notification、cross-project sync、maintenance window、rollback owner 與 post-check evidence。",
|
||
"next_owner_action": "補端口 / 防火牆變更的 change / incident ref、actor role / team、affected scope、before / after state、service dependency、customer impact、service health impact、operator notification、cross-project sync、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",
|
||
"docs/security/AGENT-BOUNTY-OWNER-REQUEST-DRAFT.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_api_read_authorized": False,
|
||
"argocd_sync_authorized": False,
|
||
"kubectl_action_authorized": False,
|
||
"helm_upgrade_authorized": False,
|
||
"network_policy_apply_authorized": False,
|
||
"nodeport_change_authorized": False,
|
||
"rbac_change_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,
|
||
"runtime_config_change_authorized": False,
|
||
"api_route_change_authorized": False,
|
||
"cors_change_authorized": False,
|
||
"frontend_env_change_authorized": False,
|
||
"middleware_auth_change_authorized": False,
|
||
"callback_url_change_authorized": False,
|
||
"webhook_secret_change_authorized": False,
|
||
"security_header_change_authorized": False,
|
||
"cookie_policy_change_authorized": False,
|
||
"csrf_disable_authorized": False,
|
||
"rate_limit_disable_authorized": False,
|
||
"api_contract_change_authorized": False,
|
||
"i18n_public_text_internal_identity_allowed": False,
|
||
"internal_ip_exposure_allowed": False,
|
||
"repo_namespace_exposure_allowed": False,
|
||
"owner_namespace_exposure_allowed": False,
|
||
"internal_status_code_exposure_allowed": False,
|
||
"internal_transcript_exposure_allowed": False,
|
||
"raw_payload_storage_allowed": False,
|
||
"desktop_mobile_smoke_authorized": False,
|
||
"database_migration_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",
|
||
"owner_response_acceptance_ledger_ready_needs_owner_live_diff",
|
||
"rendered_diff_acceptance_ledger_ready_needs_owner_live_diff",
|
||
"emergency_change_backfill_ready_needs_owner_live_diff",
|
||
"owner_response_acceptance_ledger_ready_needs_runtime_evidence",
|
||
"change_evidence_acceptance_ready_needs_gitops_owner_evidence",
|
||
"secret_injection_change_evidence_acceptance_ready_needs_owner_evidence",
|
||
"cd_runner_secret_injection_change_evidence_acceptance_ready_needs_owner_evidence",
|
||
"change_evidence_acceptance_ready_needs_runtime_config_owner_evidence",
|
||
"owner_response_acceptance_ledger_ready_needs_restore_drill_owner",
|
||
"owner_response_acceptance_ledger_ready_needs_network_owner",
|
||
"owner_response_acceptance_ledger_ready_needs_live_route_evidence",
|
||
"change_evidence_acceptance_ready_needs_network_owner_evidence",
|
||
"incident_change_evidence_acceptance_ready_needs_network_owner_evidence",
|
||
"policy_ready_needs_drift_evidence",
|
||
"inventory_needed",
|
||
"repo_only_inventory_ready_needs_live_owner_evidence",
|
||
"incident_recovery_backfill_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",
|
||
"k8s_production_gitops",
|
||
"secret_metadata",
|
||
"gitea_workflow_runner_source_control",
|
||
"public_admin_api_runtime_config",
|
||
"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())
|