New firewall.DisableGlobal() removes the wireguard_ui_global nftables table without touching stored IP list entries, and firewall.IsGlobalEnabled() reports whether it's currently loaded. New GET /firewall-lists/status and POST /firewall-lists/disable endpoints (admin-only), plus a status badge and "Toggle enable/disable" button on the Global Firewall Lists page - one click to turn the whole thing off without losing the list contents, and back on again (re-applies the current ruleset). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
75 lines
2.5 KiB
Go
75 lines
2.5 KiB
Go
package firewall
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"time"
|
|
)
|
|
|
|
// applyTable writes ruleset to a temp file and loads it with `nft -f`,
|
|
// after first deleting the given table (ignoring the error - the table may
|
|
// not exist yet on first apply). Only ever touches that single table,
|
|
// never any other nftables state.
|
|
func applyTable(tableName, ruleset string) (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
// best-effort: drop the previous version of this table so reapplying
|
|
// is idempotent. Error ignored - table may not exist yet.
|
|
_ = exec.CommandContext(ctx, "nft", "delete", "table", "inet", tableName).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
|
|
}
|
|
|
|
// Apply loads a single server's ruleset live, scoped to TableName(serverID).
|
|
func Apply(serverID, ruleset string) (string, error) {
|
|
return applyTable(TableName(serverID), ruleset)
|
|
}
|
|
|
|
// ApplyGlobal loads the host-wide allow/block list ruleset live, scoped to
|
|
// GlobalTableName.
|
|
func ApplyGlobal(ruleset string) (string, error) {
|
|
return applyTable(GlobalTableName, ruleset)
|
|
}
|
|
|
|
// DisableGlobal removes the host-wide allow/block list table entirely,
|
|
// turning enforcement off without touching the stored IP list entries -
|
|
// they stay in the database and can be re-applied later with ApplyGlobal.
|
|
// A missing table (already disabled) is not treated as an error.
|
|
func DisableGlobal() (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
out, _ := exec.CommandContext(ctx, "nft", "delete", "table", "inet", GlobalTableName).CombinedOutput()
|
|
return string(out), nil
|
|
}
|
|
|
|
// IsGlobalEnabled reports whether the host-wide allow/block list table is
|
|
// currently loaded in the live firewall.
|
|
func IsGlobalEnabled() bool {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
err := exec.CommandContext(ctx, "nft", "list", "table", "inet", GlobalTableName).Run()
|
|
return err == nil
|
|
}
|