feat: add logging to Docker and iptables operations, fix iptables path
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:
gyurix
2026-06-15 17:05:53 +02:00
parent 3172023254
commit 27607d1a2e
8 changed files with 401 additions and 66 deletions
+64 -10
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"os/exec"
"strings"
"firewall_containers/network-go/logger"
)
// IPTablesAPI defines the interface for iptables operations, enabling mock implementations for testing
@@ -40,13 +42,16 @@ func NewManager(debug bool) *Manager {
// detectBinary checks if iptables-legacy is available (matching shell script logic)
func (m *Manager) detectBinary() {
logger.Info("IPTABLES: detecting iptables binary")
cmd := exec.Command("/usr/sbin/iptables-legacy", "-L")
output, err := cmd.CombinedOutput()
if err == nil && strings.Contains(string(output), "DOCKER-USER") {
m.binary = "/usr/sbin/iptables-legacy"
logger.Info("IPTABLES: detected iptables-legacy (DOCKER-USER chain present)")
return
}
m.binary = "/usr/sbin/iptables"
logger.Info("IPTABLES: using default iptables binary")
}
// Binary returns the detected iptables binary path
@@ -56,23 +61,29 @@ func (m *Manager) Binary() string {
// run executes an iptables command on the host
func (m *Manager) run(args ...string) error {
cmdStr := m.binary + " " + strings.Join(args, " ")
logger.Info("IPTABLES: executing: %s", cmdStr)
if m.debug {
fmt.Printf("[IPTABLES DEBUG] %s %s\n", m.binary, strings.Join(args, " "))
logger.Debug("IPTABLES DEBUG: %s %s", m.binary, strings.Join(args, " "))
}
cmd := exec.Command(m.binary, args...)
output, err := cmd.CombinedOutput()
if err != nil {
logger.Error("IPTABLES: command failed: %s\noutput: %s", cmdStr, strings.TrimSpace(string(output)))
return fmt.Errorf("iptables %s failed: %w\noutput: %s", strings.Join(args, " "), err, string(output))
}
logger.Debug("IPTABLES: command succeeded: %s", cmdStr)
if len(output) > 0 {
logger.Debug("IPTABLES: output: %s", strings.TrimSpace(string(output)))
}
return nil
}
// runInContainer executes an iptables command inside a container's network namespace via nsenter
func (m *Manager) runInContainer(pid int, table string, args ...string) error {
iptPath := "/usr/sbin/iptables-legacy"
if !strings.Contains(m.binary, "legacy") {
iptPath = "/usr/sbin/iptables"
}
// Use the same binary path that was auto-detected on the host
// Alpine installs iptables at /usr/sbin/ not /sbin/
iptPath := m.binary
fullArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath}
if table != "" {
@@ -80,14 +91,22 @@ func (m *Manager) runInContainer(pid int, table string, args ...string) error {
}
fullArgs = append(fullArgs, args...)
cmdStr := "nsenter " + strings.Join(fullArgs, " ")
logger.Info("IPTABLES: executing nsenter for PID %d: %s", pid, cmdStr)
if m.debug {
fmt.Printf("[IPTABLES DEBUG] nsenter %s\n", strings.Join(fullArgs, " "))
logger.Debug("IPTABLES DEBUG: nsenter %s", strings.Join(fullArgs, " "))
}
cmd := exec.Command("nsenter", fullArgs...)
output, err := cmd.CombinedOutput()
if err != nil {
logger.Error("IPTABLES: nsenter command failed for PID %d\noutput: %s", pid, strings.TrimSpace(string(output)))
return fmt.Errorf("nsenter iptables failed: %w\noutput: %s", err, string(output))
}
logger.Info("IPTABLES: nsenter command succeeded for PID %d", pid)
if len(output) > 0 {
logger.Debug("IPTABLES: nsenter output: %s", strings.TrimSpace(string(output)))
}
return nil
}
@@ -95,38 +114,47 @@ func (m *Manager) runInContainer(pid int, table string, args ...string) error {
// Logs a warning if it fails (e.g. read-only filesystem in a container),
// since this should be configured at the host level.
func (m *Manager) EnsureIPForward() error {
logger.Info("IPTABLES: enabling IP forwarding (echo 1 > /proc/sys/net/ipv4/ip_forward)")
cmd := exec.Command("sh", "-c", "echo 1 > /proc/sys/net/ipv4/ip_forward")
output, err := cmd.CombinedOutput()
if err != nil {
logger.Warn("IPTABLES: failed to enable ip_forward (expected if read-only fs): %v", err)
return fmt.Errorf("failed to enable ip_forward: %w\noutput: %s", err, string(output))
}
logger.Info("IPTABLES: IP forwarding enabled")
return nil
}
// EnsureEstablishedRelated inserts an ESTABLISHED,RELATED accept rule at the top of a chain
func (m *Manager) EnsureEstablishedRelated(chain string) error {
logger.Debug("IPTABLES: checking for ESTABLISHED,RELATED rule in %s", chain)
checkArgs := []string{"-w", "-n", "-L", chain}
cmd := exec.Command(m.binary, checkArgs...)
output, err := cmd.Output()
if err != nil {
logger.Debug("IPTABLES: could not list chain %s: %v", chain, err)
return nil
}
if !strings.Contains(string(output), "ESTABLISHED") || !strings.Contains(string(output), "RELATED") {
logger.Info("IPTABLES: inserting ESTABLISHED,RELATED ACCEPT rule in %s", chain)
args := []string{"-w", "-I", chain, "-m", "state", "--state", "established,related", "-j", "ACCEPT"}
return m.run(args...)
}
logger.Debug("IPTABLES: ESTABLISHED,RELATED rule already exists in %s", chain)
return nil
}
// DeleteLine deletes a specific line number from a chain
func (m *Manager) DeleteLine(chain string, lineNum string) error {
logger.Info("IPTABLES: deleting line %s from chain %s", lineNum, chain)
args := []string{"-w", "-D", chain, lineNum}
return m.run(args...)
}
// DeleteLineInContainer deletes a specific line number from a chain inside a container namespace
func (m *Manager) DeleteLineInContainer(pid int, table, chain, lineNum string) error {
logger.Info("IPTABLES: deleting line %s from chain %s in container PID %d", lineNum, chain, pid)
args := []string{"-D", chain, lineNum}
return m.runInContainer(pid, table, args...)
}
@@ -141,6 +169,7 @@ func (m *Manager) getLineNumbers(chain, table string, grepPatterns ...string) []
cmd := exec.Command(m.binary, args...)
output, err := cmd.Output()
if err != nil {
logger.Debug("IPTABLES: getLineNumbers failed for %s: %v", chain, err)
return nil
}
@@ -161,12 +190,16 @@ func (m *Manager) getLineNumbers(chain, table string, grepPatterns ...string) []
}
}
}
logger.Debug("IPTABLES: getLineNumbers chain=%s patterns=%v found=%v", chain, grepPatterns, matchingLines)
return matchingLines
}
// deleteMatchingLines deletes all lines in a chain matching the given patterns
func (m *Manager) deleteMatchingLines(chain, table string, grepPatterns ...string) error {
lines := m.getLineNumbers(chain, table, grepPatterns...)
if len(lines) > 0 {
logger.Info("IPTABLES: deleting %d matching lines from %s: %v", len(lines), chain, lines)
}
for i := len(lines) - 1; i >= 0; i-- {
if err := m.DeleteLine(chain, lines[i]); err != nil {
return err
@@ -177,15 +210,13 @@ func (m *Manager) deleteMatchingLines(chain, table string, grepPatterns ...strin
// deleteMatchingLinesInContainer deletes matching lines inside a container namespace
func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, grepPatterns ...string) error {
iptPath := "/usr/sbin/iptables-legacy"
if !strings.Contains(m.binary, "legacy") {
iptPath = "/usr/sbin/iptables"
}
iptPath := m.binary
nsenterArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath, "-w", "--line-number", "-n", "-t", table, "-L", chain}
cmd := exec.Command("nsenter", nsenterArgs...)
output, err := cmd.Output()
if err != nil {
logger.Debug("IPTABLES: deleteMatchingLinesInContainer list failed for PID %d chain %s: %v", pid, chain, err)
return nil
}
@@ -207,6 +238,11 @@ func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, g
}
}
if len(matchingLines) > 0 {
logger.Info("IPTABLES: deleting %d matching lines from container PID %d chain %s: %v",
len(matchingLines), pid, chain, matchingLines)
}
for i := len(matchingLines) - 1; i >= 0; i-- {
if err := m.DeleteLineInContainer(pid, table, chain, matchingLines[i]); err != nil {
return err
@@ -217,6 +253,8 @@ func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, g
// InsertPreroutingRule inserts a DNAT PREROUTING rule on the host
func (m *Manager) InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error {
logger.Info("IPTABLES: inserting PREROUTING DNAT rule: src=%s proto=%s sport=%s -> dst=%s dport=%s comment=%q",
sourceIP, proto, sourcePort, targetIP, targetPort, comment)
patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment}
if err := m.deleteMatchingLines("PREROUTING", "nat", patterns...); err != nil {
return fmt.Errorf("failed to delete old PREROUTING rules: %w", err)
@@ -235,6 +273,8 @@ func (m *Manager) InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, ta
// InsertPreroutingRuleOnInterface inserts a DNAT PREROUTING rule on a specific interface
func (m *Manager) InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targetIP, targetPort, comment string) error {
logger.Info("IPTABLES: inserting PREROUTING DNAT rule on interface %s: proto=%s dport=%s -> %s:%s comment=%q",
iface, proto, sourcePort, targetIP, targetPort, comment)
args := []string{
"-w", "-t", "nat", "-I", "PREROUTING",
"-i", iface,
@@ -248,6 +288,8 @@ func (m *Manager) InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targ
// InsertPostroutingMasquerade inserts a MASQUERADE POSTROUTING rule on the host
func (m *Manager) InsertPostroutingMasquerade(sourceCIDR, proto, sourcePort, comment string) error {
logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule: src=%s proto=%s sport=%s comment=%q",
sourceCIDR, proto, sourcePort, comment)
patterns := []string{"MASQUERADE", comment, sourceCIDR, sourcePort}
if err := m.deleteMatchingLines("POSTROUTING", "nat", patterns...); err != nil {
return fmt.Errorf("failed to delete old POSTROUTING rules: %w", err)
@@ -266,6 +308,8 @@ func (m *Manager) InsertPostroutingMasquerade(sourceCIDR, proto, sourcePort, com
// InsertPostroutingMasqueradeForTarget inserts a MASQUERADE POSTROUTING rule for a target
func (m *Manager) InsertPostroutingMasqueradeForTarget(targetCIDR, proto, targetPort, comment string) error {
logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule for target: dst=%s proto=%s dport=%s comment=%q",
targetCIDR, proto, targetPort, comment)
patterns := []string{"MASQUERADE", comment, targetCIDR, targetPort}
if err := m.deleteMatchingLines("POSTROUTING", "nat", patterns...); err != nil {
return fmt.Errorf("failed to delete old POSTROUTING rules: %w", err)
@@ -284,6 +328,8 @@ func (m *Manager) InsertPostroutingMasqueradeForTarget(targetCIDR, proto, target
// InsertForwardAccept inserts a FORWARD ACCEPT rule on the host
func (m *Manager) InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment string) error {
logger.Info("IPTABLES: inserting FORWARD ACCEPT rule: chain=%s src=%s dst=%s proto=%s sport=%s dport=%s comment=%q",
chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment)
var grepPatterns []string
grepPatterns = append(grepPatterns, proto)
if sourceIP != "" {
@@ -323,7 +369,11 @@ func (m *Manager) InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePo
// DeleteForwardAccept deletes a FORWARD ACCEPT rule by comment
func (m *Manager) DeleteForwardAccept(chain, comment string) error {
logger.Info("IPTABLES: deleting FORWARD ACCEPT rules in %s with comment=%q", chain, comment)
lines := m.getLineNumbers(chain, "", comment)
if len(lines) > 0 {
logger.Info("IPTABLES: found %d FORWARD ACCEPT rules to delete: %v", len(lines), lines)
}
for i := len(lines) - 1; i >= 0; i-- {
if err := m.DeleteLine(chain, lines[i]); err != nil {
return err
@@ -334,6 +384,8 @@ func (m *Manager) DeleteForwardAccept(chain, comment string) error {
// InsertPreroutingRuleInContainer inserts a DNAT PREROUTING rule inside a container namespace
func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error {
logger.Info("IPTABLES: inserting PREROUTING DNAT rule in container PID %d: src=%s proto=%s dport=%s -> %s:%s comment=%q",
pid, sourceIP, proto, sourcePort, targetIP, targetPort, comment)
patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment}
if err := m.deleteMatchingLinesInContainer(pid, "nat", "PREROUTING", patterns...); err != nil {
return fmt.Errorf("failed to delete old container PREROUTING rules: %w", err)
@@ -352,6 +404,8 @@ func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sour
// InsertPostroutingMasqueradeInContainer inserts a MASQUERADE POSTROUTING rule inside a container namespace
func (m *Manager) InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, proto, sourcePort, comment string) error {
logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule in container PID %d: src=%s proto=%s sport=%s comment=%q",
pid, sourceCIDR, proto, sourcePort, comment)
patterns := []string{"MASQUERADE", comment, sourceCIDR, sourcePort}
if err := m.deleteMatchingLinesInContainer(pid, "nat", "POSTROUTING", patterns...); err != nil {
return fmt.Errorf("failed to delete old container POSTROUTING rules: %w", err)