import { Alert, Card, Space, Table, Tag, Typography } from 'antd' import type { ColumnsType } from 'antd/es/table' import { useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import apiClient, { isEnvelope } from '../../api/client' interface AutoRule { Proto: string Port: number DstIP?: string Comment: string } async function listAutoRules(): Promise { try { const r = await apiClient.get('/firewall/auto-rules') if (!isEnvelope(r.data)) return [] return (r.data.data as { rules?: AutoRule[] }).rules ?? [] } catch { return [] } } // SystemRulesCard documents the baseline nftables ruleset that // EdgeGuard installs unconditionally — anti-lockout, stateful // session handling, public ingress, cluster mTLS. These rules sit // in the kernel ruleset BEFORE any operator-defined rule and can // not be overruled from the UI (they live in the renderer's nft // template). Showing them here closes the "wait, where does the // implicit drop come from?"-gap. interface SystemRule { key: string chain: string match: string action: string note?: string } const ROWS: SystemRule[] = [ { key: 'a1', chain: 'input', match: 'tcp dport 22 (rate-limit 10/min)', action: 'accept', note: 'anti-lockout: SSH' }, { key: 'a2', chain: 'input', match: 'tcp dport 443', action: 'accept', note: 'anti-lockout: HAProxy public HTTPS' }, { key: 'a2u',chain: 'input', match: 'udp dport 443', action: 'accept', note: 'anti-lockout: HAProxy HTTP/3 (QUIC)' }, { key: 'a3', chain: 'input', match: 'tcp dport 3443', action: 'accept', note: 'anti-lockout: Management-UI (admin HTTPS)' }, { key: 'b1', chain: 'input', match: 'ct state established,related', action: 'accept', note: 'stateful baseline' }, { key: 'b2', chain: 'input', match: 'ct state invalid', action: 'drop', note: 'stateful baseline' }, { key: 'b3', chain: 'input', match: 'iif lo', action: 'accept', note: 'loopback' }, { key: 'c1', chain: 'input', match: 'icmp/icmpv6 (echo, dest-unreach, time-exc.)', action: 'accept', note: 'PMTUD + diag' }, { key: 'd1', chain: 'input', match: 'tcp dport 80', action: 'accept', note: 'HAProxy ACME + redirect' }, { key: 'e1', chain: 'input', match: 'tcp dport 8443 ip saddr @peer_ipv4/v6', action: 'accept', note: 'cluster mTLS (peers only)' }, ] const ACTION_COLORS: Record = { accept: 'green', drop: 'red', reject: 'orange', } export default function SystemRulesCard() { const { t } = useTranslation() const { data: autoRules } = useQuery({ queryKey: ['fw', 'auto-rules'], queryFn: listAutoRules, refetchInterval: 30_000 }) // Static system rules + dynamic auto-rules zu einer Liste mergen. const dynamic: SystemRule[] = (autoRules ?? []).map((r, i) => ({ key: `auto-${i}`, chain: 'input', match: `${r.DstIP ? `ip daddr ${r.DstIP} ` : ''}${r.Proto} dport ${r.Port}`, action: 'accept', note: `auto: ${r.Comment}`, })) const allRows = [...ROWS, ...dynamic] const cols: ColumnsType = [ { title: t('fw.sys.chain'), dataIndex: 'chain', key: 'chain', width: 80, render: (s: string) => {s} }, { title: t('fw.sys.match'), dataIndex: 'match', key: 'match', render: (s: string) => {s} }, { title: t('fw.sys.action'), dataIndex: 'action', key: 'action', width: 90, render: (a: string) => {a.toUpperCase()}, }, { title: t('fw.sys.note'), dataIndex: 'note', key: 'note', render: (v?: string) => v ? {v} : '—' }, ] return ( {t('fw.sys.policy')}: {t('fw.sys.policyValue')} {t('fw.sys.order')}: {t('fw.sys.orderValue')} {t('fw.sys.lockout')}: {t('fw.sys.lockoutValue')} } /> ) }