mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Add daemon option --allow-direct-routing
Per-network option com.docker.network.bridge.trusted-host-interfaces accepts a list of interfaces that are allowed to route directly to a container's published ports in a bridge network with nat enabled. This daemon level option disables direct access filtering, enabling direct access to published ports on container addresses in all bridge networks, via all host interfaces. It overlaps with short-term env-var workaround: DOCKER_INSECURE_NO_IPTABLES_RAW=1 - it does not allow packets sent from outside the host to reach ports published only to 127.0.0.1 - it will outlive iptables (the workaround was initially intended for hosts that do not have kernel support for the "raw" iptables table). Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
@@ -38,6 +38,7 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
|
||||
flags.IPVar(&conf.BridgeConfig.DefaultIP, "ip", net.IPv4zero, "Host IP for port publishing from the default bridge network")
|
||||
flags.BoolVar(&conf.BridgeConfig.EnableUserlandProxy, "userland-proxy", true, "Use userland proxy for loopback traffic")
|
||||
flags.StringVar(&conf.BridgeConfig.UserlandProxyPath, "userland-proxy-path", conf.BridgeConfig.UserlandProxyPath, "Path to the userland proxy binary")
|
||||
flags.BoolVar(&conf.BridgeConfig.AllowDirectRouting, "allow-direct-routing", false, "Allow remote access to published ports on container IP addresses")
|
||||
flags.StringVar(&conf.CgroupParent, "cgroup-parent", "", "Set parent cgroup for all containers")
|
||||
flags.StringVar(&conf.RemappedRoot, "userns-remap", "", "User/Group setting for user namespaces")
|
||||
flags.BoolVar(&conf.LiveRestoreEnabled, "live-restore", false, "Enable live restore of docker when containers are still running")
|
||||
|
||||
@@ -48,6 +48,7 @@ type BridgeConfig struct {
|
||||
EnableIPMasq bool `json:"ip-masq,omitempty"`
|
||||
EnableUserlandProxy bool `json:"userland-proxy,omitempty"`
|
||||
UserlandProxyPath string `json:"userland-proxy-path,omitempty"`
|
||||
AllowDirectRouting bool `json:"allow-direct-routing,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultBridgeConfig stores all the parameters for the default bridge network.
|
||||
|
||||
@@ -933,6 +933,7 @@ func driverOptions(config *config.Config) nwconfig.Option {
|
||||
"EnableIP6Tables": config.BridgeConfig.EnableIP6Tables,
|
||||
"EnableUserlandProxy": config.BridgeConfig.EnableUserlandProxy,
|
||||
"UserlandProxyPath": config.BridgeConfig.UserlandProxyPath,
|
||||
"AllowDirectRouting": config.BridgeConfig.AllowDirectRouting,
|
||||
"Rootless": config.Rootless,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -917,7 +917,30 @@ func TestDirectRemoteAccessOnExposedPort(t *testing.T) {
|
||||
// skip.If(t, testEnv.IsRootless, "rootlesskit has its own netns")
|
||||
|
||||
ctx := setupTest(t)
|
||||
d := daemon.New(t)
|
||||
d.StartWithBusybox(ctx, t)
|
||||
defer d.Stop(t)
|
||||
testDirectRemoteAccessOnExposedPort(t, ctx, d, false)
|
||||
}
|
||||
|
||||
// TestAllowDirectRemoteAccessOnExposedPort checks that remote hosts can directly
|
||||
// reach a container on one of its exposed ports - if the daemon is running with
|
||||
// option --allow-direct-routing.
|
||||
func TestAllowDirectRemoteAccessOnExposedPort(t *testing.T) {
|
||||
// This test checks iptables rules that live in dockerd's netns. In the case
|
||||
// of rootlesskit, this is not the same netns as the host, so they don't
|
||||
// have any effect.
|
||||
// TODO(aker): we need to figure out what we want to do for rootlesskit.
|
||||
// skip.If(t, testEnv.IsRootless, "rootlesskit has its own netns")
|
||||
|
||||
ctx := setupTest(t)
|
||||
d := daemon.New(t)
|
||||
d.StartWithBusybox(ctx, t, "--allow-direct-routing")
|
||||
defer d.Stop(t)
|
||||
testDirectRemoteAccessOnExposedPort(t, ctx, d, true)
|
||||
}
|
||||
|
||||
func testDirectRemoteAccessOnExposedPort(t *testing.T, ctx context.Context, d *daemon.Daemon, allowDirectRouting bool) {
|
||||
const (
|
||||
hostIPv4 = "192.168.120.2"
|
||||
hostIPv6 = "fdbc:277b:d40b::2"
|
||||
@@ -936,10 +959,6 @@ func TestDirectRemoteAccessOnExposedPort(t *testing.T) {
|
||||
netip.MustParsePrefix("192.168.120.3/24"),
|
||||
netip.MustParsePrefix("fdbc:277b:d40b::3/64"))
|
||||
|
||||
d := daemon.New(t)
|
||||
d.StartWithBusybox(ctx, t)
|
||||
defer d.Stop(t)
|
||||
|
||||
c := d.NewClientT(t)
|
||||
defer c.Close()
|
||||
for _, tc := range []struct {
|
||||
@@ -1006,7 +1025,7 @@ func TestDirectRemoteAccessOnExposedPort(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
expDirectAccess := tc.gwMode == "routed" || tc.gwMode == "nat-unprotected" || tc.trusted
|
||||
expDirectAccess := tc.gwMode == "routed" || tc.gwMode == "nat-unprotected" || tc.trusted || allowDirectRouting
|
||||
skip.If(t, expDirectAccess && testEnv.IsRootless(), "rootlesskit doesn't support routed mode as it's running in a separate netns")
|
||||
|
||||
testutil.StartSpan(ctx, t)
|
||||
|
||||
@@ -66,6 +66,7 @@ type configuration struct {
|
||||
EnableUserlandProxy bool
|
||||
UserlandProxyPath string
|
||||
Rootless bool
|
||||
AllowDirectRouting bool
|
||||
}
|
||||
|
||||
// networkConfiguration for network specific configuration
|
||||
@@ -508,9 +509,10 @@ func (d *driver) configure(option map[string]interface{}) error {
|
||||
|
||||
var err error
|
||||
d.firewaller, err = iptabler.NewIptabler(iptabler.FirewallConfig{
|
||||
IPv4: config.EnableIPTables,
|
||||
IPv6: config.EnableIP6Tables,
|
||||
Hairpin: !config.EnableUserlandProxy || config.UserlandProxyPath == "",
|
||||
IPv4: config.EnableIPTables,
|
||||
IPv6: config.EnableIP6Tables,
|
||||
Hairpin: !config.EnableUserlandProxy || config.UserlandProxyPath == "",
|
||||
AllowDirectRouting: config.AllowDirectRouting,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -37,6 +37,7 @@ func (n *Network) modEndpoint(ctx context.Context, epIPv4, epIPv6 netip.Addr, en
|
||||
// It is a no-op if:
|
||||
// - the network is internal
|
||||
// - gateway mode is "nat-unprotected" or "routed".
|
||||
// - direct routing is enabled at the daemon level.
|
||||
// - "raw" rules are disabled (possibly because the host doesn't have the necessary
|
||||
// kernel support).
|
||||
//
|
||||
@@ -54,7 +55,7 @@ func (n *Network) filterDirectAccess(ctx context.Context, ipv iptables.IPVersion
|
||||
// direct routing has since been disabled, the rules need to be deleted when
|
||||
// cleanup happens on restart. This also means a change in config over a
|
||||
// live-restore restart will take effect.
|
||||
if rawRulesDisabled(ctx) {
|
||||
if n.ipt.AllowDirectRouting || rawRulesDisabled(ctx) {
|
||||
enable = false
|
||||
}
|
||||
for _, ifName := range n.TrustedHostInterfaces {
|
||||
|
||||
@@ -35,9 +35,10 @@ const (
|
||||
)
|
||||
|
||||
type FirewallConfig struct {
|
||||
IPv4 bool
|
||||
IPv6 bool
|
||||
Hairpin bool
|
||||
IPv4 bool
|
||||
IPv6 bool
|
||||
Hairpin bool
|
||||
AllowDirectRouting bool
|
||||
}
|
||||
|
||||
type Iptabler struct {
|
||||
|
||||
@@ -415,6 +415,10 @@ unix://[/path/to/socket] to use.
|
||||
Use TLS and verify the remote (daemon: verify client, client: verify daemon).
|
||||
Default is **false**.
|
||||
|
||||
**--allow-direct-routing**=**true**|**false**
|
||||
Allow remote access to published ports on container IP addresses.
|
||||
Default is **false**.
|
||||
|
||||
**--userland-proxy**=**true**|**false**
|
||||
Rely on a userland proxy implementation for inter-container and
|
||||
outside-to-container loopback communications. Default is **true**.
|
||||
|
||||
Reference in New Issue
Block a user