feat(iwooos): expose Wazuh owner evidence preflight
Some checks failed
Code Review / ai-code-review (push) Successful in 19s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-27 11:41:09 +08:00
parent 9bf2399ed1
commit fccd8874fc
9 changed files with 635 additions and 23 deletions

View File

@@ -40,6 +40,8 @@ import {
type IwoooSSecurityControlCoverageResponse,
type IwoooSWazuhLiveMetadataGateItem,
type IwoooSWazuhLiveMetadataGateResponse,
type IwoooSWazuhOwnerEvidencePreflightItem,
type IwoooSWazuhOwnerEvidencePreflightResponse,
} from '@/lib/api-client'
type PostureMetric = {
@@ -2365,14 +2367,6 @@ const wazuhLiveMetadataEnvGateBoundaries = [
'not_authorization=true',
] as const
const wazuhOwnerEvidencePreflightSummary = [
{ key: 'fields', value: '23', icon: ClipboardCheck, tone: 'steady' },
{ key: 'aliases', value: '6', icon: Server, tone: 'warn' },
{ key: 'checks', value: '10', icon: ListChecks, tone: 'steady' },
{ key: 'received', value: '0', icon: FileWarning, tone: 'locked' },
{ key: 'accepted', value: '0', icon: Lock, tone: 'locked' },
] as const
const wazuhOwnerEvidencePreflightItems: WazuhOwnerEvidencePreflightItem[] = [
{ key: 'scopeAliases', check: 'EV-0', state: '6 個別名', icon: Server, tone: 'warn' },
{ key: 'registryCounts', check: 'EV-1', state: '待脫敏計數', icon: Server, tone: 'warn' },
@@ -2384,14 +2378,25 @@ const wazuhOwnerEvidencePreflightItems: WazuhOwnerEvidencePreflightItem[] = [
{ key: 'runtimeBoundary', check: 'EV-7', state: '不開執行', icon: Lock, tone: 'locked' },
] as const
const wazuhOwnerEvidencePreflightItemKeyById: Record<IwoooSWazuhOwnerEvidencePreflightItem['item_id'], WazuhOwnerEvidencePreflightItem['key']> = {
scope_aliases: 'scopeAliases',
registry_counts: 'registryCounts',
per_host_matrix: 'perHostMatrix',
time_window: 'timeWindow',
health_refs: 'healthRefs',
redaction: 'redaction',
owner_decision: 'ownerDecision',
runtime_boundary: 'runtimeBoundary',
}
const wazuhOwnerEvidencePreflightBoundaries = [
'wazuh_agent_visibility_owner_evidence_preflight_visible=true',
'wazuh_agent_visibility_owner_evidence_required_field_count=23',
'wazuh_agent_visibility_owner_evidence_reviewer_check_count=10',
'wazuh_agent_visibility_owner_evidence_required_field_count=28',
'wazuh_agent_visibility_owner_evidence_reviewer_check_count=15',
'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_outcome_lane_count=5',
'wazuh_agent_visibility_owner_evidence_forbidden_payload_count=18',
'wazuh_agent_visibility_owner_evidence_outcome_lane_count=8',
'wazuh_agent_visibility_owner_evidence_forbidden_payload_count=22',
'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_received_count=0',
@@ -9115,6 +9120,92 @@ function IwoooSWazuhLiveMetadataEnvGateBoard() {
function IwoooSWazuhOwnerEvidencePreflightBoard() {
const t = useTranslations('iwooos.wazuhOwnerEvidencePreflight')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
const [data, setData] = useState<IwoooSWazuhOwnerEvidencePreflightResponse | null>(null)
const [loading, setLoading] = useState(true)
const [failed, setFailed] = useState(false)
useEffect(() => {
let mounted = true
async function loadPreflight() {
setLoading(true)
setFailed(false)
try {
const payload = await apiClient.getIwoooSWazuhOwnerEvidencePreflight()
if (mounted) {
setData(payload)
}
} catch {
if (mounted) {
setData(null)
setFailed(true)
}
} finally {
if (mounted) {
setLoading(false)
}
}
}
loadPreflight()
return () => {
mounted = false
}
}, [])
const summary = data?.summary
const summaryItems = [
{
key: 'fields',
value: summary ? String(summary.required_field_count) : loading ? '...' : '28',
icon: ClipboardCheck,
tone: 'steady',
},
{
key: 'aliases',
value: summary ? String(summary.expected_scope_alias_count) : loading ? '...' : '6',
icon: Server,
tone: 'warn',
},
{
key: 'checks',
value: summary ? String(summary.reviewer_check_count) : loading ? '...' : '15',
icon: ListChecks,
tone: 'steady',
},
{
key: 'received',
value: summary ? String(summary.owner_evidence_received_count) : loading ? '...' : '0',
icon: FileWarning,
tone: 'locked',
},
{
key: 'accepted',
value: summary ? String(summary.owner_evidence_accepted_count) : loading ? '...' : '0',
icon: Lock,
tone: 'locked',
},
] as const
const preflightItems = data?.items?.length
? data.items.map(item => {
const key = wazuhOwnerEvidencePreflightItemKeyById[item.item_id]
const fallback = wazuhOwnerEvidencePreflightItems.find(candidate => candidate.key === key)
return {
key,
check: item.check,
state: t(`states.${item.state_key}` as never),
icon: fallback?.icon ?? FileWarning,
tone: item.tone,
}
})
: wazuhOwnerEvidencePreflightItems
const boundaryMarkers = data?.boundary_markers?.length
? data.boundary_markers
: loading
? [t('loadingBoundary')]
: wazuhOwnerEvidencePreflightBoundaries
const statusText = loading ? t('status.loading') : failed ? t('status.failed') : t('status.ready')
const statusTone: 'steady' | 'warn' | 'locked' = loading || failed ? 'warn' : 'locked'
return (
<section
@@ -9132,10 +9223,14 @@ function IwoooSWazuhOwnerEvidencePreflightBoard() {
<p style={{ fontSize: 12, color: '#315a74', margin: '6px 0 0', lineHeight: 1.55, ...textWrap }}>
{t('subtitle')}
</p>
<div style={{ marginTop: 8, display: 'inline-flex', alignItems: 'center', gap: 8, color: toneColors[statusTone], fontSize: 12, fontWeight: 700, ...textWrap }}>
<ToneDot tone={statusTone} />
{statusText}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(126px, 1fr))', gap: 8 }}>
{wazuhOwnerEvidencePreflightSummary.map(item => {
{summaryItems.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #c8dbea', borderRadius: 8, padding: 12, background: '#fff' }}>
@@ -9163,7 +9258,7 @@ function IwoooSWazuhOwnerEvidencePreflightBoard() {
gap: 10,
}}
>
{wazuhOwnerEvidencePreflightItems.map(item => {
{preflightItems.map(item => {
const Icon = item.icon
return (
<div
@@ -9219,7 +9314,7 @@ function IwoooSWazuhOwnerEvidencePreflightBoard() {
{t('boundaryIntro')}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 }}>
{wazuhOwnerEvidencePreflightBoundaries.map(item => (
{boundaryMarkers.map(item => (
<code
key={item}
style={{

View File

@@ -102,6 +102,7 @@ export interface IwoooSRuntimeSecurityReadbackLane {
| 'wazuh_registry'
| 'wazuh_live_route'
| 'wazuh_live_metadata_gate'
| 'wazuh_owner_evidence_preflight'
| 'wazuh_dashboard_api'
| 'kali_intake'
| 'alert_readability'
@@ -148,6 +149,16 @@ export interface IwoooSRuntimeSecurityReadbackResponse {
wazuh_live_metadata_gate_readonly_scope_accepted_count: number
wazuh_live_metadata_gate_post_enable_readback_count: number
wazuh_live_metadata_gate_live_query_authorized_count: number
wazuh_owner_evidence_required_field_count: number
wazuh_owner_evidence_reviewer_check_count: number
wazuh_owner_evidence_outcome_lane_count: number
wazuh_owner_evidence_forbidden_payload_count: number
wazuh_owner_evidence_expected_alias_count: number
wazuh_owner_evidence_registry_export_received_count: number
wazuh_owner_evidence_registry_export_accepted_count: number
wazuh_owner_evidence_received_count: number
wazuh_owner_evidence_accepted_count: number
wazuh_owner_evidence_runtime_gate_count: number
kali_active_scan_authorized_count: number
kali_execute_authorized_count: number
kali_finding_envelope_accepted_count: number
@@ -210,6 +221,53 @@ export interface IwoooSWazuhLiveMetadataGateResponse {
no_false_green_rules: string[]
}
export interface IwoooSWazuhOwnerEvidencePreflightItem {
item_id:
| 'scope_aliases'
| 'registry_counts'
| 'per_host_matrix'
| 'time_window'
| 'health_refs'
| 'redaction'
| 'owner_decision'
| 'runtime_boundary'
check: string
state_key: string
tone: IwoooSRuntimeSecurityReadbackTone
metrics: Record<string, number>
}
export interface IwoooSWazuhOwnerEvidencePreflightResponse {
schema_version: 'iwooos_wazuh_owner_evidence_preflight_readback_v1'
status: string
mode: string
source_refs: string[]
summary: {
required_field_count: number
reviewer_check_count: number
outcome_lane_count: number
forbidden_payload_count: number
expected_scope_alias_count: number
per_host_required_field_count: number
allowed_collection_method_count: number
registry_export_received_count: number
registry_export_accepted_count: number
owner_evidence_received_count: number
owner_evidence_accepted_count: number
owner_evidence_rejected_count: number
owner_evidence_quarantined_count: number
runtime_gate_count: number
wazuh_api_live_query_authorized_count: number
active_response_authorized_count: number
host_write_authorized_count: number
secret_value_collection_allowed_count: number
}
items: IwoooSWazuhOwnerEvidencePreflightItem[]
boundary_markers: string[]
boundaries: Record<string, boolean>
no_false_green_rules: string[]
}
export interface IwoooSSecurityControlCoverageDomain {
domain_id:
| 'high_value_asset_control'
@@ -320,6 +378,11 @@ export const apiClient = {
return handleResponse<IwoooSWazuhLiveMetadataGateResponse>(res)
},
async getIwoooSWazuhOwnerEvidencePreflight() {
const res = await fetch(`${API_BASE_URL}/iwooos/wazuh-owner-evidence-preflight`, { cache: 'no-store' })
return handleResponse<IwoooSWazuhOwnerEvidencePreflightResponse>(res)
},
async getIwoooSSecurityControlCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/security-control-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSSecurityControlCoverageResponse>(res)