feat(iwooos): add Wazuh registry export preflight
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m42s
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
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m42s
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
This commit is contained in:
@@ -29476,6 +29476,14 @@ def validate(root: Path) -> None:
|
||||
]
|
||||
)
|
||||
for expected in [
|
||||
"iwooos-wazuh-owner-evidence-preflight-board",
|
||||
"wazuhOwnerEvidencePreflight",
|
||||
"wazuh_agent_visibility_owner_evidence_required_field_count=23",
|
||||
"wazuh_agent_visibility_owner_evidence_expected_scope_alias_count=6",
|
||||
"wazuh_agent_visibility_owner_evidence_per_host_required_field_count=9",
|
||||
"wazuh_agent_visibility_owner_evidence_registry_export_received_count=0",
|
||||
"wazuh_agent_visibility_owner_evidence_registry_export_accepted_count=0",
|
||||
"wazuh_agent_visibility_owner_evidence_runtime_gate_count=0",
|
||||
"iwooos-wazuh-managed-host-coverage-board",
|
||||
"wazuhManagedHostCoverage",
|
||||
"wazuh_managed_host_coverage_manager_registry_accepted_count=0",
|
||||
|
||||
@@ -24,6 +24,7 @@ REQUIRED_FIELDS = [
|
||||
"decision",
|
||||
"decision_reason",
|
||||
"affected_scope",
|
||||
"collection_method",
|
||||
"agent_total",
|
||||
"agent_active",
|
||||
"agent_disconnected",
|
||||
@@ -31,6 +32,10 @@ REQUIRED_FIELDS = [
|
||||
"last_seen_window_start",
|
||||
"last_seen_window_end",
|
||||
"registry_collected_at",
|
||||
"registry_export_scope_aliases",
|
||||
"per_host_registry_matrix",
|
||||
"registry_gap_reason_by_alias",
|
||||
"registry_export_summary_ref",
|
||||
"manager_health_ref",
|
||||
"dashboard_api_status_ref",
|
||||
"redacted_evidence_refs",
|
||||
@@ -42,6 +47,9 @@ REQUIRED_FIELDS = [
|
||||
REVIEWER_CHECKS = [
|
||||
"欄位齊全且皆為脫敏 metadata。",
|
||||
"agent_total 不可小於 active + disconnected + never_connected。",
|
||||
"collection_method 只能是既有唯讀 API 或 manager 端脫敏 CLI 匯出,不可要求 secret 明文。",
|
||||
"registry_export_scope_aliases 必須完整覆蓋 6 個公開節點別名。",
|
||||
"per_host_registry_matrix 每列只能使用公開別名,不得包含內網位址、agent 原名或 raw payload。",
|
||||
"last_seen 時間窗需能覆蓋事故觀察區間。",
|
||||
"manager health ref 與 dashboard API status ref 不可互相替代。",
|
||||
"redacted evidence refs 不得包含 raw payload、截圖原文或主機完整輸出。",
|
||||
@@ -68,6 +76,7 @@ FORBIDDEN_PAYLOADS = [
|
||||
"bearer_token",
|
||||
"basic_auth",
|
||||
"password",
|
||||
"token",
|
||||
"cookie",
|
||||
"private_key",
|
||||
"client_keys",
|
||||
@@ -89,6 +98,39 @@ FORBIDDEN_TEXT_PATTERNS = [
|
||||
re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
|
||||
]
|
||||
|
||||
REGISTRY_EXPORT_SCOPE_ALIASES = [
|
||||
"managed_core_node_a",
|
||||
"managed_core_node_b",
|
||||
"managed_dev_node_a",
|
||||
"managed_dev_node_b",
|
||||
"managed_control_node_a",
|
||||
"managed_control_node_b",
|
||||
]
|
||||
|
||||
REGISTRY_EXPORT_ALLOWED_COLLECTION_METHODS = [
|
||||
"wazuh_api_readonly_redacted_counts",
|
||||
"manager_agent_control_redacted_export",
|
||||
]
|
||||
|
||||
PER_HOST_REGISTRY_REQUIRED_FIELDS = [
|
||||
"node_alias",
|
||||
"scope_role",
|
||||
"registry_presence",
|
||||
"agent_status_bucket",
|
||||
"last_seen_state",
|
||||
"manager_group_ref",
|
||||
"agent_id_redacted_ref",
|
||||
"gap_reason",
|
||||
"redacted_evidence_ref",
|
||||
]
|
||||
|
||||
REGISTRY_EXPORT_REDACTION_REQUIREMENTS = [
|
||||
"只允許公開節點別名,不允許內網位址、主機原名或 agent 原名。",
|
||||
"agent id 僅能用不可逆 evidence ref,不得放完整值、雜湊、前後綴或 client key。",
|
||||
"每個缺口必須有 gap reason,不得以 Dashboard 空白或口頭說明補成綠燈。",
|
||||
"只收計數、狀態桶、時間窗與證據 ref,不收 raw API payload、完整 CLI output 或截圖原文。",
|
||||
]
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
@@ -143,11 +185,23 @@ def build_snapshot() -> dict[str, Any]:
|
||||
"reviewer_checks": REVIEWER_CHECKS,
|
||||
"outcome_lanes": OUTCOME_LANES,
|
||||
"forbidden_payloads": FORBIDDEN_PAYLOADS,
|
||||
"registry_export_contract": {
|
||||
"expected_scope_aliases": REGISTRY_EXPORT_SCOPE_ALIASES,
|
||||
"allowed_collection_methods": REGISTRY_EXPORT_ALLOWED_COLLECTION_METHODS,
|
||||
"per_host_required_fields": PER_HOST_REGISTRY_REQUIRED_FIELDS,
|
||||
"redaction_requirements": REGISTRY_EXPORT_REDACTION_REQUIREMENTS,
|
||||
"registry_export_received_count": 0,
|
||||
"registry_export_accepted_count": 0,
|
||||
},
|
||||
"summary": {
|
||||
"required_field_count": len(REQUIRED_FIELDS),
|
||||
"reviewer_check_count": len(REVIEWER_CHECKS),
|
||||
"outcome_lane_count": len(OUTCOME_LANES),
|
||||
"forbidden_payload_count": len(FORBIDDEN_PAYLOADS),
|
||||
"expected_scope_alias_count": len(REGISTRY_EXPORT_SCOPE_ALIASES),
|
||||
"per_host_required_field_count": len(PER_HOST_REGISTRY_REQUIRED_FIELDS),
|
||||
"registry_export_received_count": 0,
|
||||
"registry_export_accepted_count": 0,
|
||||
"owner_evidence_received_count": 0,
|
||||
"owner_evidence_accepted_count": 0,
|
||||
"owner_evidence_rejected_count": 0,
|
||||
@@ -171,6 +225,7 @@ def build_snapshot() -> dict[str, Any]:
|
||||
"operator_interpretation": [
|
||||
"這是 Wazuh agent registry 脫敏證據收件前的預檢,不代表已收到或已接受 owner evidence。",
|
||||
"agent service active、TCP 連線存在、Dashboard 可見或口頭宣稱都不可替代 manager registry counts。",
|
||||
"逐主機 registry export 必須使用固定公開節點別名與狀態桶,不能把 agent 原名或內網識別資訊帶到前台。",
|
||||
"若 evidence 夾帶 raw log、未脫敏截圖、內網位址、agent 原名或 secret,必須隔離,不得渲染到前台。",
|
||||
"任何 active response、host write、firewall、Nginx、Docker、K8s 或 secret 變更都要切獨立人工批准。",
|
||||
],
|
||||
@@ -194,13 +249,48 @@ def validate(root: Path) -> None:
|
||||
assert_equal("reviewer_checks", snapshot.get("reviewer_checks"), REVIEWER_CHECKS)
|
||||
assert_equal("outcome_lanes", snapshot.get("outcome_lanes"), OUTCOME_LANES)
|
||||
assert_equal("forbidden_payloads", snapshot.get("forbidden_payloads"), FORBIDDEN_PAYLOADS)
|
||||
contract = snapshot.get("registry_export_contract", {})
|
||||
assert_equal(
|
||||
"registry_export_contract.expected_scope_aliases",
|
||||
contract.get("expected_scope_aliases"),
|
||||
REGISTRY_EXPORT_SCOPE_ALIASES,
|
||||
)
|
||||
assert_equal(
|
||||
"registry_export_contract.allowed_collection_methods",
|
||||
contract.get("allowed_collection_methods"),
|
||||
REGISTRY_EXPORT_ALLOWED_COLLECTION_METHODS,
|
||||
)
|
||||
assert_equal(
|
||||
"registry_export_contract.per_host_required_fields",
|
||||
contract.get("per_host_required_fields"),
|
||||
PER_HOST_REGISTRY_REQUIRED_FIELDS,
|
||||
)
|
||||
assert_equal(
|
||||
"registry_export_contract.redaction_requirements",
|
||||
contract.get("redaction_requirements"),
|
||||
REGISTRY_EXPORT_REDACTION_REQUIREMENTS,
|
||||
)
|
||||
assert_zero("registry_export_contract.registry_export_received_count", contract.get("registry_export_received_count"))
|
||||
assert_zero("registry_export_contract.registry_export_accepted_count", contract.get("registry_export_accepted_count"))
|
||||
|
||||
summary = snapshot.get("summary", {})
|
||||
assert_equal("summary.required_field_count", summary.get("required_field_count"), len(REQUIRED_FIELDS))
|
||||
assert_equal("summary.reviewer_check_count", summary.get("reviewer_check_count"), len(REVIEWER_CHECKS))
|
||||
assert_equal("summary.outcome_lane_count", summary.get("outcome_lane_count"), len(OUTCOME_LANES))
|
||||
assert_equal("summary.forbidden_payload_count", summary.get("forbidden_payload_count"), len(FORBIDDEN_PAYLOADS))
|
||||
assert_equal(
|
||||
"summary.expected_scope_alias_count",
|
||||
summary.get("expected_scope_alias_count"),
|
||||
len(REGISTRY_EXPORT_SCOPE_ALIASES),
|
||||
)
|
||||
assert_equal(
|
||||
"summary.per_host_required_field_count",
|
||||
summary.get("per_host_required_field_count"),
|
||||
len(PER_HOST_REGISTRY_REQUIRED_FIELDS),
|
||||
)
|
||||
for key in [
|
||||
"registry_export_received_count",
|
||||
"registry_export_accepted_count",
|
||||
"owner_evidence_received_count",
|
||||
"owner_evidence_accepted_count",
|
||||
"owner_evidence_rejected_count",
|
||||
@@ -245,6 +335,8 @@ def main() -> None:
|
||||
"WAZUH_AGENT_VISIBILITY_OWNER_EVIDENCE_PREFLIGHT_OK "
|
||||
f"fields={summary['required_field_count']} "
|
||||
f"checks={summary['reviewer_check_count']} "
|
||||
f"aliases={summary['expected_scope_alias_count']} "
|
||||
f"export_received={summary['registry_export_received_count']} "
|
||||
f"received={summary['owner_evidence_received_count']} "
|
||||
f"accepted={summary['owner_evidence_accepted_count']} "
|
||||
f"runtime_gate={summary['runtime_gate_count']}"
|
||||
|
||||
Reference in New Issue
Block a user