From 7ca26a717b5daab459c0eb04efbc22228e0d45a1 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Wed, 30 Mar 2022 08:39:32 +0000 Subject: [PATCH] fix(firewall): iptables support detection - Add dummy rule to `INPUT` to test for iptables support - This may resolve #896 --- internal/firewall/firewall.go | 9 +++-- internal/firewall/ip6tables.go | 23 ++++--------- internal/firewall/iptables.go | 30 ---------------- internal/firewall/support.go | 63 ++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 48 deletions(-) create mode 100644 internal/firewall/support.go diff --git a/internal/firewall/firewall.go b/internal/firewall/firewall.go index 3b6cb3f75..e829b6e7f 100644 --- a/internal/firewall/firewall.go +++ b/internal/firewall/firewall.go @@ -49,7 +49,12 @@ type Config struct { //nolint:maligned func NewConfig(ctx context.Context, logger Logger, runner command.Runner, defaultRoutes []routing.DefaultRoute, localNetworks []routing.LocalNetwork) (config *Config, err error) { - iptables, err := findIptablesSupported(ctx, runner) + iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft") + if err != nil { + return nil, err + } + + ip6tables, err := findIP6tablesSupported(ctx, runner) if err != nil { return nil, err } @@ -59,7 +64,7 @@ func NewConfig(ctx context.Context, logger Logger, logger: logger, allowedInputPorts: make(map[uint16]map[string]struct{}), ipTables: iptables, - ip6Tables: findIP6tablesSupported(ctx, runner), + ip6Tables: ip6tables, customRulesPath: "/iptables/post-rules.txt", // Obtained from routing defaultRoutes: defaultRoutes, diff --git a/internal/firewall/ip6tables.go b/internal/firewall/ip6tables.go index 7b261fb35..2384febcb 100644 --- a/internal/firewall/ip6tables.go +++ b/internal/firewall/ip6tables.go @@ -14,23 +14,14 @@ import ( // and returns the iptables path that is supported. If none work, an // empty string path is returned. func findIP6tablesSupported(ctx context.Context, runner command.Runner) ( - ip6tablesPath string) { - binsToTry := []string{"ip6tables", "ip6tables-nft"} - - var err error - for _, ip6tablesPath = range binsToTry { - cmd := exec.CommandContext(ctx, ip6tablesPath, "-L") - _, err = runner.Run(cmd) - if err == nil { - break - } + ip6tablesPath string, err error) { + ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft") + if errors.Is(err, ErrIPTablesNotSupported) { + return "", nil + } else if err != nil { + return "", err } - - if err != nil { - return "" - } - - return ip6tablesPath + return ip6tablesPath, nil } func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error { diff --git a/internal/firewall/iptables.go b/internal/firewall/iptables.go index a65244054..666568d03 100644 --- a/internal/firewall/iptables.go +++ b/internal/firewall/iptables.go @@ -15,41 +15,11 @@ import ( ) var ( - ErrIPTablesNotSupported = errors.New("no iptables supported found") - ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing") ErrIPTablesVersionTooShort = errors.New("iptables version string is too short") ErrPolicyUnknown = errors.New("unknown policy") ErrNeedIP6Tables = errors.New("ip6tables is required, please upgrade your kernel to support it") ) -func findIptablesSupported(ctx context.Context, runner command.Runner) (iptablesPath string, err error) { - binsToTry := []string{"iptables", "iptables-nft"} - - var errMessage string - for _, iptablesPath = range binsToTry { - cmd := exec.CommandContext(ctx, iptablesPath, "-L") - errMessage, err = runner.Run(cmd) - if err == nil { - break - } - - const permissionDeniedString = "Permission denied (you must be root)" - if strings.Contains(errMessage, permissionDeniedString) { - return "", fmt.Errorf("%w: %s (%s)", ErrNetAdminMissing, errMessage, err) - } - - errMessage = fmt.Sprintf("%s (%s)", errMessage, err) - } - - if err != nil { - return "", fmt.Errorf("%w: from %s: last error is: %s", - ErrIPTablesNotSupported, strings.Join(binsToTry, ", "), - errMessage) - } - - return iptablesPath, nil -} - func appendOrDelete(remove bool) string { if remove { return "--delete" diff --git a/internal/firewall/support.go b/internal/firewall/support.go new file mode 100644 index 000000000..3b2126ef1 --- /dev/null +++ b/internal/firewall/support.go @@ -0,0 +1,63 @@ +package firewall + +import ( + "context" + "errors" + "fmt" + "math/rand" + "os/exec" + "strings" + + "github.com/qdm12/golibs/command" +) + +var ( + ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing") + ErrTestRuleCleanup = errors.New("failed cleaning up test rule") + ErrIPTablesNotSupported = errors.New("no iptables supported found") +) + +func checkIptablesSupport(ctx context.Context, runner command.Runner, + iptablesPathsToTry ...string) (iptablesPath string, err error) { + var errMessage string + testInterfaceName := randomInterfaceName() + for _, iptablesPath = range iptablesPathsToTry { + cmd := exec.CommandContext(ctx, iptablesPath, "-A", "INPUT", "-i", testInterfaceName, "-j", "DROP") + errMessage, err = runner.Run(cmd) + if err == nil { + break + } + + const permissionDeniedString = "Permission denied (you must be root)" + if strings.Contains(errMessage, permissionDeniedString) { + return "", fmt.Errorf("%w: %s (%s)", ErrNetAdminMissing, errMessage, err) + } + errMessage = fmt.Sprintf("%s (%s)", errMessage, err) + } + + if err != nil { // all iptables to try failed + return "", fmt.Errorf("%w: from %s: last error is: %s", + ErrIPTablesNotSupported, strings.Join(iptablesPathsToTry, ", "), + errMessage) + } + + // Cleanup test rule + cmd := exec.CommandContext(ctx, iptablesPath, "-D", "INPUT", "-i", testInterfaceName, "-j", "DROP") + errMessage, err = runner.Run(cmd) + if err != nil { + return "", fmt.Errorf("%w: %s (%s)", ErrTestRuleCleanup, errMessage, err) + } + + return iptablesPath, nil +} + +func randomInterfaceName() (interfaceName string) { + const size = 15 + letterRunes := []rune("abcdefghijklmnopqrstuvwxyz0123456789") + b := make([]rune, size) + for i := range b { + letterIndex := rand.Intn(len(letterRunes)) //nolint:gosec + b[i] = letterRunes[letterIndex] + } + return string(b) +}