Add live firewall rule management per server (nftables)
New model.FirewallRule + jsondb CRUD (GetFirewallRules/CreateFirewallRule/ UpdateFirewallRule/DeleteFirewallRule), scoped per server. firewall package now generates a full ruleset (baseline + enabled custom rules) and can apply it live via `nft -f` (firewall.Apply), scoped to a per-server nftables table (wireguard_ui_<serverID>) so applying one server never touches another server's rules or any pre-existing firewall state. New endpoints: GET/POST /servers/:id/firewall/rules, POST .../rules/:ruleId, POST .../rules/:ruleId/delete, POST .../apply (live, admin-only). UI in the All Servers page: rule table with add/delete, ruleset preview, and an "Apply now (live)" button with an explicit confirm() warning before it touches the running firewall. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
28eb08df41
commit
1d080904b0
@@ -0,0 +1,44 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Apply writes ruleset to a temp file and loads it with `nft -f`, after
|
||||
// first deleting the server's own table (ignoring the error - the table
|
||||
// may not exist yet on first apply). Only ever touches the single table
|
||||
// named by TableName(serverID), never any other nftables state.
|
||||
// Returns combined nft output for display, and an error if the load failed.
|
||||
func Apply(serverID, ruleset string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// best-effort: drop any previous version of this server's table so
|
||||
// reapplying is idempotent. Error ignored - table may not exist yet.
|
||||
_ = exec.CommandContext(ctx, "nft", "delete", "table", "inet", TableName(serverID)).Run()
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "wg-ui-multi-fw-*.nft")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot create temp ruleset file: %w", err)
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
if _, err := tmpFile.WriteString(ruleset); err != nil {
|
||||
tmpFile.Close()
|
||||
return "", fmt.Errorf("cannot write temp ruleset file: %w", err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
return "", fmt.Errorf("cannot close temp ruleset file: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "nft", "-f", tmpFile.Name())
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return string(out), fmt.Errorf("nft -f failed: %w", err)
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
Reference in New Issue
Block a user