mirror of
https://github.com/qdm12/gluetun.git
synced 2025-12-11 04:38:54 -06:00
346 lines
11 KiB
Go
346 lines
11 KiB
Go
package firewall
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newAppendTestRuleMatcher(path string) *cmdMatcher {
|
|
return newCmdMatcher(path,
|
|
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
|
"^-j$", "^DROP$")
|
|
}
|
|
|
|
func newDeleteTestRuleMatcher(path string) *cmdMatcher {
|
|
return newCmdMatcher(path,
|
|
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
|
"^-j$", "^DROP$")
|
|
}
|
|
|
|
func newListInputRulesMatcher(path string) *cmdMatcher {
|
|
return newCmdMatcher(path,
|
|
"^-L$", "^INPUT$")
|
|
}
|
|
|
|
func newSetPolicyMatcher(path, inputPolicy string) *cmdMatcher { //nolint:unparam
|
|
return newCmdMatcher(path,
|
|
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
|
|
}
|
|
|
|
func Test_checkIptablesSupport(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
errDummy := errors.New("exit code 4")
|
|
const inputPolicy = "ACCEPT"
|
|
|
|
testCases := map[string]struct {
|
|
buildRunner func(ctrl *gomock.Controller) CmdRunner
|
|
iptablesPathsToTry []string
|
|
iptablesPath string
|
|
errSentinel error
|
|
errMessage string
|
|
}{
|
|
"critical error when checking": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
|
Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
|
Return("output", errDummy)
|
|
return runner
|
|
},
|
|
iptablesPathsToTry: []string{"path1", "path2"},
|
|
errSentinel: ErrTestRuleCleanup,
|
|
errMessage: "for path1: failed cleaning up test rule: " +
|
|
"output (exit code 4)",
|
|
},
|
|
"found valid path": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
|
Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
|
Return("", nil)
|
|
runner.EXPECT().Run(newListInputRulesMatcher("path1")).
|
|
Return("Chain INPUT (policy "+inputPolicy+")", nil)
|
|
runner.EXPECT().Run(newSetPolicyMatcher("path1", inputPolicy)).
|
|
Return("", nil)
|
|
return runner
|
|
},
|
|
iptablesPathsToTry: []string{"path1", "path2"},
|
|
iptablesPath: "path1",
|
|
},
|
|
"all permission denied": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
|
Return("Permission denied (you must be root) more context", errDummy)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
|
Return("context: Permission denied (you must be root)", errDummy)
|
|
return runner
|
|
},
|
|
iptablesPathsToTry: []string{"path1", "path2"},
|
|
errSentinel: ErrNetAdminMissing,
|
|
errMessage: "NET_ADMIN capability is missing: " +
|
|
"path1: Permission denied (you must be root) more context (exit code 4); " +
|
|
"path2: context: Permission denied (you must be root) (exit code 4)",
|
|
},
|
|
"no valid path": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
|
Return("output 1", errDummy)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
|
Return("output 2", errDummy)
|
|
return runner
|
|
},
|
|
iptablesPathsToTry: []string{"path1", "path2"},
|
|
errSentinel: ErrIPTablesNotSupported,
|
|
errMessage: "no iptables supported found: " +
|
|
"errors encountered are: " +
|
|
"path1: output 1 (exit code 4); " +
|
|
"path2: output 2 (exit code 4)",
|
|
},
|
|
}
|
|
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctrl := gomock.NewController(t)
|
|
|
|
runner := testCase.buildRunner(ctrl)
|
|
|
|
iptablesPath, err := checkIptablesSupport(ctx, runner, testCase.iptablesPathsToTry...)
|
|
|
|
require.ErrorIs(t, err, testCase.errSentinel)
|
|
if testCase.errSentinel != nil {
|
|
assert.EqualError(t, err, testCase.errMessage)
|
|
}
|
|
assert.Equal(t, testCase.iptablesPath, iptablesPath)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_testIptablesPath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
const path = "dummypath"
|
|
errDummy := errors.New("exit code 4")
|
|
const inputPolicy = "ACCEPT"
|
|
|
|
testCases := map[string]struct {
|
|
buildRunner func(ctrl *gomock.Controller) CmdRunner
|
|
ok bool
|
|
unsupportedMessage string
|
|
criticalErrWrapped error
|
|
criticalErrMessage string
|
|
}{
|
|
"append test rule permission denied": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
|
Return("Permission denied (you must be root)", errDummy)
|
|
return runner
|
|
},
|
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
|
},
|
|
"append test rule unsupported": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
|
Return("some output", errDummy)
|
|
return runner
|
|
},
|
|
unsupportedMessage: "some output (exit code 4)",
|
|
},
|
|
"remove test rule error": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).
|
|
Return("some output", errDummy)
|
|
return runner
|
|
},
|
|
criticalErrWrapped: ErrTestRuleCleanup,
|
|
criticalErrMessage: "failed cleaning up test rule: some output (exit code 4)",
|
|
},
|
|
"list input rules permission denied": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
|
Return("Permission denied (you must be root)", errDummy)
|
|
return runner
|
|
},
|
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
|
},
|
|
"list input rules unsupported": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
|
Return("some output", errDummy)
|
|
return runner
|
|
},
|
|
unsupportedMessage: "some output (exit code 4)",
|
|
},
|
|
"list input rules no policy": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
|
Return("some\noutput", nil)
|
|
return runner
|
|
},
|
|
criticalErrWrapped: ErrInputPolicyNotFound,
|
|
criticalErrMessage: "input policy not found: in INPUT rules: some\noutput",
|
|
},
|
|
"set policy permission denied": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
|
Return("\nChain INPUT (policy "+inputPolicy+")\nAA\n", nil)
|
|
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
|
|
Return("Permission denied (you must be root)", errDummy)
|
|
return runner
|
|
},
|
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
|
},
|
|
"set policy unsupported": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
|
Return("\nChain INPUT (policy "+inputPolicy+")\nBB\n", nil)
|
|
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
|
|
Return("some output", errDummy)
|
|
return runner
|
|
},
|
|
unsupportedMessage: "some output (exit code 4)",
|
|
},
|
|
"success": {
|
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
|
runner := NewMockCmdRunner(ctrl)
|
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
|
Return("\nChain INPUT (policy "+inputPolicy+")\nCC\n", nil)
|
|
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
|
|
Return("some output", nil)
|
|
return runner
|
|
},
|
|
ok: true,
|
|
},
|
|
}
|
|
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctrl := gomock.NewController(t)
|
|
|
|
runner := testCase.buildRunner(ctrl)
|
|
|
|
ok, unsupportedMessage, criticalErr := testIptablesPath(ctx, path, runner)
|
|
|
|
assert.Equal(t, testCase.ok, ok)
|
|
assert.Equal(t, testCase.unsupportedMessage, unsupportedMessage)
|
|
assert.ErrorIs(t, criticalErr, testCase.criticalErrWrapped)
|
|
if testCase.criticalErrWrapped != nil {
|
|
assert.EqualError(t, criticalErr, testCase.criticalErrMessage)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_isPermissionDenied(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := map[string]struct {
|
|
errMessage string
|
|
ok bool
|
|
}{
|
|
"empty error": {},
|
|
"other error": {
|
|
errMessage: "some error",
|
|
},
|
|
"permission denied": {
|
|
errMessage: "Permission denied (you must be root) have you tried blabla",
|
|
ok: true,
|
|
},
|
|
}
|
|
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ok := isPermissionDenied(testCase.errMessage)
|
|
|
|
assert.Equal(t, testCase.ok, ok)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_extractInputPolicy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := map[string]struct {
|
|
line string
|
|
policy string
|
|
ok bool
|
|
}{
|
|
"empty line": {},
|
|
"random line": {
|
|
line: "random line",
|
|
},
|
|
"only first part": {
|
|
line: "Chain INPUT (policy ",
|
|
},
|
|
"empty policy": {
|
|
line: "Chain INPUT (policy )",
|
|
},
|
|
"ACCEPT policy": {
|
|
line: "Chain INPUT (policy ACCEPT)",
|
|
policy: "ACCEPT",
|
|
ok: true,
|
|
},
|
|
|
|
"ACCEPT policy with surrounding garbage": {
|
|
line: "garbage Chain INPUT (policy ACCEPT\t) )g()arbage",
|
|
policy: "ACCEPT",
|
|
ok: true,
|
|
},
|
|
}
|
|
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
policy, ok := extractInputPolicy(testCase.line)
|
|
|
|
assert.Equal(t, testCase.policy, policy)
|
|
assert.Equal(t, testCase.ok, ok)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_randomInterfaceName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const expectedRegex = `^[a-z0-9]{15}$`
|
|
interfaceName := randomInterfaceName()
|
|
assert.Regexp(t, expectedRegex, interfaceName)
|
|
}
|