feat(iwooos): add security control coverage board
Some checks failed
Code Review / ai-code-review (push) Successful in 14s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
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-26 18:18:45 +08:00
parent f8c6dd7284
commit c8912204ce
8 changed files with 1021 additions and 1 deletions

View File

@@ -33,7 +33,12 @@ import Link from 'next/link'
import { useTranslations } from 'next-intl'
import { useEffect, useRef, useState, type ReactNode } from 'react'
import { AppLayout } from '@/components/layout'
import { apiClient, type IwoooSRuntimeSecurityReadbackResponse } from '@/lib/api-client'
import {
apiClient,
type IwoooSRuntimeSecurityReadbackResponse,
type IwoooSSecurityControlCoverageDomain,
type IwoooSSecurityControlCoverageResponse,
} from '@/lib/api-client'
type PostureMetric = {
key: string
@@ -327,6 +332,13 @@ type RuntimeSecurityReadbackSummaryItem = {
tone: 'steady' | 'warn' | 'locked'
}
type SecurityControlCoverageSummaryItem = {
key: 'visibleScopes' | 'controlDomains' | 'controlPlane' | 'runtimeAcceptance' | 'ownerAccepted' | 'wazuhAccepted'
value: string
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
type SocSiemKaliWazuhIntegrationItem = {
key: string
check: string
@@ -8281,6 +8293,276 @@ function IwoooSRuntimeSecurityReadbackBoard() {
)
}
function coverageTone(domain: IwoooSSecurityControlCoverageDomain): 'steady' | 'warn' | 'locked' {
if (domain.domain_id === 'wazuh_managed_host_coverage') return 'locked'
if (domain.coverage_percent >= 80) return 'steady'
if (domain.coverage_percent >= 50) return 'warn'
return 'locked'
}
function IwoooSSecurityControlCoverageBoard() {
const t = useTranslations('iwooos.securityControlCoverage')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
const [data, setData] = useState<IwoooSSecurityControlCoverageResponse | null>(null)
const [loading, setLoading] = useState(true)
const [failed, setFailed] = useState(false)
useEffect(() => {
let mounted = true
async function loadCoverage() {
setLoading(true)
setFailed(false)
try {
const payload = await apiClient.getIwoooSSecurityControlCoverage()
if (mounted) {
setData(payload)
}
} catch {
if (mounted) {
setData(null)
setFailed(true)
}
} finally {
if (mounted) {
setLoading(false)
}
}
}
loadCoverage()
return () => {
mounted = false
}
}, [])
const summary = data?.summary
const statusKey = loading ? 'loading' : failed || !data ? 'failed' : 'ready'
const statusTone: 'steady' | 'warn' | 'locked' = loading ? 'warn' : failed || !data ? 'warn' : 'locked'
const summaryItems: SecurityControlCoverageSummaryItem[] = [
{
key: 'visibleScopes',
value: summary ? String(summary.visible_scope_unit_count) : '...',
icon: Boxes,
tone: summary && summary.visible_scope_unit_count > 0 ? 'steady' : 'warn',
},
{
key: 'controlDomains',
value: summary ? String(summary.control_domain_count) : '...',
icon: Network,
tone: summary && summary.control_domain_count > 0 ? 'steady' : 'warn',
},
{
key: 'controlPlane',
value: summary ? `${summary.control_plane_visibility_percent}%` : '...',
icon: ShieldCheck,
tone: summary && summary.control_plane_visibility_percent >= 70 ? 'steady' : 'warn',
},
{
key: 'runtimeAcceptance',
value: summary ? `${summary.actual_runtime_acceptance_percent}%` : '...',
icon: Lock,
tone: 'locked',
},
{
key: 'ownerAccepted',
value: summary ? String(summary.owner_response_accepted_count) : '...',
icon: ClipboardCheck,
tone: 'locked',
},
{
key: 'wazuhAccepted',
value: summary ? String(summary.wazuh_manager_registry_accepted_count) : '...',
icon: Radar,
tone: 'locked',
},
]
const domains = data?.domains ?? []
const p0Actions = data?.p0_next_actions ?? []
return (
<section
style={{ marginBottom: 14, maxWidth: '100%', overflow: 'hidden' }}
data-testid="iwooos-security-control-coverage-board"
>
<div style={{ ...band, padding: 16, background: '#fcfbf7', borderColor: '#ded7c2' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))', gap: 14 }}>
<div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#6a5730', fontSize: 12, fontWeight: 700 }}>
<Boxes size={17} color="#80652b" />
{t('eyebrow')}
</div>
<h2 style={{ fontSize: 17, margin: '8px 0 0', color: '#141413' }}>{t('title')}</h2>
<p style={{ fontSize: 12, color: '#5d5036', margin: '6px 0 0', lineHeight: 1.55, ...textWrap }}>
{t('subtitle')}
</p>
</div>
<div style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, padding: 12, background: '#fff', minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
<span style={{ fontSize: 11, color: '#75684b', fontWeight: 700 }}>{t('statusLabel')}</span>
<ToneDot tone={statusTone} />
</div>
<div style={{ marginTop: 8, fontSize: 15, color: toneColors[statusTone], fontWeight: 700, ...textWrap }}>
{t(`status.${statusKey}` as never)}
</div>
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.5, margin: '8px 0 0', ...textWrap }}>
{t('statusDetail')}
</p>
</div>
</div>
<div
style={{
marginTop: 14,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 132px), 1fr))',
gap: 8,
}}
>
{summaryItems.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, padding: 12, background: '#fff', minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ fontSize: 11, color: '#75684b', ...textWrap }}>{t(`summary.${item.key}.label` as never)}</span>
<Icon size={16} color={toneColors[item.tone]} />
</div>
<div style={{ fontSize: 20, fontWeight: 700, color: toneColors[item.tone], lineHeight: 1.1, marginTop: 8, ...textWrap }}>
{item.value}
</div>
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '8px 0 0', ...textWrap }}>
{t(`summary.${item.key}.detail` as never)}
</p>
</div>
)
})}
</div>
<div
style={{
marginTop: 14,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))',
gap: 10,
}}
>
{domains.length === 0 ? (
<div style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fff', padding: 13, color: '#5d5036', fontSize: 12 }}>
{t('emptyDomains')}
</div>
) : domains.map(domain => {
const tone = coverageTone(domain)
return (
<div
key={domain.domain_id}
style={{
border: `0.5px solid ${tone === 'steady' ? '#bddfc9' : tone === 'warn' ? '#e6d1a6' : '#dad7ce'}`,
borderRadius: 8,
background: tone === 'warn' ? '#fffaf1' : tone === 'locked' ? '#f8f7f3' : '#fff',
padding: 13,
display: 'grid',
gap: 10,
minWidth: 0,
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'flex-start' }}>
<div style={{ minWidth: 0 }}>
<div style={{ fontSize: 12, color: toneColors[tone], fontWeight: 700, ...textWrap }}>{domain.label}</div>
<div style={{ marginTop: 5, fontSize: 11, color: '#5d5036', lineHeight: 1.45, ...textWrap }}>
{t(`domainBody.${domain.domain_id}` as never)}
</div>
</div>
<span style={{ fontSize: 12, color: toneColors[tone], fontWeight: 700 }}>{domain.coverage_percent}%</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 6 }}>
{([
['scope', domain.scope_count],
['blocked', domain.blocked_count],
['accepted', domain.accepted_count],
] as const).map(([key, value]) => (
<div key={key} style={{ border: '0.5px solid #e7debf', borderRadius: 8, background: '#fff', padding: 8, minWidth: 0 }}>
<div style={{ fontSize: 10, color: '#75684b', fontWeight: 700 }}>{t(`domainMetric.${key}` as never)}</div>
<div style={{ marginTop: 5, fontSize: 14, color: key === 'accepted' ? toneColors.locked : '#141413', fontWeight: 700 }}>
{value}
</div>
</div>
))}
</div>
<div style={{ border: '0.5px solid #e7debf', borderRadius: 8, padding: 8, background: '#fff', minWidth: 0 }}>
<div style={{ fontSize: 10, color: '#75684b', fontWeight: 700 }}>{t('domainStatusLabel')}</div>
<div style={{ marginTop: 5, fontSize: 11, color: '#141413', fontWeight: 700, ...textWrap }}>
{t(`domainStatus.${domain.status}` as never)}
</div>
</div>
</div>
)
})}
</div>
<div style={{ marginTop: 14 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#6a5730', fontSize: 12, fontWeight: 700 }}>
<ListChecks size={16} color="#80652b" />
{t('p0Title')}
</div>
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.5, margin: '6px 0 10px', ...textWrap }}>
{t('p0Intro')}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 }}>
{p0Actions.map(action => (
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fff', padding: 11, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
</div>
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
{t('requiredEvidenceLabel')}{action.required_evidence}
</p>
</div>
))}
</div>
</div>
<details
data-testid="iwooos-security-control-coverage-boundaries"
style={{
marginTop: 12,
border: '0.5px solid #e0d8bf',
borderRadius: 8,
background: '#fff',
padding: '8px 10px',
}}
>
<summary style={{ cursor: 'pointer', fontSize: 12, fontWeight: 700, color: '#6a5730' }}>
{t('boundaryTitle')}
</summary>
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 }}>
{(data?.no_false_green_rules ?? []).map(item => (
<code
key={item}
style={{
border: '0.5px solid #e0d8bf',
borderRadius: 8,
padding: '6px 8px',
color: '#5d5036',
fontSize: 11,
lineHeight: 1.4,
background: '#fffaf1',
overflowWrap: 'anywhere',
}}
>
{item}
</code>
))}
</div>
</details>
</div>
</section>
)
}
function wazuhReadonlyStatusKey(httpStatus: number | null, data: WazuhReadonlyStatusResponse | null) {
if (httpStatus === 404) return 'predeploy'
if (!data) return 'unavailable'
@@ -22178,6 +22460,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
<IwoooSRolloutRiskReadOnlyBoard />
<IwoooSWazuhIntrusionReadbackBoard />
<IwoooSRuntimeSecurityReadbackBoard />
<IwoooSSecurityControlCoverageBoard />
<IwoooSWazuhLiveRouteReadbackBoard />
<IwoooSWazuhReleaseGateBoard />
<IwoooSWazuhOwnerEvidencePreflightBoard />

View File

@@ -144,6 +144,73 @@ export interface IwoooSRuntimeSecurityReadbackResponse {
no_false_green_rules: string[]
}
export interface IwoooSSecurityControlCoverageDomain {
domain_id:
| 'high_value_asset_control'
| 'host_service_runtime'
| 'monitoring_alerting_observability'
| 'ssh_firewall_network_access'
| 'awoooi_runtime_surfaces'
| 'wazuh_managed_host_coverage'
| 'agent_bounty_protocol'
| 'ai_agent_automation'
label: string
priority: string
coverage_percent: number
scope_count: number
write_capable_count: number
accepted_count: number
blocked_count: number
status: string
next_gate: string
source_refs: string[]
}
export interface IwoooSSecurityControlCoverageResponse {
schema_version: 'iwooos_security_control_coverage_v1'
status: string
mode: string
summary: {
source_snapshot_count: number
control_domain_count: number
visible_scope_unit_count: number
write_capable_scope_count: number
blocked_scope_count: number
accepted_scope_count: number
control_plane_visibility_percent: number
actual_runtime_acceptance_percent: number
runtime_gate_count: number
owner_response_received_count: number
owner_response_accepted_count: number
live_evidence_accepted_count: number
wazuh_manager_registry_accepted_count: number
active_scan_authorized_count: number
active_response_authorized_count: number
telegram_send_authorized_count: number
host_write_authorized_count: number
secret_value_collected_count: number
agent_bounty_runtime_gate_open_count: number
ai_agent_runtime_write_gate_open_count: number
asset_group_count: number
host_service_surface_count: number
monitoring_surface_count: number
ssh_network_surface_count: number
runtime_surface_count: number
wazuh_expected_host_scope_count: number
agent_bounty_product_surface_count: number
ai_agent_asset_count: number
all_scope_runtime_controlled: boolean
}
domains: IwoooSSecurityControlCoverageDomain[]
p0_next_actions: Array<{
priority: string
title: string
required_evidence: string
}>
no_false_green_rules: string[]
source_refs: string[]
}
async function handleResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
@@ -182,6 +249,11 @@ export const apiClient = {
return handleResponse<IwoooSRuntimeSecurityReadbackResponse>(res)
},
async getIwoooSSecurityControlCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/security-control-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSSecurityControlCoverageResponse>(res)
},
// Agent
async getAgentStatus() {
const res = await fetch(`${API_BASE_URL}/agent/status`)