feat(fw): Renderer-Rewrite + auto-apply + Anti-Lockout

internal/firewall/firewall.go komplett neu: joint zone-iface-mapping
(network_interfaces.role), address objects + groups (members
expandiert), services + groups, rules, nat-rules. Output: einheitliche
View mit Legs (rule × service cross-product) damit das Template kein
sub-template/dict braucht.

Template:
* Anti-Lockout-Block am input-chain-Top (SSH+443 immer erlaubt,
  KANN nicht von Custom-Rules overruled werden — User-Wunsch).
* Rules: pro Leg eine nft-Zeile mit iif/oif sets, ip saddr/daddr,
  proto+dport, optional log-prefix.
* prerouting_nat: iteriert dnat-Rules.
* postrouting_nat: snat + masquerade.

Auto-apply: FirewallHandler bekommt einen Reloader-Hook der nach
jedem POST/PUT/DELETE aufgerufen wird. main.go injected
firewall.New(pool).Render — schreibt + sudo nft -f.

Sudoers (/etc/sudoers.d/edgeguard): NOPASSWD für 'nft -f
/etc/edgeguard/nftables.d/ruleset.nft'. configgen.ReloadService
nutzt jetzt sudo (haproxy reload klappte vorher nicht aus dem
edgeguard-User).

Frontend (Sweep): style={{ marginBottom: 16 }} → className="mb-16"
in allen 7 Firewall-Tabs — User-Feedback "globales CSS statt inline".

Live auf 89.163.205.6: nft list table inet edgeguard zeigt
Anti-Lockout + Baseline + Cluster-Peer-Set + (jetzt noch leere)
Custom-Rules-Sektion. render-config postinst-mäßig sauber.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-10 13:34:06 +02:00
parent e2bdce9271
commit 1b2c0d7411
13 changed files with 542 additions and 161 deletions

View File

@@ -85,7 +85,7 @@ export default function AddressGroupsTab() {
return (
<>
<Button type="primary" style={{ marginBottom: 16 }} onClick={() => {
<Button type="primary" className="mb-16" onClick={() => {
setCreating(true); form.resetFields()
form.setFieldsValue({ member_ids: [] })
}}>

View File

@@ -78,7 +78,7 @@ export default function AddressObjectsTab() {
return (
<>
<Button type="primary" style={{ marginBottom: 16 }} onClick={() => {
<Button type="primary" className="mb-16" onClick={() => {
setCreating(true); form.resetFields()
form.setFieldsValue({ kind: 'host' })
}}>

View File

@@ -122,7 +122,7 @@ export default function NATRulesTab() {
return (
<>
<Button type="primary" style={{ marginBottom: 16 }} onClick={() => {
<Button type="primary" className="mb-16" onClick={() => {
setCreating(true); form.resetFields()
form.setFieldsValue({ priority: 100, enabled: true, kind: 'dnat' })
}}>

View File

@@ -185,7 +185,7 @@ export default function RulesTab() {
return (
<>
<Button type="primary" style={{ marginBottom: 16 }} onClick={() => {
<Button type="primary" className="mb-16" onClick={() => {
setCreating(true); form.resetFields()
form.setFieldsValue({
priority: 100, enabled: true, action: 'accept', log: false,
@@ -213,7 +213,7 @@ export default function RulesTab() {
<Input placeholder="SSH von Office" />
</Form.Item>
<Space size="middle" style={{ display: 'flex', flexWrap: 'wrap' }}>
<Space size="middle" className="flex-wrap">
<Form.Item label={t('fw.rule.priority')} name="priority" rules={[{ required: true }]}>
<InputNumber min={0} max={9999} />
</Form.Item>
@@ -262,7 +262,7 @@ export default function RulesTab() {
</Space>
))}
<Space size="middle" style={{ display: 'flex', flexWrap: 'wrap' }}>
<Space size="middle" className="flex-wrap">
<Form.Item label={t('fw.rule.serviceKind')} name="service_kind" rules={[{ required: true }]}>
<Select style={{ width: 120 }} options={(['any','object','group'] as const).map(k => ({ value: k, label: k }))} />
</Form.Item>

View File

@@ -85,7 +85,7 @@ export default function ServiceGroupsTab() {
return (
<>
<Button type="primary" style={{ marginBottom: 16 }} onClick={() => {
<Button type="primary" className="mb-16" onClick={() => {
setCreating(true); form.resetFields()
form.setFieldsValue({ member_ids: [] })
}}>

View File

@@ -89,7 +89,7 @@ export default function ServicesTab() {
return (
<>
<Button type="primary" style={{ marginBottom: 16 }} onClick={() => {
<Button type="primary" className="mb-16" onClick={() => {
setCreating(true); form.resetFields()
form.setFieldsValue({ proto: 'tcp' })
}}>