Add SQLite persistence and write-through gRPC mutations
Database (internal/db) stores listeners, routes, and firewall rules with WAL mode, foreign keys, and idempotent migrations. First run seeds from TOML config; subsequent runs load from DB as source of truth. gRPC admin API now writes to the database before updating in-memory state (write-through cache pattern). Adds snapshot command for VACUUM INTO backups. Refactors firewall.New to accept raw rule slices instead of config struct for flexibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,10 @@ package firewall
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/config"
|
||||
)
|
||||
|
||||
func TestEmptyFirewall(t *testing.T) {
|
||||
fw, err := New(config.Firewall{})
|
||||
fw, err := New("", nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -24,9 +22,7 @@ func TestEmptyFirewall(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPBlocking(t *testing.T) {
|
||||
fw, err := New(config.Firewall{
|
||||
BlockedIPs: []string{"192.0.2.1", "2001:db8::dead"},
|
||||
})
|
||||
fw, err := New("", []string{"192.0.2.1", "2001:db8::dead"}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -51,9 +47,7 @@ func TestIPBlocking(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCIDRBlocking(t *testing.T) {
|
||||
fw, err := New(config.Firewall{
|
||||
BlockedCIDRs: []string{"198.51.100.0/24", "2001:db8::/32"},
|
||||
})
|
||||
fw, err := New("", nil, []string{"198.51.100.0/24", "2001:db8::/32"}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -79,15 +73,12 @@ func TestCIDRBlocking(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPv4MappedIPv6(t *testing.T) {
|
||||
fw, err := New(config.Firewall{
|
||||
BlockedIPs: []string{"192.0.2.1"},
|
||||
})
|
||||
fw, err := New("", []string{"192.0.2.1"}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
// IPv4-mapped IPv6 representation of 192.0.2.1.
|
||||
addr := netip.MustParseAddr("::ffff:192.0.2.1")
|
||||
if !fw.Blocked(addr) {
|
||||
t.Fatal("expected IPv4-mapped IPv6 address to be blocked")
|
||||
@@ -95,28 +86,21 @@ func TestIPv4MappedIPv6(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInvalidIP(t *testing.T) {
|
||||
_, err := New(config.Firewall{
|
||||
BlockedIPs: []string{"not-an-ip"},
|
||||
})
|
||||
_, err := New("", []string{"not-an-ip"}, nil, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid IP")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidCIDR(t *testing.T) {
|
||||
_, err := New(config.Firewall{
|
||||
BlockedCIDRs: []string{"not-a-cidr"},
|
||||
})
|
||||
_, err := New("", nil, []string{"not-a-cidr"}, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid CIDR")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedRules(t *testing.T) {
|
||||
fw, err := New(config.Firewall{
|
||||
BlockedIPs: []string{"10.0.0.1"},
|
||||
BlockedCIDRs: []string{"192.168.0.0/16"},
|
||||
})
|
||||
fw, err := New("", []string{"10.0.0.1"}, []string{"192.168.0.0/16"}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -126,10 +110,10 @@ func TestCombinedRules(t *testing.T) {
|
||||
addr string
|
||||
blocked bool
|
||||
}{
|
||||
{"10.0.0.1", true}, // IP match
|
||||
{"10.0.0.2", false}, // no match
|
||||
{"192.168.1.1", true}, // CIDR match
|
||||
{"172.16.0.1", false}, // no match
|
||||
{"10.0.0.1", true},
|
||||
{"10.0.0.2", false},
|
||||
{"192.168.1.1", true},
|
||||
{"172.16.0.1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -139,3 +123,30 @@ func TestCombinedRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeMutation(t *testing.T) {
|
||||
fw, err := New("", nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
addr := netip.MustParseAddr("10.0.0.1")
|
||||
if fw.Blocked(addr) {
|
||||
t.Fatal("should not be blocked initially")
|
||||
}
|
||||
|
||||
if err := fw.AddIP("10.0.0.1"); err != nil {
|
||||
t.Fatalf("add IP: %v", err)
|
||||
}
|
||||
if !fw.Blocked(addr) {
|
||||
t.Fatal("should be blocked after AddIP")
|
||||
}
|
||||
|
||||
if err := fw.RemoveIP("10.0.0.1"); err != nil {
|
||||
t.Fatalf("remove IP: %v", err)
|
||||
}
|
||||
if fw.Blocked(addr) {
|
||||
t.Fatal("should not be blocked after RemoveIP")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user