diff --git a/cmd/nerdctl/network/network_create.go b/cmd/nerdctl/network/network_create.go index ca8b414654f..720a6ff1cec 100644 --- a/cmd/nerdctl/network/network_create.go +++ b/cmd/nerdctl/network/network_create.go @@ -51,6 +51,7 @@ func createCommand() *cobra.Command { cmd.Flags().String("ip-range", "", `Allocate container ip from a sub-range`) cmd.Flags().StringArray("label", nil, "Set metadata for a network") cmd.Flags().Bool("ipv6", false, "Enable IPv6 networking") + cmd.Flags().Bool("internal", false, "Restrict external access to the network") return cmd } @@ -100,6 +101,10 @@ func createAction(cmd *cobra.Command, args []string) error { if err != nil { return err } + internal, err := cmd.Flags().GetBool("internal") + if err != nil { + return err + } return network.Create(types.NetworkCreateOptions{ GOptions: globalOptions, @@ -113,5 +118,6 @@ func createAction(cmd *cobra.Command, args []string) error { IPRange: ipRangeStr, Labels: labels, IPv6: ipv6, + Internal: internal, }, cmd.OutOrStdout()) } diff --git a/cmd/nerdctl/network/network_create_linux_test.go b/cmd/nerdctl/network/network_create_linux_test.go index 6d42809f2ff..843ec56c44b 100644 --- a/cmd/nerdctl/network/network_create_linux_test.go +++ b/cmd/nerdctl/network/network_create_linux_test.go @@ -17,6 +17,7 @@ package network import ( + "encoding/json" "fmt" "net" "strings" @@ -107,6 +108,53 @@ func TestNetworkCreate(t *testing.T) { } }, }, + { + Description: "internal enabled", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", "--internal", data.Identifier()) + netw := nerdtest.InspectNetwork(helpers, data.Identifier()) + assert.Equal(t, len(netw.IPAM.Config), 1) + data.Labels().Set("subnet", netw.IPAM.Config[0].Subnet) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", "--net", data.Identifier(), testutil.CommonImage, "ip", "route") + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + assert.Assert(t, strings.Contains(stdout, data.Labels().Get("subnet"))) + assert.Assert(t, !strings.Contains(stdout, "default ")) + if nerdtest.IsDocker() { + return + } + nativeNet := nerdtest.InspectNetworkNative(helpers, data.Identifier()) + var cni struct { + Plugins []struct { + Type string `json:"type"` + IsGW bool `json:"isGateway"` + IPMasq bool `json:"ipMasq"` + } `json:"plugins"` + } + _ = json.Unmarshal(nativeNet.CNI, &cni) + // bridge plugin assertions and no portmap + foundBridge := false + for _, p := range cni.Plugins { + assert.Assert(t, p.Type != "portmap") + if p.Type == "bridge" { + foundBridge = true + assert.Assert(t, !p.IsGW) + assert.Assert(t, !p.IPMasq) + } + } + assert.Assert(t, foundBridge) + }, + } + }, + }, } testCase.Run(t) diff --git a/docs/command-reference.md b/docs/command-reference.md index fdbf101d27c..0cc67e9571c 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -1169,8 +1169,9 @@ Flags: - :whale: `--ip-range`: Allocate container ip from a sub-range - :whale: `--label`: Set metadata on a network - :whale: `--ipv6`: Enable IPv6. Should be used with a valid subnet. +- :whale: `--internal`: Restrict external access to the network. -Unimplemented `docker network create` flags: `--attachable`, `--aux-address`, `--config-from`, `--config-only`, `--ingress`, `--internal`, `--scope` +Unimplemented `docker network create` flags: `--attachable`, `--aux-address`, `--config-from`, `--config-only`, `--ingress`, `--scope` ### :whale: nerdctl network ls diff --git a/pkg/api/types/network_types.go b/pkg/api/types/network_types.go index 5cb26b3ea15..530f66fa729 100644 --- a/pkg/api/types/network_types.go +++ b/pkg/api/types/network_types.go @@ -35,6 +35,7 @@ type NetworkCreateOptions struct { IPRange string Labels []string IPv6 bool + Internal bool } // NetworkInspectOptions specifies options for `nerdctl network inspect`. diff --git a/pkg/netutil/netutil.go b/pkg/netutil/netutil.go index 66c80c430d7..c3312bb5191 100644 --- a/pkg/netutil/netutil.go +++ b/pkg/netutil/netutil.go @@ -306,11 +306,11 @@ func (e *CNIEnv) CreateNetwork(opts types.NetworkCreateOptions) (*NetworkConfig, if _, ok := netMap[opts.Name]; ok { return nil, errdefs.ErrAlreadyExists } - ipam, err := e.generateIPAM(opts.IPAMDriver, opts.Subnets, opts.Gateway, opts.IPRange, opts.IPAMOptions, opts.IPv6) + ipam, err := e.generateIPAM(opts.IPAMDriver, opts.Subnets, opts.Gateway, opts.IPRange, opts.IPAMOptions, opts.IPv6, opts.Internal) if err != nil { return nil, err } - plugins, err := e.generateCNIPlugins(opts.Driver, opts.Name, ipam, opts.Options, opts.IPv6) + plugins, err := e.generateCNIPlugins(opts.Driver, opts.Name, ipam, opts.Options, opts.IPv6, opts.Internal) if err != nil { return nil, err } diff --git a/pkg/netutil/netutil_unix.go b/pkg/netutil/netutil_unix.go index ffb1d8503a8..f71dc100742 100644 --- a/pkg/netutil/netutil_unix.go +++ b/pkg/netutil/netutil_unix.go @@ -90,7 +90,7 @@ func (n *NetworkConfig) clean() error { return nil } -func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]interface{}, opts map[string]string, ipv6 bool) ([]CNIPlugin, error) { +func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]interface{}, opts map[string]string, ipv6 bool, internal bool) ([]CNIPlugin, error) { var ( plugins []CNIPlugin err error @@ -123,13 +123,21 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string] } bridge.MTU = mtu bridge.IPAM = ipam - bridge.IsGW = true - bridge.IPMasq = iPMasq + bridge.IsGW = !internal + if internal { + bridge.IPMasq = false + } else { + bridge.IPMasq = iPMasq + } bridge.HairpinMode = true if ipv6 { bridge.Capabilities["ips"] = true } - plugins = []CNIPlugin{bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin()} + if internal { + plugins = []CNIPlugin{bridge, newFirewallPlugin(), newTuningPlugin()} + } else { + plugins = []CNIPlugin{bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin()} + } if name != DefaultNetworkName { firewallPath := filepath.Join(e.Path, "firewall") ok, err := firewallPluginGEQ110(firewallPath) @@ -186,13 +194,15 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string] return plugins, nil } -func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool) (map[string]interface{}, error) { +func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) { var ipamConfig interface{} switch driver { case "default", "host-local": ipamConf := newHostLocalIPAMConfig() - ipamConf.Routes = []IPAMRoute{ - {Dst: "0.0.0.0/0"}, + if !internal { + ipamConf.Routes = []IPAMRoute{ + {Dst: "0.0.0.0/0"}, + } } ranges, findIPv4, err := e.parseIPAMRanges(subnets, gatewayStr, ipRangeStr, ipv6) if err != nil { diff --git a/pkg/netutil/netutil_windows.go b/pkg/netutil/netutil_windows.go index 8e0e67a01ed..bd03e6ec4aa 100644 --- a/pkg/netutil/netutil_windows.go +++ b/pkg/netutil/netutil_windows.go @@ -30,7 +30,7 @@ const ( // When creating non-default network without passing in `--subnet` option, // nerdctl assigns subnet address for the creation starting from `StartingCIDR` - // This prevents subnet address overlapping with `DefaultCIDR` used by the default networkß + // This prevents subnet address overlapping with `DefaultCIDR` used by the default network StartingCIDR = "10.4.1.0/24" ) @@ -58,7 +58,7 @@ func (n *NetworkConfig) clean() error { return nil } -func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]interface{}, opts map[string]string, ipv6 bool) ([]CNIPlugin, error) { +func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]interface{}, opts map[string]string, ipv6 bool, internal bool) ([]CNIPlugin, error) { var plugins []CNIPlugin switch driver { case "nat": @@ -71,7 +71,7 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string] return plugins, nil } -func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool) (map[string]interface{}, error) { +func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) { switch driver { case "default": default: diff --git a/pkg/testutil/nerdtest/utilities.go b/pkg/testutil/nerdtest/utilities.go index ca70166b725..b3f1d15ac2e 100644 --- a/pkg/testutil/nerdtest/utilities.go +++ b/pkg/testutil/nerdtest/utilities.go @@ -88,6 +88,19 @@ func InspectNetwork(helpers test.Helpers, name string) dockercompat.Network { return res } +func InspectNetworkNative(helpers test.Helpers, name string) native.Network { + helpers.T().Helper() + var res native.Network + cmd := helpers.Command("network", "inspect", "--mode", "native", name) + cmd.Run(&test.Expected{ + Output: expect.JSON([]native.Network{}, func(dc []native.Network, t tig.T) { + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results") + res = dc[0] + }), + }) + return res +} + func InspectImage(helpers test.Helpers, name string) dockercompat.Image { helpers.T().Helper() var res dockercompat.Image