feat: add logging to Docker and iptables operations, fix iptables path
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Create /var/log/network-go directory in Dockerfile for log storage - Add comprehensive logging to Docker client creation, network management, and container operations - Add logging to iptables rule management (list, delete, etc.) - Fix iptables executable path resolution in deleteMatchingLinesInContainer to use configured binary path
This commit is contained in:
@@ -2,7 +2,6 @@ package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"firewall_containers/network-go/config"
|
||||
"firewall_containers/network-go/docker"
|
||||
"firewall_containers/network-go/iptables"
|
||||
"firewall_containers/network-go/logger"
|
||||
"firewall_containers/network-go/resolver"
|
||||
)
|
||||
|
||||
@@ -33,16 +33,18 @@ func NewOrchestrator(dockerClient docker.DockerAPI, iptablesMgr iptables.IPTable
|
||||
|
||||
// ReconcileAll runs the full reconciliation: networks, container connections, and firewall rules
|
||||
func (o *Orchestrator) ReconcileAll(ctx context.Context, cfg *config.NetworksConfig) {
|
||||
log.Println("FIREWALL: starting full reconciliation")
|
||||
logger.Info("FIREWALL: starting full reconciliation")
|
||||
logger.Debug("FIREWALL: config has %d networks, %d IPs, %d policies",
|
||||
len(cfg.Networks), len(cfg.IPs), len(cfg.Policies))
|
||||
|
||||
// Update resolver with latest config
|
||||
o.resolver.SetConfig(cfg)
|
||||
|
||||
// Step 0: Enable IP forwarding (may fail in containers with read-only fs)
|
||||
if err := o.iptablesMgr.EnsureIPForward(); err != nil {
|
||||
log.Printf("FIREWALL: WARNING could not enable ip_forward: %v", err)
|
||||
logger.Warn("FIREWALL: could not enable ip_forward: %v", err)
|
||||
} else {
|
||||
log.Println("FIREWALL: IP forwarding enabled")
|
||||
logger.Info("FIREWALL: IP forwarding enabled")
|
||||
}
|
||||
|
||||
// Step 1: Ensure all defined networks exist
|
||||
@@ -54,15 +56,18 @@ func (o *Orchestrator) ReconcileAll(ctx context.Context, cfg *config.NetworksCon
|
||||
// Step 3: Reconcile firewall policies
|
||||
o.reconcilePolicies(ctx, cfg)
|
||||
|
||||
log.Println("FIREWALL: full reconciliation completed")
|
||||
logger.Info("FIREWALL: full reconciliation completed")
|
||||
}
|
||||
|
||||
// reconcileNetworks creates Docker networks if they don't exist
|
||||
func (o *Orchestrator) reconcileNetworks(ctx context.Context, cfg *config.NetworksConfig) {
|
||||
for name, netCfg := range cfg.Networks {
|
||||
log.Printf("FIREWALL: ensuring network %s (%s, subnet=%s, gateway=%s)", name, netCfg.NetworkName, netCfg.Subnet, netCfg.Gateway)
|
||||
logger.Info("FIREWALL: ensuring network %s (name=%s, subnet=%s, gateway=%s)",
|
||||
name, netCfg.NetworkName, netCfg.Subnet, netCfg.Gateway)
|
||||
if err := o.dockerClient.EnsureNetwork(ctx, netCfg); err != nil {
|
||||
log.Printf("FIREWALL: ERROR ensuring network %s: %v", name, err)
|
||||
logger.Error("FIREWALL: failed to ensure network %s: %v", name, err)
|
||||
} else {
|
||||
logger.Debug("FIREWALL: network %s ready", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,28 +77,46 @@ func (o *Orchestrator) reconcileIPs(ctx context.Context, cfg *config.NetworksCon
|
||||
for ipStr, ipCfg := range cfg.IPs {
|
||||
networkName := findNetworkForIP(cfg, ipStr)
|
||||
if networkName == "" {
|
||||
log.Printf("FIREWALL: WARNING no network found for IP %s (container=%s)", ipStr, ipCfg.ContainerName)
|
||||
logger.Warn("FIREWALL: no network found for IP %s (container=%s, selector=%s)",
|
||||
ipStr, ipCfg.ContainerName, ipCfg.Selector)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("FIREWALL: resolving container name for IP %s (container=%s, selector=%s)",
|
||||
ipStr, ipCfg.ContainerName, ipCfg.Selector)
|
||||
|
||||
// Resolve the actual container name, with fallback to fuzzy matching
|
||||
// (old shell script behavior: docker ps | grep $D"-")
|
||||
containerName, err := o.dockerClient.FindContainerName(ctx, ipCfg.ContainerName, ipCfg.Selector)
|
||||
if err != nil {
|
||||
log.Printf("FIREWALL: WARNING container %s (selector=%s) not found: %v, trying connection anyway", ipCfg.ContainerName, ipCfg.Selector, err)
|
||||
logger.Warn("FIREWALL: container %s (selector=%s) not found: %v, using config name anyway",
|
||||
ipCfg.ContainerName, ipCfg.Selector, err)
|
||||
containerName = ipCfg.ContainerName
|
||||
} else if containerName != ipCfg.ContainerName {
|
||||
logger.Info("FIREWALL: container resolved: config_name=%s -> actual=%s",
|
||||
ipCfg.ContainerName, containerName)
|
||||
}
|
||||
|
||||
log.Printf("FIREWALL: connecting container %s to network %s with IP %s", containerName, networkName, ipStr)
|
||||
logger.Info("FIREWALL: connecting container %s to network %s with IP %s",
|
||||
containerName, networkName, ipStr)
|
||||
|
||||
waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
if err := o.dockerClient.WaitForContainerRunning(waitCtx, containerName, 10*time.Second); err != nil {
|
||||
log.Printf("FIREWALL: WARNING container %s not running yet: %v, connecting anyway", containerName, err)
|
||||
}
|
||||
waitErr := o.dockerClient.WaitForContainerRunning(waitCtx, containerName, 10*time.Second)
|
||||
cancel()
|
||||
|
||||
if waitErr != nil {
|
||||
logger.Warn("FIREWALL: container %s not running yet: %v, connecting anyway",
|
||||
containerName, waitErr)
|
||||
} else {
|
||||
logger.Debug("FIREWALL: container %s is running", containerName)
|
||||
}
|
||||
|
||||
if err := o.dockerClient.ConnectContainer(ctx, containerName, networkName, ipStr); err != nil {
|
||||
log.Printf("FIREWALL: ERROR connecting container %s to %s: %v", containerName, networkName, err)
|
||||
logger.Error("FIREWALL: failed to connect container %s to %s: %v",
|
||||
containerName, networkName, err)
|
||||
} else {
|
||||
logger.Info("FIREWALL: container %s connected to network %s with IP %s",
|
||||
containerName, networkName, ipStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +124,10 @@ func (o *Orchestrator) reconcileIPs(ctx context.Context, cfg *config.NetworksCon
|
||||
// reconcilePolicies translates PolicyConfig entries into iptables rules
|
||||
func (o *Orchestrator) reconcilePolicies(ctx context.Context, cfg *config.NetworksConfig) {
|
||||
for i, policy := range cfg.Policies {
|
||||
log.Printf("FIREWALL: processing policy[%d]", i)
|
||||
logger.Info("FIREWALL: processing policy[%d]", i)
|
||||
logger.Debug("FIREWALL: policy[%d] details: service=%s container=%s selector=%s from=%s to=%s port=%d proto=%s nat=%s iface=%s",
|
||||
i, policy.ServiceName, policy.ContainerName, policy.Selector,
|
||||
policy.From, policy.To, policy.Port, policy.Proto, policy.Nat, policy.Iface)
|
||||
|
||||
proto := policy.Proto
|
||||
if proto == "" {
|
||||
@@ -122,6 +148,7 @@ func (o *Orchestrator) reconcilePolicies(ctx context.Context, cfg *config.Networ
|
||||
comment = policy.ServiceName
|
||||
}
|
||||
}
|
||||
logger.Debug("FIREWALL: policy[%d] comment=%q", i, comment)
|
||||
|
||||
// CASE 1: Rule with "from" field — this is a FORWARD ACCEPT rule
|
||||
if policy.From != "" {
|
||||
@@ -136,7 +163,7 @@ func (o *Orchestrator) reconcilePolicies(ctx context.Context, cfg *config.Networ
|
||||
}
|
||||
|
||||
// Unhandled pattern
|
||||
log.Printf("FIREWALL: policy[%d] unhandled pattern — service=%s container=%s selector=%s from=%s to=%s port=%d proto=%s nat=%s",
|
||||
logger.Warn("FIREWALL: policy[%d] unhandled pattern — service=%s container=%s selector=%s from=%s to=%s port=%d proto=%s nat=%s",
|
||||
i, policy.ServiceName, policy.ContainerName, policy.Selector, policy.From, policy.To, policy.Port, policy.Proto, policy.Nat)
|
||||
}
|
||||
}
|
||||
@@ -147,32 +174,43 @@ func (o *Orchestrator) applyForwardRule(ctx context.Context, cfg *config.Network
|
||||
if policy.To != "" {
|
||||
targetIP = o.resolveIP(policy.To)
|
||||
}
|
||||
logger.Info("FIREWALL: forward rule: from=%q (IP=%s) to=%q (IP=%s) proto=%s port=%s",
|
||||
policy.From, sourceIP, policy.To, targetIP, proto, port)
|
||||
|
||||
// Determine the chain: use DOCKER-USER (iptables-legacy) or FORWARD
|
||||
chain := "FORWARD"
|
||||
if o.iptablesMgr.Binary() == "/usr/sbin/iptables-legacy" {
|
||||
chain = "DOCKER-USER"
|
||||
}
|
||||
logger.Debug("FIREWALL: using iptables chain=%s (binary=%s)", chain, o.iptablesMgr.Binary())
|
||||
|
||||
// Ensure established/related rule exists at the top
|
||||
if err := o.iptablesMgr.EnsureEstablishedRelated(chain); err != nil {
|
||||
log.Printf("FIREWALL: ERROR ensuring established/related rule in %s: %v", chain, err)
|
||||
logger.Error("FIREWALL: failed to ensure established/related rule in %s: %v", chain, err)
|
||||
} else {
|
||||
logger.Debug("FIREWALL: established/related rule ensured in %s", chain)
|
||||
}
|
||||
|
||||
// Insert the FORWARD ACCEPT rule
|
||||
if err := o.iptablesMgr.InsertForwardAccept(chain, sourceIP, targetIP, proto, "", port, comment); err != nil {
|
||||
log.Printf("FIREWALL: ERROR inserting FORWARD ACCEPT rule: %v", err)
|
||||
logger.Error("FIREWALL: failed to insert FORWARD ACCEPT rule in %s: %v", chain, err)
|
||||
} else {
|
||||
logger.Info("FIREWALL: FORWARD ACCEPT rule inserted: chain=%s src=%s dst=%s proto=%s port=%s comment=%q",
|
||||
chain, sourceIP, targetIP, proto, port, comment)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Orchestrator) applyNATRule(ctx context.Context, cfg *config.NetworksConfig, policy config.PolicyConfig, proto, port, comment string) {
|
||||
to := policy.To
|
||||
logger.Info("FIREWALL: NAT rule: to=%s proto=%s port=%s nat=%s iface=%s",
|
||||
to, proto, port, policy.Nat, policy.Iface)
|
||||
|
||||
// Resolve "to" as target IP
|
||||
targetIP := o.resolveIP(to)
|
||||
logger.Debug("FIREWALL: resolved target %q -> IP=%q", to, targetIP)
|
||||
|
||||
if targetIP == "" {
|
||||
log.Printf("FIREWALL: WARNING cannot resolve target %s for nat policy", to)
|
||||
logger.Warn("FIREWALL: cannot resolve target %s for nat policy", to)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -185,26 +223,34 @@ func (o *Orchestrator) applyNATRule(ctx context.Context, cfg *config.NetworksCon
|
||||
if selector == "" {
|
||||
selector = policy.Name
|
||||
}
|
||||
logger.Debug("FIREWALL: DNAT selector=%s", selector)
|
||||
|
||||
// Try to insert rules inside the container namespace via nsenter
|
||||
usedContainer := false
|
||||
if selector != "" {
|
||||
pid, err := o.dockerClient.GetContainerPID(ctx, selector)
|
||||
if err == nil {
|
||||
logger.Info("FIREWALL: inserting DNAT rule in container %s (PID=%d)", selector, pid)
|
||||
if err := o.iptablesMgr.InsertPreroutingRuleInContainer(pid, "0.0.0.0/0", proto, port, targetIP, port, comment); err != nil {
|
||||
log.Printf("FIREWALL: ERROR inserting container PREROUTING rule: %v", err)
|
||||
logger.Error("FIREWALL: failed to insert container PREROUTING rule: %v", err)
|
||||
} else {
|
||||
logger.Info("FIREWALL: DNAT rule inserted in container %s: target=%s proto=%s port=%s",
|
||||
selector, targetIP, proto, port)
|
||||
usedContainer = true
|
||||
}
|
||||
} else {
|
||||
log.Printf("FIREWALL: WARNING cannot get PID for container %s: %v, trying host rules", selector, err)
|
||||
logger.Warn("FIREWALL: cannot get PID for container %s: %v, trying host rules", selector, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to host-level PREROUTING if container not used
|
||||
if !usedContainer && policy.Iface != "" {
|
||||
logger.Info("FIREWALL: inserting host-level DNAT rule on interface %s", policy.Iface)
|
||||
if err := o.iptablesMgr.InsertPreroutingRuleOnInterface(policy.Iface, proto, port, targetIP, port, comment); err != nil {
|
||||
log.Printf("FIREWALL: ERROR inserting interface PREROUTING rule: %v", err)
|
||||
logger.Error("FIREWALL: failed to insert interface PREROUTING rule on %s: %v", policy.Iface, err)
|
||||
} else {
|
||||
logger.Info("FIREWALL: host DNAT rule inserted: iface=%s target=%s proto=%s port=%s",
|
||||
policy.Iface, targetIP, proto, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,15 +260,19 @@ func (o *Orchestrator) applyNATRule(ctx context.Context, cfg *config.NetworksCon
|
||||
func (o *Orchestrator) resolveIP(name string) string {
|
||||
// If it's already an IP, return it as CIDR
|
||||
if config.IsIP(name) {
|
||||
return config.ToCIDR(name)
|
||||
result := config.ToCIDR(name)
|
||||
logger.Debug("FIREWALL: resolveIP(%q): direct IP -> %s", name, result)
|
||||
return result
|
||||
}
|
||||
|
||||
// Use the resolver which looks up from networks.json
|
||||
ips := o.resolver.Resolve(name)
|
||||
if len(ips) > 0 {
|
||||
logger.Debug("FIREWALL: resolveIP(%q): resolved -> %s", name, ips[0])
|
||||
return ips[0]
|
||||
}
|
||||
|
||||
logger.Debug("FIREWALL: resolveIP(%q): not found", name)
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user