Skip to content

Commit

Permalink
fix(firewall): iptables support detection
Browse files Browse the repository at this point in the history
- Add dummy rule to `INPUT` to test for iptables support
- This may resolve #896
  • Loading branch information
qdm12 committed Mar 30, 2022
1 parent c2c8dcd commit 7ca26a7
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 48 deletions.
9 changes: 7 additions & 2 deletions internal/firewall/firewall.go
Expand Up @@ -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
}
Expand All @@ -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,
Expand Down
23 changes: 7 additions & 16 deletions internal/firewall/ip6tables.go
Expand Up @@ -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 {
Expand Down
30 changes: 0 additions & 30 deletions internal/firewall/iptables.go
Expand Up @@ -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"
Expand Down
63 changes: 63 additions & 0 deletions 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)
}

0 comments on commit 7ca26a7

Please sign in to comment.