feat(iwooos): expose Wazuh live metadata gate readback
This commit is contained in:
@@ -21,6 +21,7 @@ TAIPEI = timezone(timedelta(hours=8))
|
||||
NEXT_ROUTE_PATH = Path("apps/web/src/app/api/iwooos/wazuh/route.ts")
|
||||
BACKEND_ROUTE_PATH = Path("apps/api/src/api/v1/iwooos.py")
|
||||
BACKEND_SERVICE_PATH = Path("apps/api/src/services/iwooos_wazuh_readonly_status.py")
|
||||
BACKEND_LIVE_METADATA_GATE_SERVICE_PATH = Path("apps/api/src/services/iwooos_wazuh_live_metadata_gate.py")
|
||||
PUBLIC_PAGE_PATH = Path("apps/web/src/app/[locale]/iwooos/page.tsx")
|
||||
PUBLIC_COMPONENT_ROOT = Path("apps/web/src/components/iwooos")
|
||||
|
||||
@@ -84,6 +85,22 @@ BACKEND_SERVICE_REQUIRED_TOKENS = [
|
||||
"agent_visibility_no_false_green_count",
|
||||
]
|
||||
|
||||
BACKEND_LIVE_METADATA_GATE_REQUIRED_TOKENS = [
|
||||
"iwooos_wazuh_live_metadata_gate_readback_v1",
|
||||
"committed_snapshot_readback_with_public_safe_wazuh_route_metadata",
|
||||
"boundary_markers",
|
||||
"secret_value_collection_allowed",
|
||||
"raw_wazuh_payload_storage_allowed",
|
||||
"wazuh_api_live_query_authorized",
|
||||
"wazuh_active_response_authorized",
|
||||
"host_write_authorized",
|
||||
"runtime_gate_count",
|
||||
"not_authorization",
|
||||
"正式路由讀回",
|
||||
"機密明文收集=false",
|
||||
"原始 Wazuh 載荷保存=false",
|
||||
]
|
||||
|
||||
|
||||
FORBIDDEN_PATTERNS = [
|
||||
ForbiddenPattern(
|
||||
@@ -184,6 +201,7 @@ def collect_forbidden_matches(root: Path) -> list[dict[str, Any]]:
|
||||
("route", NEXT_ROUTE_PATH),
|
||||
("route", BACKEND_ROUTE_PATH),
|
||||
("route", BACKEND_SERVICE_PATH),
|
||||
("route", BACKEND_LIVE_METADATA_GATE_SERVICE_PATH),
|
||||
]
|
||||
targets.extend(("public_ui", path) for path in collect_public_ui_files(root))
|
||||
|
||||
@@ -214,12 +232,17 @@ def build_report(root: Path, generated_at: str | None = None) -> dict[str, Any]:
|
||||
next_route = root / NEXT_ROUTE_PATH
|
||||
backend_route = root / BACKEND_ROUTE_PATH
|
||||
backend_service = root / BACKEND_SERVICE_PATH
|
||||
backend_live_metadata_gate_service = root / BACKEND_LIVE_METADATA_GATE_SERVICE_PATH
|
||||
next_route_present = next_route.exists()
|
||||
backend_route_present = backend_route.exists()
|
||||
backend_service_present = backend_service.exists()
|
||||
backend_live_metadata_gate_service_present = backend_live_metadata_gate_service.exists()
|
||||
next_route_text = read_text(next_route) if next_route_present else ""
|
||||
backend_route_text = read_text(backend_route) if backend_route_present else ""
|
||||
backend_service_text = read_text(backend_service) if backend_service_present else ""
|
||||
backend_live_metadata_gate_service_text = (
|
||||
read_text(backend_live_metadata_gate_service) if backend_live_metadata_gate_service_present else ""
|
||||
)
|
||||
public_ui_files = collect_public_ui_files(root)
|
||||
missing_required_tokens = {
|
||||
NEXT_ROUTE_PATH.as_posix(): collect_missing_required_tokens(next_route_text, ROUTE_REQUIRED_TOKENS),
|
||||
@@ -227,6 +250,10 @@ def build_report(root: Path, generated_at: str | None = None) -> dict[str, Any]:
|
||||
BACKEND_SERVICE_PATH.as_posix(): collect_missing_required_tokens(
|
||||
backend_service_text, BACKEND_SERVICE_REQUIRED_TOKENS
|
||||
),
|
||||
BACKEND_LIVE_METADATA_GATE_SERVICE_PATH.as_posix(): collect_missing_required_tokens(
|
||||
backend_live_metadata_gate_service_text,
|
||||
BACKEND_LIVE_METADATA_GATE_REQUIRED_TOKENS,
|
||||
),
|
||||
}
|
||||
missing_required_token_count = sum(len(tokens) for tokens in missing_required_tokens.values())
|
||||
forbidden_matches = collect_forbidden_matches(root)
|
||||
@@ -236,7 +263,14 @@ def build_report(root: Path, generated_at: str | None = None) -> dict[str, Any]:
|
||||
"generated_at": generated_at or now_iso(),
|
||||
"status": (
|
||||
"pass"
|
||||
if next_route_present and backend_route_present and not missing_required_token_count and not forbidden_matches
|
||||
if (
|
||||
next_route_present
|
||||
and backend_route_present
|
||||
and backend_service_present
|
||||
and backend_live_metadata_gate_service_present
|
||||
and not missing_required_token_count
|
||||
and not forbidden_matches
|
||||
)
|
||||
else "blocked"
|
||||
),
|
||||
"mode": "repo_source_scan_no_runtime_no_secret_collection",
|
||||
@@ -244,33 +278,52 @@ def build_report(root: Path, generated_at: str | None = None) -> dict[str, Any]:
|
||||
NEXT_ROUTE_PATH.as_posix(),
|
||||
BACKEND_ROUTE_PATH.as_posix(),
|
||||
BACKEND_SERVICE_PATH.as_posix(),
|
||||
BACKEND_LIVE_METADATA_GATE_SERVICE_PATH.as_posix(),
|
||||
],
|
||||
"guarded_public_ui_paths": [path.as_posix() for path in public_ui_files],
|
||||
"required_route_tokens": {
|
||||
NEXT_ROUTE_PATH.as_posix(): ROUTE_REQUIRED_TOKENS,
|
||||
BACKEND_ROUTE_PATH.as_posix(): BACKEND_REQUIRED_TOKENS,
|
||||
BACKEND_SERVICE_PATH.as_posix(): BACKEND_SERVICE_REQUIRED_TOKENS,
|
||||
BACKEND_LIVE_METADATA_GATE_SERVICE_PATH.as_posix(): BACKEND_LIVE_METADATA_GATE_REQUIRED_TOKENS,
|
||||
},
|
||||
"forbidden_pattern_ids": [pattern.pattern_id for pattern in FORBIDDEN_PATTERNS],
|
||||
"summary": {
|
||||
"route_present_count": int(next_route_present) + int(backend_route_present) + int(backend_service_present),
|
||||
"route_present_count": (
|
||||
int(next_route_present)
|
||||
+ int(backend_route_present)
|
||||
+ int(backend_service_present)
|
||||
+ int(backend_live_metadata_gate_service_present)
|
||||
),
|
||||
"live_metadata_gate_service_present_count": 1 if backend_live_metadata_gate_service_present else 0,
|
||||
"next_route_present_count": 1 if next_route_present else 0,
|
||||
"backend_route_present_count": 1 if backend_route_present else 0,
|
||||
"backend_service_present_count": 1 if backend_service_present else 0,
|
||||
"public_ui_file_count": len(public_ui_files),
|
||||
"required_token_count": len(ROUTE_REQUIRED_TOKENS)
|
||||
+ len(BACKEND_REQUIRED_TOKENS)
|
||||
+ len(BACKEND_SERVICE_REQUIRED_TOKENS),
|
||||
+ len(BACKEND_SERVICE_REQUIRED_TOKENS)
|
||||
+ len(BACKEND_LIVE_METADATA_GATE_REQUIRED_TOKENS),
|
||||
"missing_required_token_count": missing_required_token_count,
|
||||
"forbidden_pattern_count": len(FORBIDDEN_PATTERNS),
|
||||
"forbidden_match_count": len(forbidden_matches),
|
||||
"readonly_api_default_closed_count": sum(
|
||||
"IWOOOS_WAZUH_READONLY_ENABLED" in text
|
||||
for text in [next_route_text, backend_route_text, backend_service_text]
|
||||
for text in [
|
||||
next_route_text,
|
||||
backend_route_text,
|
||||
backend_service_text,
|
||||
backend_live_metadata_gate_service_text,
|
||||
]
|
||||
),
|
||||
"server_side_env_required_count": sum(
|
||||
token in text
|
||||
for text in [next_route_text, backend_route_text, backend_service_text]
|
||||
for text in [
|
||||
next_route_text,
|
||||
backend_route_text,
|
||||
backend_service_text,
|
||||
backend_live_metadata_gate_service_text,
|
||||
]
|
||||
for token in ["WAZUH_API_BASE_URL", "WAZUH_API_USERNAME", "WAZUH_API_PASSWORD"]
|
||||
),
|
||||
"tls_disable_match_count": sum(
|
||||
@@ -325,6 +378,10 @@ def validate(root: Path) -> None:
|
||||
errors.append(f"{BACKEND_ROUTE_PATH.as_posix()}: Wazuh FastAPI 相容 route 不存在")
|
||||
if report["summary"]["backend_service_present_count"] != 1:
|
||||
errors.append(f"{BACKEND_SERVICE_PATH.as_posix()}: Wazuh FastAPI 只讀 service 不存在")
|
||||
if report["summary"]["live_metadata_gate_service_present_count"] != 1:
|
||||
errors.append(
|
||||
f"{BACKEND_LIVE_METADATA_GATE_SERVICE_PATH.as_posix()}: Wazuh 即時中繼資料閘門 service 不存在"
|
||||
)
|
||||
for path, tokens in report["missing_required_tokens"].items():
|
||||
for token in tokens:
|
||||
errors.append(f"{path}: 缺少必要只讀邊界 token {token!r}")
|
||||
|
||||
Reference in New Issue
Block a user