fix(firewall): iptables support detection
- Add dummy rule to `INPUT` to test for iptables support - This may resolve #896
This commit is contained in:
@@ -49,7 +49,12 @@ type Config struct { //nolint:maligned
|
|||||||
func NewConfig(ctx context.Context, logger Logger,
|
func NewConfig(ctx context.Context, logger Logger,
|
||||||
runner command.Runner, defaultRoutes []routing.DefaultRoute,
|
runner command.Runner, defaultRoutes []routing.DefaultRoute,
|
||||||
localNetworks []routing.LocalNetwork) (config *Config, err error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -59,7 +64,7 @@ func NewConfig(ctx context.Context, logger Logger,
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
allowedInputPorts: make(map[uint16]map[string]struct{}),
|
allowedInputPorts: make(map[uint16]map[string]struct{}),
|
||||||
ipTables: iptables,
|
ipTables: iptables,
|
||||||
ip6Tables: findIP6tablesSupported(ctx, runner),
|
ip6Tables: ip6tables,
|
||||||
customRulesPath: "/iptables/post-rules.txt",
|
customRulesPath: "/iptables/post-rules.txt",
|
||||||
// Obtained from routing
|
// Obtained from routing
|
||||||
defaultRoutes: defaultRoutes,
|
defaultRoutes: defaultRoutes,
|
||||||
|
|||||||
@@ -14,23 +14,14 @@ import (
|
|||||||
// and returns the iptables path that is supported. If none work, an
|
// and returns the iptables path that is supported. If none work, an
|
||||||
// empty string path is returned.
|
// empty string path is returned.
|
||||||
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
|
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
|
||||||
ip6tablesPath string) {
|
ip6tablesPath string, err error) {
|
||||||
binsToTry := []string{"ip6tables", "ip6tables-nft"}
|
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft")
|
||||||
|
if errors.Is(err, ErrIPTablesNotSupported) {
|
||||||
var err error
|
return "", nil
|
||||||
for _, ip6tablesPath = range binsToTry {
|
} else if err != nil {
|
||||||
cmd := exec.CommandContext(ctx, ip6tablesPath, "-L")
|
return "", err
|
||||||
_, err = runner.Run(cmd)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ip6tablesPath, nil
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip6tablesPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
|
|||||||
@@ -15,41 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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")
|
ErrIPTablesVersionTooShort = errors.New("iptables version string is too short")
|
||||||
ErrPolicyUnknown = errors.New("unknown policy")
|
ErrPolicyUnknown = errors.New("unknown policy")
|
||||||
ErrNeedIP6Tables = errors.New("ip6tables is required, please upgrade your kernel to support it")
|
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 {
|
func appendOrDelete(remove bool) string {
|
||||||
if remove {
|
if remove {
|
||||||
return "--delete"
|
return "--delete"
|
||||||
|
|||||||
63
internal/firewall/support.go
Normal file
63
internal/firewall/support.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user