Die Systemregeln-Dokumentation listete nur TCP/443, aber das nftables- Template enthält auch udp dport 443 für QUIC. Ohne den UDP-Eintrag wirkte das Panel inkorrekt und erschwerte Firewall-Troubleshooting. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
4.4 KiB
TypeScript
103 lines
4.4 KiB
TypeScript
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<AutoRule[]> {
|
|
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<string, string> = {
|
|
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<SystemRule> = [
|
|
{ title: t('fw.sys.chain'), dataIndex: 'chain', key: 'chain', width: 80, render: (s: string) => <Tag>{s}</Tag> },
|
|
{ title: t('fw.sys.match'), dataIndex: 'match', key: 'match', render: (s: string) => <code>{s}</code> },
|
|
{
|
|
title: t('fw.sys.action'), dataIndex: 'action', key: 'action', width: 90,
|
|
render: (a: string) => <Tag color={ACTION_COLORS[a] ?? 'default'}>{a.toUpperCase()}</Tag>,
|
|
},
|
|
{ title: t('fw.sys.note'), dataIndex: 'note', key: 'note', render: (v?: string) => v ? <Typography.Text type="secondary">{v}</Typography.Text> : '—' },
|
|
]
|
|
return (
|
|
<Card size="small" title={t('fw.sys.title')} className="mb-12">
|
|
<Alert
|
|
type="info"
|
|
showIcon
|
|
className="mb-12"
|
|
message={
|
|
<Space direction="vertical" size={2}>
|
|
<span><b>{t('fw.sys.policy')}:</b> {t('fw.sys.policyValue')}</span>
|
|
<span><b>{t('fw.sys.order')}:</b> {t('fw.sys.orderValue')}</span>
|
|
<span><b>{t('fw.sys.lockout')}:</b> {t('fw.sys.lockoutValue')}</span>
|
|
</Space>
|
|
}
|
|
/>
|
|
<Table
|
|
size="small"
|
|
rowKey="key"
|
|
columns={cols}
|
|
dataSource={allRows}
|
|
pagination={false}
|
|
/>
|
|
</Card>
|
|
)
|
|
}
|