From 379ca06b4a76b2f88cedee0387a6a741a9f9e832 Mon Sep 17 00:00:00 2001 From: Immanuel Tikhonov Date: Mon, 21 Sep 2026 10:05:50 +0400 Subject: [PATCH] fix: reject invalid network ls filters Signed-off-by: Immanuel Tikhonov --- pkg/cmd/network/list.go | 79 ++++++++++++++++++++++-------------- pkg/cmd/network/list_test.go | 31 ++++++++++++-- 2 files changed, 76 insertions(+), 34 deletions(-) diff --git a/pkg/cmd/network/list.go b/pkg/cmd/network/list.go index 090af4b5ddf..1064b15310b 100644 --- a/pkg/cmd/network/list.go +++ b/pkg/cmd/network/list.go @@ -100,14 +100,14 @@ func List(ctx context.Context, options types.NetworkListOptions) error { return err } - labelFilterFuncs, nameFilterFuncs, err := getNetworkFilterFuncs(filters) + labelFilterFuncs, nameFilterFuncs, driverFilters, err := getNetworkFilterFuncs(filters) if err != nil { return err } if len(filters) > 0 { filtered := make([]*netutil.NetworkConfig, 0) for _, net := range netConfigs { - if networkMatchesFilter(net, labelFilterFuncs, nameFilterFuncs) { + if networkMatchesFilter(net, labelFilterFuncs, nameFilterFuncs, driverFilters) { filtered = append(filtered, net) } } @@ -167,45 +167,62 @@ func List(ctx context.Context, options types.NetworkListOptions) error { return nil } -func getNetworkFilterFuncs(filters []string) ([]func(*map[string]string) bool, []func(string) bool, error) { +func getNetworkFilterFuncs(filters []string) ([]func(*map[string]string) bool, []func(string) bool, []string, error) { labelFilterFuncs := make([]func(*map[string]string) bool, 0) nameFilterFuncs := make([]func(string) bool, 0) + var driverFilters []string for _, filter := range filters { - if strings.HasPrefix(filter, "name") || strings.HasPrefix(filter, "label") { - filter, value, ok := strings.Cut(filter, "=") - if !ok { - continue + key, value, ok := strings.Cut(filter, "=") + if !ok { + return nil, nil, nil, fmt.Errorf("invalid argument %q for \"-f, --filter\": bad format of filter (expected name=value)", filter) + } + switch key { + case "name": + re, err := regexp.Compile(value) + if err != nil { + return nil, nil, nil, err } - switch filter { - case "name": - re, err := regexp.Compile(value) - if err != nil { - return nil, nil, err + nameFilterFuncs = append(nameFilterFuncs, func(name string) bool { + return re.MatchString(name) + }) + case "label": + k, v, hasValue := strings.Cut(value, "=") + labelFilterFuncs = append(labelFilterFuncs, func(labels *map[string]string) bool { + if labels == nil { + return false } - nameFilterFuncs = append(nameFilterFuncs, func(name string) bool { - return re.MatchString(name) - }) - case "label": - k, v, hasValue := strings.Cut(value, "=") - labelFilterFuncs = append(labelFilterFuncs, func(labels *map[string]string) bool { - if labels == nil { - return false - } - val, ok := (*labels)[k] - if !ok || (hasValue && val != v) { - return false - } - return true - }) - } - continue + val, ok := (*labels)[k] + if !ok || (hasValue && val != v) { + return false + } + return true + }) + case "driver": + driverFilters = append(driverFilters, value) + default: + return nil, nil, nil, fmt.Errorf("invalid filter '%s'", key) } } - return labelFilterFuncs, nameFilterFuncs, nil + return labelFilterFuncs, nameFilterFuncs, driverFilters, nil } -func networkMatchesFilter(net *netutil.NetworkConfig, labelFilterFuncs []func(*map[string]string) bool, nameFilterFuncs []func(string) bool) bool { +func networkMatchesFilter(net *netutil.NetworkConfig, labelFilterFuncs []func(*map[string]string) bool, nameFilterFuncs []func(string) bool, driverFilters []string) bool { + if len(driverFilters) > 0 { + if len(net.Plugins) == 0 { + return false + } + matched := false + for _, driver := range driverFilters { + if driver == net.Plugins[0].Network.Type { + matched = true + break + } + } + if !matched { + return false + } + } // Match against the user-visible labels only, so a --filter label= query can // neither select on nor be confused by nerdctl-internal keys. visible := visibleNetworkLabels(net.NerdctlLabels) diff --git a/pkg/cmd/network/list_test.go b/pkg/cmd/network/list_test.go index 87c61e51618..d1e4ba26a51 100644 --- a/pkg/cmd/network/list_test.go +++ b/pkg/cmd/network/list_test.go @@ -28,8 +28,10 @@ import ( func TestNetworkMatchesFilter(t *testing.T) { t.Parallel() labels := map[string]string{"env": "prod", "tier": "web"} + config, err := libcni.ConfListFromBytes([]byte(`{"cniVersion":"1.0.0","name":"frontend","plugins":[{"type":"bridge"}]}`)) + assert.NilError(t, err) net := &netutil.NetworkConfig{ - NetworkConfigList: &libcni.NetworkConfigList{Name: "frontend"}, + NetworkConfigList: config, NerdctlLabels: &labels, } @@ -47,12 +49,35 @@ func TestNetworkMatchesFilter(t *testing.T) { {"matching name only", []string{"name=frontend", "label=env=dev"}, false}, {"matching label only", []string{"name=backend", "label=env=prod"}, false}, {"no match", []string{"name=backend", "label=env=dev"}, false}, + {"matching driver", []string{"driver=bridge"}, true}, + {"nonmatching driver", []string{"driver=macvlan"}, false}, + {"one of multiple drivers", []string{"driver=macvlan", "driver=bridge"}, true}, + {"matching driver and label", []string{"driver=bridge", "label=env=prod"}, true}, + {"matching driver only", []string{"driver=bridge", "label=env=dev"}, false}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - labelFilters, nameFilters, err := getNetworkFilterFuncs(tc.filters) + labelFilters, nameFilters, driverFilters, err := getNetworkFilterFuncs(tc.filters) assert.NilError(t, err) - assert.Equal(t, networkMatchesFilter(net, labelFilters, nameFilters), tc.expected) + assert.Equal(t, networkMatchesFilter(net, labelFilters, nameFilters, driverFilters), tc.expected) + }) + } +} + +func TestNetworkFilterRejectsInvalidInput(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + filter string + want string + }{ + {"name", "bad format of filter"}, + {"label", "bad format of filter"}, + {"names=frontend", "invalid filter 'names'"}, + {"labels=env=prod", "invalid filter 'labels'"}, + } { + t.Run(tc.filter, func(t *testing.T) { + _, _, _, err := getNetworkFilterFuncs([]string{tc.filter}) + assert.ErrorContains(t, err, tc.want) }) } }