fix(firewall): iptables detection improved
1. Try setting a dummy output rule 2. Remove the dummy output rule 3. Get the INPUT table policy 4. Set the INPUT table policy to its existing policy
This commit is contained in:
250
internal/firewall/support_test.go
Normal file
250
internal/firewall/support_test.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination=runner_mock_test.go -package $GOPACKAGE github.com/qdm12/golibs/command Runner
|
||||
|
||||
func Test_testIptablesPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
const path = "dummypath"
|
||||
errDummy := errors.New("exit code 4")
|
||||
const inputPolicy = "ACCEPT"
|
||||
|
||||
appendTestRuleMatcher := newCmdMatcher(path,
|
||||
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
||||
"^-j$", "^DROP$")
|
||||
deleteTestRuleMatcher := newCmdMatcher(path,
|
||||
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
||||
"^-j$", "^DROP$")
|
||||
listInputRulesMatcher := newCmdMatcher(path,
|
||||
"^-L$", "^INPUT$")
|
||||
setPolicyMatcher := newCmdMatcher(path,
|
||||
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
|
||||
|
||||
testCases := map[string]struct {
|
||||
buildRunner func(ctrl *gomock.Controller) command.Runner
|
||||
ok bool
|
||||
unsupportedMessage string
|
||||
criticalErrWrapped error
|
||||
criticalErrMessage string
|
||||
}{
|
||||
"append test rule permission denied": {
|
||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).
|
||||
Return("Permission denied (you must be root)", errDummy)
|
||||
return runner
|
||||
},
|
||||
criticalErrWrapped: ErrNetAdminMissing,
|
||||
criticalErrMessage: "NET_ADMIN capability is missing: " +
|
||||
"Permission denied (you must be root)",
|
||||
},
|
||||
"append test rule unsupported": {
|
||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).
|
||||
Return("some output", errDummy)
|
||||
return runner
|
||||
},
|
||||
unsupportedMessage: "some output (exit code 4)",
|
||||
},
|
||||
"remove test rule error": {
|
||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(deleteTestRuleMatcher).
|
||||
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) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(listInputRulesMatcher).
|
||||
Return("Permission denied (you must be root)", errDummy)
|
||||
return runner
|
||||
},
|
||||
criticalErrWrapped: ErrNetAdminMissing,
|
||||
criticalErrMessage: "NET_ADMIN capability is missing: " +
|
||||
"Permission denied (you must be root)",
|
||||
},
|
||||
"list input rules unsupported": {
|
||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(listInputRulesMatcher).
|
||||
Return("some output", errDummy)
|
||||
return runner
|
||||
},
|
||||
unsupportedMessage: "some output (exit code 4)",
|
||||
},
|
||||
"list input rules no policy": {
|
||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(listInputRulesMatcher).
|
||||
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) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(listInputRulesMatcher).
|
||||
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
||||
runner.EXPECT().Run(setPolicyMatcher).
|
||||
Return("Permission denied (you must be root)", errDummy)
|
||||
return runner
|
||||
},
|
||||
criticalErrWrapped: ErrNetAdminMissing,
|
||||
criticalErrMessage: "NET_ADMIN capability is missing: " +
|
||||
"Permission denied (you must be root)",
|
||||
},
|
||||
"set policy unsupported": {
|
||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(listInputRulesMatcher).
|
||||
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
||||
runner.EXPECT().Run(setPolicyMatcher).
|
||||
Return("some output", errDummy)
|
||||
return runner
|
||||
},
|
||||
unsupportedMessage: "some output (exit code 4)",
|
||||
},
|
||||
"success": {
|
||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
||||
runner.EXPECT().Run(listInputRulesMatcher).
|
||||
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
||||
runner.EXPECT().Run(setPolicyMatcher).Return("some output", nil)
|
||||
return runner
|
||||
},
|
||||
ok: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
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 {
|
||||
testCase := testCase
|
||||
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 {
|
||||
testCase := testCase
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user