-
Notifications
You must be signed in to change notification settings - Fork 881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DOCKER-USER chain not created when IPTableEnable=false. #2471
Conversation
Please sign your commits following these rules: $ git clone -b "master" git@github.com:suwang48404/libnetwork.git somewhere
$ cd somewhere
$ git commit --amend -s --no-edit
$ git push -f Amending updates the existing PR. You DO NOT need to open a new one. |
f148a99
to
55c5699
Compare
Some useful documentation in this area - This wont take care of the cases where IMHO a clean way to fix this could be to incorporate this flag into |
Arko, I did spend sometime considering the approach, and feel this commit addressed user's need while not potentially causing unexpected behavior. -- iptables=false (a.k.a prevent docker manipulate iptables) has a limited use cases for conrtainers that with (network mode=null, host, macvlan), in these uses cases this commit allows DOCKER-USER not created, and DOCKER-INGRESS chain will also not be created as there is no swarm service. In another word, I ask in what circumstances where a user (correctly) wants to disable iptable manipulation by docker engine? maybe when containers need no network connectivities (network mode =null), or container expose network port/ip directly (network mode = host, macvlan). In these cases, DOCKER-USER/DOCKER-INGRESS will not be created. Thx, Su |
I know that there's definitely users running with |
Hi Sebastian, thx for the heads-up with regard to iptable=false use case, i did not know that ... The question is are we comfortable changing existing docker engine ibehavior and strictly enforcing iptable=false, i.e no iptable programming at all, or error on caution? Thx, Su |
Hi Sebastiaan, can u please help review. As this will eventually be moved to dockerd? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left some comments; still trying to figure out what the best approach would be (not a fan of the current global variable / sync.once)
firewall_test.go
Outdated
{enable: true, insert: true}, | ||
} | ||
|
||
for _, i := range tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you use a different name for this variable? i
is more commonly used for the index, than for the value
firewall_test.go
Outdated
|
||
const ( | ||
fwdChainName = "FORWARD" | ||
usrChainName = "DOCKER-USER" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there's an existing const for this in the actual (non-testing) code; https://github.com/docker/libnetwork/blob/90afbb01e1d8acacb505a092744ea42b9f167377/firewall_linux.go#L8
firewall_test.go
Outdated
} | ||
rules := strings.Split(string(output), "\n") | ||
if !enabled { | ||
if len(rules)-1 != 1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having to use len(rules)-1
everywhere makes it a bit difficult to read; IIUC, the -1
is to compensate for the trailing newline; perhaps add a small helper function that reads all rules in a chain, and drops the last one?
Something like;
func getRules(t *testing.T, chain string) []string {
t.Helper()
output, err := iptables.Raw("-S", chain)
assert.NilError(t, err, "chain %s: failed to get rules", chain)
rules := strings.Split(string(output), "\n")
if len(rules) > 0 {
rules = rules[:len(rules)-1]
}
return rules
}
Also, perhaps instead of checking number of rules, we should add an expected []string
to the tests, to check that the expected rules are created 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
take ur firewall_test.go instead
firewall_test.go
Outdated
t.Fatal(err) | ||
} | ||
if insert { | ||
_, err = iptables.Raw("-A", fwdChainName, "-j", "DROP") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to understand what the insert
option is testing, as we don't have a combination iptables=false
, and insert=true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the reason to have insert is that in case there is already a rule in FORWARD chain, DOCKER-CHAIN rule would be placed ahead of that rule when iptable=true; if iptable=false, it is moot because we do add DOCKER-CHAIN rule to FORWARD chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so the thing that worries me a bit in the test, is that we're creating a new code-path just for the test. The production code does not have check wether or not this should be called. (The production code would (currently) call setupArrangeUserFilterRule
and arrangeUserFilterRule
when calling controller.NewNetwork()
, which is not the code-path we're testing.
And because of userChainOnce
and ctrl
being global, we can't test different permutations of iptables being enabled/disabled etc.
firewall_linux.go
Outdated
iptables.OnReloaded(func() { | ||
var ( | ||
ctrl *controller = nil | ||
userChainOnce sync.Once |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering why these are implemented as global variables, and not as a property on the controller
IIUC, currently, the DOCKER-USER
chain is created when first calling NewNetwork
- should we create it on New()
(so when constructing the controller?) Or perhaps have an exported function that can be called from dockerd
when starting the daemon.
I think the intent is not to cal this only once, but to make it idempotent, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, cal_ once is the mean to goal of not setup DOCKER-USER chain multiple times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but effectively we're making controller a singleton(-like) construct, which makes the testing difficult (see above), but also makes the first controller that's created "magic" as that's the controller that now owns iptables.
So, wondering if there should be a sync.Once
per controller, and only call if it iptables is enabled. I see arrangeUserFilterRule
is already (largely) idempotent (as in: it stops adding the return/jump rules if the chain already existed).
Is iptables
enabled/disabled a global option, or one per controller (wondering) 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fix assumes there shall be only one instance of controller per docker engine (instantiated moby/moby/daemon/daemon_unix.go). I'd think (please correct me) many things will be broken if multiple libnetwork controllers are created within the same engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in another word, iptables enabled/disabled is a global option, it is executed based on singleton controller configuration. Hope this make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realise having multiple controllers is not a realistic scenario; it's not what the code reflects though; there's a New()
, which "creates a new instance of network controller", so it's not a singleton.
If:
iptables
enable/disabled is not something that can be changed during the lifetime of a controller (wether that be a single one, or multiple)arrangeUserFilterRule
is idempotent (even if multiple controllers were instantiated, they would not trip over each other, because they'd only mutate iptables if theDOCKER-USER
chain doesn't exist)
Then, why not setup the DOCKER-USER chain when instantiating the controller? (I don't think a sync.once
would even be needed in that case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry I misunderstood u. I have moved invocation of setupArrangeUserFilterRule to controller.go:New(), and do away with sync.once.
Thx, Su
@thaJeztah I have taken ur firewall_test.go, it is much cleaner, thanks alot. thx, Su |
3806be4
to
f137635
Compare
@suwang48404 , changes look good to me. If I remember correctly there were few places I mentioned #2339 that this code path fix didnt cover. Can you pls take a look and let me know if that is ok. coping from #2339 PR comment. fwMarker()
redirector()
programIngress()
with the PR, I don't think we are covering these code path. we might end up leaving the daemon iptable configuration in faulty state.``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@selansen The fix is pointed fix for docekr-user chain only. The iptable usage is pervasive in libnetwork, we do not have a good understanding and/or test metrics to confidently disable all iptable programming when iptable=false. IMHO, the risk is to0 high for such general fix. |
This fix addresses https://docker.atlassian.net/browse/ENGCORE-1115 Expected behaviors upon docker engine restarts: 1. IPTableEnable=true, DOCKER-USER chain present -- no change to DOCKER-USER chain 2. IPTableEnable=true, DOCKER-USER chain not present -- DOCKER-USER chain created and inserted top of FORWARD chain. 3. IPTableEnable=false, DOCKER-USER chain present -- no change to DOCKER-USER chain the rational is that DOCKER-USER is populated and may be used by end-user for purpose other than filtering docker container traffic. Thus even if IPTableEnable=false, docker engine does not touch pre-existing DOCKER-USER chain. 4. IPTableEnable=false, DOCKER-USER chain not present -- DOCKER-USER chain is not created. Signed-off-by: Su Wang <su.wang@docker.com>
@thaJeztah PTAL ? it looks good to me . wanted to make sure your review comments are updated and you approve this one. |
PTAL @selansen @thaJeztah |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
full diff: moby/libnetwork@ef149a9...1a17fb3 - moby/libnetwork#2538 produce an error with invalid address pool - addresses moby#40388 dockerd ignores the --default-address-pool option - moby/libnetwork#2471 DOCKER-USER chain not created when IPTableEnable=false - moby/libnetwork#2544 Fix NPE due to null value returned by ep.Iface() - carries moby/libnetwork#2239 Prevent NPE in addServiceInfoToCluster() - addresses moby#37506 Error initializing docker.server while starting daemon by systemd Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
full diff: moby/libnetwork@ef149a9...1a17fb3 - moby/libnetwork#2538 produce an error with invalid address pool - addresses moby/moby#40388 dockerd ignores the --default-address-pool option - moby/libnetwork#2471 DOCKER-USER chain not created when IPTableEnable=false - moby/libnetwork#2544 Fix NPE due to null value returned by ep.Iface() - carries moby/libnetwork#2239 Prevent NPE in addServiceInfoToCluster() - addresses moby/moby#37506 Error initializing docker.server while starting daemon by systemd Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Upstream-commit: c3808634e7a44fc5b4fb8caacd9d079f7e0a0fee Component: engine
arrangeUserFilterRule uses the package-level [`ctrl` variable][1], which holds a reference to a controller instance. This variable is set by [`setupArrangeUserFilterRule()`][2], which is called when initialization a controller ([`libnetwork.New`][3]). In normal circumstances, there would only be one controller, created during daemon startup, and the instance of the controller would be the same as the controller that `NewNetwork` is called from, but there's no protection for the `ctrl` variable, and various integration tests create their own controller instance. The global `ctrl` var was introduced in [54e7900fb89b1aeeb188d935f29cf05514fd419b][4], with the assumption that [only one controller could ever exist][5]. This patch tries to reduce uses of the `ctrl` variable, and as we're calling this code from inside a method on a specific controller, we inline the code and use that specific controller instead. [1]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L12 [2]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L14-L17 [3]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/controller.go#L163 [4]: moby/libnetwork@54e7900 [5]: moby/libnetwork#2471 (comment) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
arrangeUserFilterRule uses the package-level [`ctrl` variable][1], which holds a reference to a controller instance. This variable is set by [`setupArrangeUserFilterRule()`][2], which is called when initialization a controller ([`libnetwork.New`][3]). In normal circumstances, there would only be one controller, created during daemon startup, and the instance of the controller would be the same as the controller that `NewNetwork` is called from, but there's no protection for the `ctrl` variable, and various integration tests create their own controller instance. The global `ctrl` var was introduced in [54e7900fb89b1aeeb188d935f29cf05514fd419b][4], with the assumption that [only one controller could ever exist][5]. This patch tries to reduce uses of the `ctrl` variable, and as we're calling this code from inside a method on a specific controller, we inline the code and use that specific controller instead. [1]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L12 [2]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L14-L17 [3]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/controller.go#L163 [4]: moby/libnetwork@54e7900 [5]: moby/libnetwork#2471 (comment) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
arrangeUserFilterRule uses the package-level [`ctrl` variable][1], which holds a reference to a controller instance. This variable is set by [`setupArrangeUserFilterRule()`][2], which is called when initialization a controller ([`libnetwork.New`][3]). In normal circumstances, there would only be one controller, created during daemon startup, and the instance of the controller would be the same as the controller that `NewNetwork` is called from, but there's no protection for the `ctrl` variable, and various integration tests create their own controller instance. The global `ctrl` var was introduced in [54e7900fb89b1aeeb188d935f29cf05514fd419b][4], with the assumption that [only one controller could ever exist][5]. This patch tries to reduce uses of the `ctrl` variable, and as we're calling this code from inside a method on a specific controller, we inline the code and use that specific controller instead. [1]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L12 [2]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L14-L17 [3]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/controller.go#L163 [4]: moby/libnetwork@54e7900 [5]: moby/libnetwork#2471 (comment) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
arrangeUserFilterRule uses the package-level [`ctrl` variable][1], which holds a reference to a controller instance. This variable is set by [`setupArrangeUserFilterRule()`][2], which is called when initialization a controller ([`libnetwork.New`][3]). In normal circumstances, there would only be one controller, created during daemon startup, and the instance of the controller would be the same as the controller that `NewNetwork` is called from, but there's no protection for the `ctrl` variable, and various integration tests create their own controller instance. The global `ctrl` var was introduced in [54e7900fb89b1aeeb188d935f29cf05514fd419b][4], with the assumption that [only one controller could ever exist][5]. This patch tries to reduce uses of the `ctrl` variable, and as we're calling this code from inside a method on a specific controller, we inline the code and use that specific controller instead. [1]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L12 [2]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/firewall_linux.go#L14-L17 [3]: https://github.com/moby/moby/blob/37b908aa628ccf8f1e10182d2fc049423707422d/libnetwork/controller.go#L163 [4]: moby/libnetwork@54e7900 [5]: moby/libnetwork#2471 (comment) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This fix addresses https://docker.atlassian.net/browse/ENGCORE-1115
Expected behaviors upon docker engine restarts:
-- no change to DOCKER-USER chain
-- DOCKER-USER chain created and inserted top of FORWARD
chain.
-- no change to DOCKER-USER chain
the rational is that DOCKER-USER is populated
and may be used by end-user for purpose other than
filtering docker container traffic. Thus even if
IPTableEnable=false, docker engine does not touch
pre-existing DOCKER-USER chain.
-- DOCKER-USER chain is not created.