diff --git a/cmd/nfd-master/main.go b/cmd/nfd-master/main.go index b42cc6286e..cbcb36cc87 100644 --- a/cmd/nfd-master/main.go +++ b/cmd/nfd-master/main.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "regexp" + "strings" "k8s.io/klog/v2" @@ -85,12 +86,15 @@ func main() { func initFlags(flagset *flag.FlagSet) *master.Args { args := &master.Args{ LabelWhiteList: utils.RegexpVal{Regexp: *regexp.MustCompile("")}, + DenyLabelNs: map[string]struct{}{"kubernetes.io": {}}, } flagset.StringVar(&args.CaFile, "ca-file", "", "Root certificate for verifying connections") flagset.StringVar(&args.CertFile, "cert-file", "", "Certificate used for authenticating connections") + flagset.Var(&args.DenyLabelNs, "deny-label-ns", + "Comma separated list of denied label namespaces") flagset.Var(&args.ExtraLabelNs, "extra-label-ns", "Comma separated list of allowed extra label namespaces") flagset.StringVar(&args.Instance, "instance", "", @@ -122,5 +126,24 @@ func initFlags(flagset *flag.FlagSet) *master.Args { "Verify worker node name against the worker's TLS certificate. "+ "Only takes effect when TLS authentication has been enabled.") + normalDeniedNs, wildcardDeniedNs := preProcessDeniedNamespaces(args.DenyLabelNs) + args.NormalDenyLabelNs = normalDeniedNs + args.WildcardDenyLabelNs = wildcardDeniedNs + return args } + +// Seperate denied namespaces into two lists: +// one contains wildcard namespaces the other contains normal namespaces +func preProcessDeniedNamespaces(deniedNs map[string]struct{}) (normalDeniedNs map[string]struct{}, wildcardDeniedNs map[string]struct{}) { + normalDeniedNs = map[string]struct{}{} + wildcardDeniedNs = map[string]struct{}{} + for ns := range deniedNs { + if strings.HasPrefix(ns, "*") { + wildcardDeniedNs[ns] = struct{}{} + } else { + normalDeniedNs[ns] = struct{}{} + } + } + return +} diff --git a/deployment/helm/node-feature-discovery/templates/master.yaml b/deployment/helm/node-feature-discovery/templates/master.yaml index 8c66a79553..c259df529b 100644 --- a/deployment/helm/node-feature-discovery/templates/master.yaml +++ b/deployment/helm/node-feature-discovery/templates/master.yaml @@ -86,6 +86,9 @@ spec: {{- if .Values.master.extraLabelNs | empty | not }} - "-extra-label-ns={{- join "," .Values.master.extraLabelNs }}" {{- end }} + {{- if .Values.master.denyLabelNs | empty | not }} + - "-deny-label-ns={{- join "," .Values.master.denyLabelNs }}" + {{- end }} {{- if .Values.master.resourceLabels | empty | not }} - "-resource-labels={{- join "," .Values.master.resourceLabels }}" {{- end }} diff --git a/deployment/helm/node-feature-discovery/values.yaml b/deployment/helm/node-feature-discovery/values.yaml index 799a0bec3d..5f2835c24b 100644 --- a/deployment/helm/node-feature-discovery/values.yaml +++ b/deployment/helm/node-feature-discovery/values.yaml @@ -17,6 +17,7 @@ master: port: 8080 instance: featureApi: + denyLabelNs: [] extraLabelNs: [] resourceLabels: [] crdController: null diff --git a/docs/reference/master-commandline-reference.md b/docs/reference/master-commandline-reference.md index 7b8c000ae1..b4cb44874f 100644 --- a/docs/reference/master-commandline-reference.md +++ b/docs/reference/master-commandline-reference.md @@ -234,6 +234,22 @@ Example: nfd-master -extra-label-ns=vendor-1.com,vendor-2.io ``` +### -deny-label-ns + +The `-deny-label-ns` flag specifies a comma-separated list of excluded +label namespaces. By default, nfd-master allows creating labels in all +namespaces, excluding `kubernetes.io` namespace and its sub-namespaces +(e.g. `sub.ns.kubernetes.io`). +This option can be used to exclude some vendors or application specific namespaces. + +Default: *kubernetes.io* + +Example: + +```bash +nfd-master -deny-label-ns=kubernetes.io,vendor-2.io +``` + ### -resource-labels The `-resource-labels` flag specifies a comma-separated list of features to be diff --git a/docs/usage/customization-guide.md b/docs/usage/customization-guide.md index 1bdb0799cf..68e21ad435 100644 --- a/docs/usage/customization-guide.md +++ b/docs/usage/customization-guide.md @@ -249,9 +249,6 @@ feature.node.kubernetes.io/my-feature.2: "myvalue" my.namespace/my-feature.3: "456" ``` -Note that in the example above `-extra-label-ns=my.namespace` must be specified -on the nfd-master command line. - ### Hooks **DEPRECATED** The `local` source executes hooks found in @@ -308,6 +305,8 @@ namespace must be explicitly allowed with the `-extra-label-ns` command line flag of nfd-master if using something else than `[.]feature.node.kubernetes.io` or `[.]profile.node.kubernetes.io`. +Also, the namespace can be excluded using the +`-deny-label-ns` command line flag of nfd-master. ### Mounts @@ -421,6 +420,9 @@ The namespace part (i.e. prefix) of the labels is controlled by nfd: - Additional namespaces may be enabled with the [`-extra-label-ns`](../reference/master-commandline-reference.md#-extra-label-ns) command line flag of nfd-master +- Namespaces may be excluded with the + [`-deny-label-ns`](../reference/master-commandline-reference.md#-deny-label-ns) + command line flag of nfd-master ## Label rule format diff --git a/pkg/nfd-master/nfd-master-internal_test.go b/pkg/nfd-master/nfd-master-internal_test.go index cf50e49064..c59203df90 100644 --- a/pkg/nfd-master/nfd-master-internal_test.go +++ b/pkg/nfd-master/nfd-master-internal_test.go @@ -352,16 +352,17 @@ func TestSetLabels(t *testing.T) { }) }) - Convey("When -extra-label-ns and -instance are specified", func() { + Convey("When -extra-label-ns, -deny-label-ns and -instance are specified", func() { // In the gRPC request the label names may omit the default ns instance := "foo" vendorFeatureLabel := "vendor." + nfdv1alpha1.FeatureLabelNs + "/feature-4" vendorProfileLabel := "vendor." + nfdv1alpha1.ProfileLabelNs + "/feature-5" mockLabels := map[string]string{"feature-1": "val-1", - "valid.ns/feature-2": "val-2", - "invalid.ns/feature-3": "val-3", - vendorFeatureLabel: " val-4", - vendorProfileLabel: " val-5"} + "valid.ns/feature-2": "val-2", + "invalid.ns/feature-3": "val-3", + "random.denied.ns/feature-3": "val-4", + vendorFeatureLabel: " val-5", + vendorProfileLabel: " val-6"} expectedPatches := []apihelper.JsonPatch{ apihelper.NewJsonPatch("add", "/metadata/annotations", instance+"."+nfdv1alpha1.WorkerVersionAnnotation, workerVer), apihelper.NewJsonPatch("add", "/metadata/annotations", @@ -374,6 +375,7 @@ func TestSetLabels(t *testing.T) { apihelper.NewJsonPatch("add", "/metadata/labels", vendorProfileLabel, mockLabels[vendorProfileLabel]), } + mockMaster.args.DenyLabelNs = map[string]struct{}{"denied.ns": {}} mockMaster.args.ExtraLabelNs = map[string]struct{}{"valid.ns": {}} mockMaster.args.Instance = instance mockHelper.On("GetClient").Return(mockClient, nil) diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go index 33549f9b46..4c1b6e2556 100644 --- a/pkg/nfd-master/nfd-master.go +++ b/pkg/nfd-master/nfd-master.go @@ -63,6 +63,9 @@ type Annotations map[string]string type Args struct { CaFile string CertFile string + DenyLabelNs utils.StringSetVal + WildcardDenyLabelNs utils.StringSetVal + NormalDenyLabelNs utils.StringSetVal ExtraLabelNs utils.StringSetVal Instance string KeyFile string @@ -392,7 +395,7 @@ func (m *nfdMaster) updateMasterNode() error { // into extended resources. This function also handles proper namespacing of // labels and ERs, i.e. adds the possibly missing default namespace for labels // arriving through the gRPC API. -func filterFeatureLabels(labels Labels, extraLabelNs map[string]struct{}, labelWhiteList regexp.Regexp, extendedResourceNames map[string]struct{}) (Labels, ExtendedResources) { +func filterFeatureLabels(labels Labels, extraLabelNs map[string]struct{}, wildcardDeniedLabelNs map[string]struct{}, normalDeniedLabelsNs map[string]struct{}, labelWhiteList regexp.Regexp, extendedResourceNames map[string]struct{}) (Labels, ExtendedResources) { outLabels := Labels{} for label, value := range labels { @@ -408,6 +411,10 @@ func filterFeatureLabels(labels Labels, extraLabelNs map[string]struct{}, labelW klog.Errorf("Namespace %q is not allowed. Ignoring label %q\n", ns, label) continue } + if isNamespaceDenied(ns, wildcardDeniedLabelNs, normalDeniedLabelsNs) { + klog.Errorf("Namespace %q is not allowed. Ignoring label %q\n", ns, label) + continue + } } // Skip if label doesn't match labelWhiteList @@ -449,6 +456,21 @@ func verifyNodeName(cert *x509.Certificate, nodeName string) error { return nil } +func isNamespaceDenied(labelNs string, wildcardDeniedNs map[string]struct{}, normalDeniedNs map[string]struct{}) bool { + for deniedNs := range normalDeniedNs { + if labelNs == deniedNs { + return true + } + } + for deniedNs := range wildcardDeniedNs { + deniedSuffix := strings.TrimLeft(deniedNs, "*") + if strings.HasSuffix(labelNs, deniedSuffix) { + return true + } + } + return false +} + // SetLabels implements LabelerServer func (m *nfdMaster) SetLabels(c context.Context, r *pb.SetLabelsRequest) (*pb.SetLabelsReply, error) { err := authorizeClient(c, m.args.VerifyNodeName, r.NodeName) @@ -583,7 +605,7 @@ func (m *nfdMaster) refreshNodeFeatures(cli *kubernetes.Clientset, nodeName stri labels[k] = v } - labels, extendedResources := filterFeatureLabels(labels, m.args.ExtraLabelNs, m.args.LabelWhiteList.Regexp, m.args.ResourceLabels) + labels, extendedResources := filterFeatureLabels(labels, m.args.ExtraLabelNs, m.args.WildcardDenyLabelNs, m.args.NormalDenyLabelNs, m.args.LabelWhiteList.Regexp, m.args.ResourceLabels) var taints []corev1.Taint if m.args.EnableTaints { diff --git a/pkg/utils/flags.go b/pkg/utils/flags.go index cfd23806e4..2045bd5fa2 100644 --- a/pkg/utils/flags.go +++ b/pkg/utils/flags.go @@ -76,8 +76,7 @@ func (a *StringSetVal) String() string { if *a == nil { return "" } - - vals := make([]string, len(*a), 0) + vals := make([]string, 0, len(*a)) for val := range *a { vals = append(vals, val) }