Skip to content

Commit

Permalink
Merge pull request moby#48721 from akerouanton/45610-filter-by-input-…
Browse files Browse the repository at this point in the history
…iface

libnet/d/bridge: port mappings: filter by input iface
  • Loading branch information
akerouanton authored Jan 14, 2025
2 parents f2854d1 + 433b1f9 commit 6abba37
Show file tree
Hide file tree
Showing 12 changed files with 513 additions and 25 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
libprotobuf-c1 \
libyajl2 \
net-tools \
netcat-openbsd \
patch \
pigz \
sudo \
Expand Down
7 changes: 7 additions & 0 deletions daemon/info_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

runcoptions "github.com/containerd/containerd/api/types/runc/options"
Expand Down Expand Up @@ -159,6 +160,12 @@ func (daemon *Daemon) fillPlatformInfo(ctx context.Context, v *system.Info, sysI
if !v.IPv4Forwarding {
v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled")
}
if filtering, _ := strconv.ParseBool(os.Getenv("DOCKER_DISABLE_INPUT_IFACE_FILTERING")); filtering {
v.Warnings = append(v.Warnings,
"WARNING: input interface filtering is disabled on port mappings, this might be insecure",
"DEPRECATED: DOCKER_DISABLE_INPUT_IFACE_FILTERING is deprecated and will be removed in a future release",
)
}
return nil
}

Expand Down
8 changes: 8 additions & 0 deletions integration/internal/network/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ func WithIPv6() func(*network.CreateOptions) {
}
}

// WithIPv6Disabled makes sure IPv6 is disabled on the network.
func WithIPv6Disabled() func(*network.CreateOptions) {
return func(n *network.CreateOptions) {
enable := false
n.EnableIPv6 = &enable
}
}

// WithInternal enables Internal flag on the create network request
func WithInternal() func(*network.CreateOptions) {
return func(n *network.CreateOptions) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
## Container on a user-defined network, with a port published on a specific HostIP

Adding a network running a container with a mapped port, equivalent to:

docker network create \
-o com.docker.network.bridge.name=bridge1 \
--subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1
docker run --network bridge1 -p 127.0.0.1:8080:80 --name c1 busybox

The filter and nat tables are the same as with no HostIP specified.

<details>
<summary>Filter table</summary>

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/0
2 0 0 ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
3 0 0 DOCKER-ISOLATION-STAGE-1 0 -- * * 0.0.0.0/0 0.0.0.0/0
4 0 0 DOCKER 0 -- * * 0.0.0.0/0 0.0.0.0/0 match-set docker-ext-bridges-v4 dst
5 0 0 ACCEPT 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0
6 0 0 ACCEPT 0 -- bridge1 * 0.0.0.0/0 0.0.0.0/0

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination

Chain DOCKER (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT 6 -- !bridge1 bridge1 0.0.0.0/0 192.0.2.2 tcp dpt:80
2 0 0 DROP 0 -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0
3 0 0 DROP 0 -- !bridge1 bridge1 0.0.0.0/0 0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER-ISOLATION-STAGE-2 0 -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
2 0 0 DOCKER-ISOLATION-STAGE-2 0 -- bridge1 !bridge1 0.0.0.0/0 0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (2 references)
num pkts bytes target prot opt in out source destination
1 0 0 DROP 0 -- * bridge1 0.0.0.0/0 0.0.0.0/0
2 0 0 DROP 0 -- * docker0 0.0.0.0/0 0.0.0.0/0

Chain DOCKER-USER (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 RETURN 0 -- * * 0.0.0.0/0 0.0.0.0/0


-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
-A FORWARD -i docker0 -j ACCEPT
-A FORWARD -i bridge1 -j ACCEPT
-A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER ! -i docker0 -o docker0 -j DROP
-A DOCKER ! -i bridge1 -o bridge1 -j DROP
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-USER -j RETURN


</details>

<details>
<summary>NAT table</summary>

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER 0 -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER 0 -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 MASQUERADE 0 -- * !bridge1 192.0.2.0/24 0.0.0.0/0
2 0 0 MASQUERADE 0 -- * !docker0 172.17.0.0/16 0.0.0.0/0

Chain DOCKER (2 references)
num pkts bytes target prot opt in out source destination
1 0 0 RETURN 0 -- bridge1 * 0.0.0.0/0 0.0.0.0/0
2 0 0 RETURN 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0
3 0 0 DNAT 6 -- !bridge1 * 0.0.0.0/0 127.0.0.1 tcp dpt:8080 to:192.0.2.2:80


-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 192.0.2.0/24 ! -o bridge1 -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i bridge1 -j RETURN
-A DOCKER -i docker0 -j RETURN
-A DOCKER -d 127.0.0.1/32 ! -i bridge1 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80


</details>

The raw table is:

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT 6 -- * * 0.0.0.0/0 127.0.0.1 tcp dpt:8080 ADDRTYPE match dst-type LOCAL limit-in
2 0 0 DROP 6 -- * * 0.0.0.0/0 127.0.0.1 tcp dpt:8080

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination


<details>
<summary>iptables commands</summary>

-P PREROUTING ACCEPT
-P OUTPUT ACCEPT
-A PREROUTING -d 127.0.0.1/32 -p tcp -m tcp --dport 8080 -m addrtype --dst-type LOCAL --limit-iface-in -j ACCEPT
-A PREROUTING -d 127.0.0.1/32 -p tcp -m tcp --dport 8080 -j DROP


</details>

The difference from [port mapping with no HostIP][0] is:

- An ACCEPT rule is added to the PREROUTING chain to drop packets targeting the
mapped port and coming from the interface that has the HostIP assigned.
- And a DROP rule is added too, to drop packets targeting the mapped port but
didn't pass the previous check.

[0]: usernet-portmap.md
1 change: 1 addition & 0 deletions integration/network/bridge/iptablesdoc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ Scenarios:
- [Container on a routed-mode network, with a published port](generated/usernet-portmap-routed.md)
- [Container on a nat-unprotected network, with a published port](generated/usernet-portmap-natunprot.md)
- [Swarm service, with a published port](generated/swarm-portmap.md)
- [Container on a user-defined network, with a port published on a specific HostIP](generated/usernet-portmap-hostip.md)
16 changes: 16 additions & 0 deletions integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ var index = []section{
},
}},
},
{
name: "usernet-portmap-hostip.md",
networks: []networkDesc{{
name: "bridge1",
containers: []ctrDesc{
{
name: "c1",
portMappings: nat.PortMap{"80/tcp": {{HostIP: "127.0.0.1", HostPort: "8080"}}},
},
},
}},
},
}

// iptCmdType is used to look up iptCmds in the markdown (can't use an int
Expand All @@ -188,6 +200,8 @@ const (
iptCmdSFilterDocker4 iptCmdType = "SFilterDocker4"
iptCmdLNat4 iptCmdType = "LNat4"
iptCmdSNat4 iptCmdType = "SNat4"
iptCmdLRaw4 iptCmdType = "LRaw4"
iptCmdSRaw4 iptCmdType = "SRaw4"
)

var iptCmds = map[iptCmdType][]string{
Expand All @@ -198,6 +212,8 @@ var iptCmds = map[iptCmdType][]string{
iptCmdSFilterDocker4: {"iptables", "-S", "DOCKER"},
iptCmdLNat4: {"iptables", "-nvL", "--line-numbers", "-t", "nat"},
iptCmdSNat4: {"iptables", "-S", "-t", "nat"},
iptCmdLRaw4: {"iptables", "-nvL", "--line-numbers", "-t", "raw"},
iptCmdSRaw4: {"iptables", "-S", "-t", "raw"},
}

func TestBridgeIptablesDoc(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Container on a user-defined network, with a port published on a specific HostIP

Adding a network running a container with a mapped port, equivalent to:

docker network create \
-o com.docker.network.bridge.name=bridge1 \
--subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1
docker run --network bridge1 -p 127.0.0.1:8080:80 --name c1 busybox

The filter and nat tables are the same as with no HostIP specified.

<details>
<summary>Filter table</summary>

{{index . "LFilter4"}}

{{index . "SFilter4"}}

</details>

<details>
<summary>NAT table</summary>

{{index . "LNat4"}}

{{index . "SNat4"}}

</details>

The raw table is:

{{index . "LRaw4"}}

<details>
<summary>iptables commands</summary>

{{index . "SRaw4"}}

</details>

The difference from [port mapping with no HostIP][0] is:

- An ACCEPT rule is added to the PREROUTING chain to drop packets targeting the
mapped port and coming from the interface that has the HostIP assigned.
- And a DROP rule is added too, to drop packets targeting the mapped port but
didn't pass the previous check.

[0]: usernet-portmap.md
Loading

0 comments on commit 6abba37

Please sign in to comment.