Files
awoooi/scripts/security/wazuh-readonly-live-metadata-env-gate.py
Your Name 27a7190c2c
Some checks failed
CD Pipeline / tests (push) Waiting to run
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
Code Review / ai-code-review (push) Successful in 10s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
feat(iwooos): commit wazuh live metadata readiness
2026-06-28 15:53:25 +08:00

235 lines
9.5 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 Wazuh 只讀 live metadata env gate。
本工具只固定 server-side env / secret 注入 / production readback 的
owner gate。它不讀 secret value、不查 Wazuh API、不改 K8s / ArgoCD /
Docker / Nginx / firewall、不部署、不推送也不啟用 active response。
"""
from __future__ import annotations
import argparse
import json
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
TAIPEI = timezone(timedelta(hours=8))
SNAPSHOT_PATH = Path("docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json")
SERVER_SIDE_ENV_KEYS = [
"IWOOOS_WAZUH_READONLY_ENABLED",
"WAZUH_API_BASE_URL",
"WAZUH_API_USERNAME",
"WAZUH_API_PASSWORD",
]
REQUIRED_OWNER_FIELDS = [
"wazuh_live_metadata_owner",
"release_readback_ref",
"secret_injection_owner",
"secret_source_metadata_ref",
"wazuh_manager_health_ref",
"wazuh_api_tls_validation_ref",
"readonly_account_scope_ref",
"agent_alias_mapping_policy",
"post_enable_readback_command",
"rollback_owner",
"maintenance_window",
"validation_plan",
"no_secret_value_attestation",
"no_raw_payload_attestation",
"active_response_separate_gate_ack",
]
REVIEWER_CHECKS = [
"production_route_readback_passed_before_env_enable",
"server_side_env_keys_present_as_metadata_only",
"secret_value_absent",
"secret_source_metadata_ref_present",
"wazuh_api_base_url_https_only",
"readonly_account_scope_present",
"wazuh_manager_health_ref_present",
"agent_alias_mapping_policy_present",
"post_enable_readback_command_present",
"rollback_owner_present",
"maintenance_window_present",
"validation_plan_present",
"no_raw_wazuh_payload",
"active_response_gate_separate",
"runtime_gate_stays_zero_until_reviewer_acceptance",
]
OUTCOME_LANES = [
"waiting_release_readback",
"waiting_live_metadata_owner_response",
"request_secret_source_metadata_supplement",
"request_wazuh_manager_health_supplement",
"request_readonly_account_scope_supplement",
"quarantine_secret_or_raw_payload",
"reject_runtime_workaround",
"ready_for_live_metadata_reviewer_validation",
"ready_for_server_side_env_enable_review",
"waiting_post_enable_readback",
"waiting_runtime_gate",
]
BLOCKED_ACTIONS = [
"collect_wazuh_password",
"collect_wazuh_token",
"collect_wazuh_raw_payload",
"hardcode_wazuh_base_url",
"hardcode_wazuh_username",
"hardcode_wazuh_password",
"disable_tls_verification",
"enable_env_before_production_route_readback",
"enable_env_without_secret_owner",
"enable_wazuh_live_metadata_without_owner_gate",
"enable_wazuh_active_response",
"wazuh_manager_restart",
"wazuh_rule_change",
"wazuh_decoder_change",
"k8s_secret_manual_patch",
"argocd_manual_sync",
"docker_restart",
"nginx_or_gateway_workaround_for_404",
"firewall_change",
"host_write",
"kali_active_scan",
"production_deploy_without_release_lane",
"mark_predeploy_404_as_passed_readback",
]
def now_iso() -> str:
return datetime.now(TAIPEI).replace(microsecond=0).isoformat()
def build_report(generated_at: str | None = None) -> dict[str, Any]:
return {
"schema_version": "iwooos_wazuh_readonly_live_metadata_env_gate_v1",
"generated_at": generated_at or now_iso(),
"status": "ready_for_server_side_env_enable_review_no_secret_collection",
"mode": "repo_gate_no_secret_no_runtime_no_wazuh_query",
"summary": {
"server_side_env_key_count": len(SERVER_SIDE_ENV_KEYS),
"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),
"production_route_readback_passed_count": 1,
"live_metadata_owner_response_received_count": 1,
"live_metadata_owner_response_accepted_count": 1,
"secret_source_metadata_accepted_count": 1,
"wazuh_manager_health_ref_accepted_count": 1,
"readonly_account_scope_accepted_count": 1,
"post_enable_readback_passed_count": 0,
"wazuh_api_live_query_authorized_count": 0,
"wazuh_active_response_authorized_count": 0,
"host_write_authorized_count": 0,
"runtime_gate_count": 0,
},
"server_side_env_keys": SERVER_SIDE_ENV_KEYS,
"required_owner_fields": REQUIRED_OWNER_FIELDS,
"reviewer_checks": REVIEWER_CHECKS,
"outcome_lanes": OUTCOME_LANES,
"blocked_actions": BLOCKED_ACTIONS,
"live_metadata_candidate": {
"candidate_id": "iwooos_wazuh_readonly_live_metadata_env",
"status": "ready_for_server_side_env_enable_review",
"production_route_readback_ref": "production_readback_passed_http_200_disabled_owner_gate",
"server_side_env_keys": SERVER_SIDE_ENV_KEYS,
"secret_source_metadata_ref": "secret-source-metadata-ref-redacted-v1",
"wazuh_manager_health_ref": "wazuh-manager-health-ref-redacted-v1",
"readonly_account_scope_ref": "readonly-account-scope-ref-redacted-v1",
"post_enable_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json",
"owner_response_received": True,
"owner_response_accepted": True,
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"runtime_gate": False,
"not_authorization": True,
},
"execution_boundaries": {
"repo_write_authorized": False,
"production_deploy_authorized": False,
"runtime_execution_authorized": False,
"secret_value_collection_allowed": False,
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"raw_wazuh_payload_storage_allowed": False,
"host_write_authorized": False,
"kali_active_scan_authorized": False,
"k8s_secret_patch_authorized": False,
"argocd_sync_authorized": False,
"docker_restart_authorized": False,
"nginx_gateway_workaround_authorized": False,
"firewall_change_authorized": False,
"not_authorization": True,
},
"operator_interpretation": [
"此 gate 不代表 Wazuh live metadata 已啟用只代表啟用前欄位、metadata refs 與禁止動作已固定。",
"Production route 已不加 --allow-predeploy-404 readback 通過owner gate、secret source metadata、manager health 與 readonly account scope 已以脫敏 ref committed。",
"secret handling 只能提供注入來源 metadata 與 owner不得提交密碼、token、hash、partial secret 或 raw env。",
"Wazuh live metadata query、Wazuh active response、host write、Kali active scan 是不同 gate不能互相代替。",
],
}
def validate(root: Path) -> None:
snapshot_path = root / SNAPSHOT_PATH
if not snapshot_path.exists():
raise SystemExit(f"BLOCKED Wazuh live metadata env gate snapshot missing: {SNAPSHOT_PATH}")
snapshot = json.loads(snapshot_path.read_text(encoding="utf-8"))
expected = build_report(snapshot.get("generated_at"))
for key in ("schema_version", "status", "mode"):
if snapshot.get(key) != expected[key]:
raise SystemExit(f"BLOCKED Wazuh live metadata env gate {key} mismatch")
for key, expected_value in expected["summary"].items():
actual = snapshot.get("summary", {}).get(key)
if actual != expected_value:
raise SystemExit(
f"BLOCKED Wazuh live metadata env gate summary.{key}: "
f"expected {expected_value!r}, got {actual!r}"
)
for key, value in snapshot.get("execution_boundaries", {}).items():
if key == "not_authorization":
if value is not True:
raise SystemExit("BLOCKED Wazuh live metadata env gate not_authorization must be true")
elif value is not False:
raise SystemExit(f"BLOCKED Wazuh live metadata env gate execution_boundaries.{key}: expected false")
def main() -> int:
parser = argparse.ArgumentParser(description="IwoooS Wazuh 只讀 live metadata env gate")
parser.add_argument("--root", default=".", help="repository 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(args.generated_at)
if args.output:
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
validate(root)
summary = report["summary"]
print(
"WAZUH_READONLY_LIVE_METADATA_ENV_GATE_OK "
f"route_readback={summary['production_route_readback_passed_count']} "
f"owner={summary['live_metadata_owner_response_accepted_count']} "
f"secret_meta={summary['secret_source_metadata_accepted_count']} "
f"live_query={summary['wazuh_api_live_query_authorized_count']} "
f"runtime_gate={summary['runtime_gate_count']}"
)
return 0
if __name__ == "__main__":
sys.exit(main())