This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"firewall_containers/network-go/config"
|
||||
"firewall_containers/network-go/mock"
|
||||
)
|
||||
|
||||
func testConfig() *config.NetworksConfig {
|
||||
return &config.NetworksConfig{
|
||||
Networks: map[string]config.NetworkConfig{
|
||||
"smarthost-loadbalancer": {
|
||||
NetworkName: "smarthost-loadbalancer",
|
||||
Subnet: "172.18.103.0/24",
|
||||
Gateway: "172.18.103.1",
|
||||
},
|
||||
"smarthost_backend-1": {
|
||||
NetworkName: "smarthost_backend-1",
|
||||
Subnet: "172.18.104.0/24",
|
||||
Gateway: "172.18.104.1",
|
||||
},
|
||||
},
|
||||
IPs: map[string]config.IPConfig{
|
||||
"172.18.103.2": {
|
||||
IP: "172.18.103.2",
|
||||
ContainerName: "smarthostloadbalancer",
|
||||
Selector: "smarthostloadbalancer",
|
||||
ServiceName: "smarthost-proxy",
|
||||
},
|
||||
"172.18.104.2": {
|
||||
IP: "172.18.104.2",
|
||||
ContainerName: "smarthostbackend-1",
|
||||
Selector: "smarthostbackend-1",
|
||||
ServiceName: "smarthost-proxy",
|
||||
},
|
||||
},
|
||||
Policies: []config.PolicyConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcileAllCreatesNetworks(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.ReconcileAll(ctx, cfg)
|
||||
|
||||
if !docker.EnsureNetworkCalled {
|
||||
t.Error("EnsureNetwork was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcileAllConnectsContainers(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.ReconcileAll(ctx, cfg)
|
||||
|
||||
if !docker.ConnectContainerCalled {
|
||||
t.Error("ConnectContainer was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcileAllEnablesIPForward(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.ReconcileAll(ctx, cfg)
|
||||
|
||||
if !iptables.EnsureIPForwardCalled {
|
||||
t.Error("EnsureIPForward was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilePoliciesForwardRule(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
cfg.Policies = []config.PolicyConfig{
|
||||
{
|
||||
ServiceName: "smarthost-proxy",
|
||||
ContainerName: "smarthostloadbalancer",
|
||||
Selector: "smarthostloadbalancer",
|
||||
From: "publicbackend",
|
||||
Port: 80,
|
||||
Proto: "tcp",
|
||||
},
|
||||
}
|
||||
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{BinaryResult: "/usr/sbin/iptables"}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.reconcilePolicies(ctx, cfg)
|
||||
|
||||
// "publicbackend" should be resolved to an IP from the config (if it matches)
|
||||
// Since "publicbackend" is not in the IPs map, sourceIP will be empty
|
||||
if !iptables.InsertForwardAcceptCalled {
|
||||
t.Error("InsertForwardAccept was not called")
|
||||
}
|
||||
|
||||
// Should use FORWARD chain (not iptables-legacy)
|
||||
if iptables.InsertForwardAcceptChain != "FORWARD" {
|
||||
t.Errorf("expected FORWARD chain, got %s", iptables.InsertForwardAcceptChain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilePoliciesForwardRuleWithLegacy(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
cfg.Policies = []config.PolicyConfig{
|
||||
{
|
||||
ServiceName: "smarthost-proxy",
|
||||
From: "publicbackend",
|
||||
Port: 80,
|
||||
Proto: "tcp",
|
||||
},
|
||||
}
|
||||
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{BinaryResult: "/usr/sbin/iptables-legacy"}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.reconcilePolicies(ctx, cfg)
|
||||
|
||||
if !iptables.InsertForwardAcceptCalled {
|
||||
t.Error("InsertForwardAccept was not called")
|
||||
}
|
||||
|
||||
// Should use DOCKER-USER chain when iptables-legacy
|
||||
if iptables.InsertForwardAcceptChain != "DOCKER-USER" {
|
||||
t.Errorf("expected DOCKER-USER chain, got %s", iptables.InsertForwardAcceptChain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilePoliciesDNATWithInterface(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
cfg.Policies = []config.PolicyConfig{
|
||||
{
|
||||
ServiceName: "smarthost-proxy",
|
||||
Name: "wireguardproxy",
|
||||
Selector: "smarthostloadbalancer",
|
||||
Iface: "wg0",
|
||||
Nat: "dnat",
|
||||
To: "smarthostloadbalancer",
|
||||
Port: 80,
|
||||
Proto: "tcp",
|
||||
},
|
||||
}
|
||||
|
||||
docker := &mock.MockDockerClient{
|
||||
GetContainerPIDResult: 0, // simulate no PID available
|
||||
GetContainerPIDErr: assertError("container not running"),
|
||||
}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.reconcilePolicies(ctx, cfg)
|
||||
|
||||
// When GetContainerPID fails, should fall back to interface-based rule
|
||||
if !iptables.InsertPreroutingRuleOnInterfaceCalled {
|
||||
t.Error("InsertPreroutingRuleOnInterface was not called (should fall back from nsenter)")
|
||||
}
|
||||
|
||||
if len(iptables.InsertPreroutingRuleOnInterfaceArgs) > 0 {
|
||||
iface := iptables.InsertPreroutingRuleOnInterfaceArgs[0]
|
||||
if iface != "wg0" {
|
||||
t.Errorf("expected interface wg0, got %s", iface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilePoliciesDNATWithContainerPID(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
cfg.Policies = []config.PolicyConfig{
|
||||
{
|
||||
ServiceName: "smarthost-proxy",
|
||||
Name: "wireguardproxy",
|
||||
Selector: "smarthostloadbalancer",
|
||||
Nat: "dnat",
|
||||
To: "smarthostloadbalancer",
|
||||
Port: 80,
|
||||
Proto: "tcp",
|
||||
},
|
||||
}
|
||||
|
||||
docker := &mock.MockDockerClient{
|
||||
GetContainerPIDResult: 1234,
|
||||
GetContainerPIDErr: nil,
|
||||
}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.reconcilePolicies(ctx, cfg)
|
||||
|
||||
if !docker.GetContainerPIDCalled {
|
||||
t.Error("GetContainerPID was not called")
|
||||
}
|
||||
|
||||
if !iptables.InsertPreroutingRuleInContainerCalled {
|
||||
t.Error("InsertPreroutingRuleInContainer was not called")
|
||||
}
|
||||
|
||||
if iptables.InsertPreroutingRuleInContainerPID != 1234 {
|
||||
t.Errorf("expected PID 1234, got %d", iptables.InsertPreroutingRuleInContainerPID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilePoliciesUnresolvedTarget(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
cfg.Policies = []config.PolicyConfig{
|
||||
{
|
||||
ServiceName: "test",
|
||||
Nat: "dnat",
|
||||
Selector: "container1",
|
||||
To: "nonexistent-target",
|
||||
Port: 80,
|
||||
},
|
||||
}
|
||||
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
orch.reconcilePolicies(ctx, cfg)
|
||||
|
||||
// Should not call GetContainerPID when target can't be resolved
|
||||
if docker.GetContainerPIDCalled {
|
||||
t.Error("GetContainerPID should not be called when target is unresolvable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPDirectIP(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
// Direct IP should be returned as CIDR
|
||||
result := orch.resolveIP("172.18.103.2")
|
||||
if result != "172.18.103.2" {
|
||||
t.Errorf("expected 172.18.103.2, got %s", result)
|
||||
}
|
||||
|
||||
// .0 ending should be converted to /24
|
||||
result = orch.resolveIP("172.18.103.0")
|
||||
if result != "172.18.103.0/24" {
|
||||
t.Errorf("expected 172.18.103.0/24, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPFromConfig(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
|
||||
// Should resolve container name from config
|
||||
result := orch.resolveIP("smarthostloadbalancer")
|
||||
if result != "172.18.103.2" {
|
||||
t.Errorf("expected 172.18.103.2, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindNetworkForIP(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
|
||||
tests := []struct {
|
||||
ip string
|
||||
want string
|
||||
}{
|
||||
{"172.18.103.5", "smarthost-loadbalancer"},
|
||||
{"172.18.104.5", "smarthost_backend-1"},
|
||||
{"10.0.0.1", ""},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := findNetworkForIP(cfg, tt.ip)
|
||||
if got != tt.want {
|
||||
t.Errorf("findNetworkForIP(%q) = %q, want %q", tt.ip, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcileAllReproducible(t *testing.T) {
|
||||
cfg := testConfig()
|
||||
cfg.Policies = []config.PolicyConfig{
|
||||
{
|
||||
ServiceName: "test-svc",
|
||||
From: "publicbackend",
|
||||
Port: 80,
|
||||
Proto: "tcp",
|
||||
},
|
||||
}
|
||||
|
||||
// Run reconciliation twice with separate mocks
|
||||
for i := 0; i < 2; i++ {
|
||||
docker := &mock.MockDockerClient{}
|
||||
iptables := &mock.MockIPTablesManager{}
|
||||
orch := NewOrchestrator(docker, iptables, cfg)
|
||||
ctx := context.Background()
|
||||
orch.ReconcileAll(ctx, cfg)
|
||||
|
||||
if !docker.EnsureNetworkCalled {
|
||||
t.Errorf("run %d: EnsureNetwork not called", i)
|
||||
}
|
||||
if !iptables.InsertForwardAcceptCalled {
|
||||
t.Errorf("run %d: InsertForwardAccept not called", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assertError is a helper to create a simple error for tests
|
||||
type simpleErr struct{ msg string }
|
||||
|
||||
func (e simpleErr) Error() string { return e.msg }
|
||||
|
||||
func assertError(msg string) error {
|
||||
return simpleErr{msg: msg}
|
||||
}
|
||||
Reference in New Issue
Block a user