1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

Create a Validate pkg

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
This commit is contained in:
Carlos Eduardo Arango Gutierrez 2023-11-01 15:57:31 +01:00
parent bdfef6df18
commit affb93ea50
No known key found for this signature in database
GPG key ID: 42D9CB42F300A852
7 changed files with 505 additions and 96 deletions

View file

@ -59,8 +59,6 @@ namespaces, excluding `kubernetes.io` namespace and its sub-namespaces
`kubernetes.io` and its sub-namespaces are always denied. `kubernetes.io` and its sub-namespaces are always denied.
This option can be used to exclude some vendors or application specific This option can be used to exclude some vendors or application specific
namespaces. namespaces.
Note that the namespaces `feature.node.kubernetes.io` and `profile.node.kubernetes.io`
and their sub-namespaces are always allowed and cannot be denied.
Default: *empty* Default: *empty*

View file

@ -475,9 +475,6 @@ The namespace part (i.e. prefix) of the labels is controlled by nfd:
[`-extra-label-ns`](../reference/master-commandline-reference.md#-extra-label-ns) [`-extra-label-ns`](../reference/master-commandline-reference.md#-extra-label-ns)
command line flag of nfd-master. command line flag of nfd-master.
e.g: `nfd-master -deny-label-ns="*" -extra-label-ns=example.com` e.g: `nfd-master -deny-label-ns="*" -extra-label-ns=example.com`
- Built-in default namespaces `feature.node.kubernetes.io` and
`profile.node.kubernetes.io` (and their sub-namespaces) are always allowed
and cannot be denied.
## Feature rule format ## Feature rule format

View file

@ -0,0 +1,166 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validate
import (
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
k8sQuantity "k8s.io/apimachinery/pkg/api/resource"
k8svalidation "k8s.io/apimachinery/pkg/util/validation"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
)
var (
// Default error message for invalid label/annotation keys
ErrNSNotAllowed = fmt.Errorf("namespace is not allowed")
// Default error message for invalid label/annotation keys
ErrUnprefixedKeysNotAllowed = fmt.Errorf("unprefixed keys are not allowed")
// Default error for invalid taint effect
ErrInvalidTaintEffect = fmt.Errorf("invalid taint effect")
// Default error for empty taint effect
ErrEmptyTaintEffect = fmt.Errorf("empty taint effect")
)
// Label validates a label key and value and returns an error if the key or
// value is invalid.
func Label(key, value string) error {
//Validate label key and value
if err := k8svalidation.IsQualifiedName(key); len(err) > 0 {
return fmt.Errorf("invalid label key %q: %s", key, strings.Join(err, "; "))
}
// Check label namespace, filter out if ns is not whitelisted
ns, _ := splitNs(key)
// And is not empty
if ns == "" {
return ErrUnprefixedKeysNotAllowed
}
// And is not a denied namespace
if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") {
// And is not a default namespace
if ns != nfdv1alpha1.FeatureLabelNs && ns != nfdv1alpha1.ProfileLabelNs &&
!strings.HasSuffix(ns, nfdv1alpha1.FeatureLabelSubNsSuffix) && !strings.HasSuffix(ns, nfdv1alpha1.ProfileLabelSubNsSuffix) {
return ErrNSNotAllowed
}
}
// Validate label value
if err := k8svalidation.IsValidLabelValue(value); len(err) > 0 {
return fmt.Errorf("invalid labelvalue %q: %s", value, strings.Join(err, "; "))
}
return nil
}
// Annotation validates an annotation key and value and returns an error if the
// key or value is invalid.
func Annotation(key, value string) error {
// Validate the annotation key
if err := k8svalidation.IsQualifiedName(key); len(err) > 0 {
return fmt.Errorf("invalid annotation key %q: %s", key, strings.Join(err, "; "))
}
ns, _ := splitNs(key)
// And is not empty
if ns == "" {
return ErrUnprefixedKeysNotAllowed
}
// And is not a denied namespace
if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") {
// And is not a default namespace
if ns != nfdv1alpha1.FeatureAnnotationNs && !strings.HasSuffix(ns, nfdv1alpha1.FeatureAnnotationSubNsSuffix) {
return ErrNSNotAllowed
}
}
// Validate annotation value
if errs := k8svalidation.IsValidLabelValue(value); len(errs) > 0 {
return fmt.Errorf("invalid annotation value %q: %s", value, strings.Join(errs, "; "))
}
return nil
}
// Taint validates a taint key and value and returns an error if the key or
// value is invalid.
func Taint(taint *corev1.Taint) error {
ns, _ := splitNs(taint.Key)
// And is not empty
if ns == "" {
return ErrUnprefixedKeysNotAllowed
}
// And is not a denied namespace
if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") {
// And is not a default namespace
if ns != nfdv1alpha1.TaintNs && !strings.HasSuffix(ns, nfdv1alpha1.TaintSubNsSuffix) {
return ErrNSNotAllowed
}
}
// Validate taint effect is not empty
if taint.Effect == "" {
return ErrEmptyTaintEffect
}
// Validate effect to be only one of NoSchedule, PreferNoSchedule or NoExecute
if taint.Effect != corev1.TaintEffectNoSchedule &&
taint.Effect != corev1.TaintEffectPreferNoSchedule &&
taint.Effect != corev1.TaintEffectNoExecute {
return ErrInvalidTaintEffect
}
return nil
}
// ExtendedResource validates an extended resource key and value and returns an
// error if the key or value is invalid.
func ExtendedResource(key, value string) error {
//Validate extendedResource name
if errs := k8svalidation.IsQualifiedName(key); len(errs) > 0 {
return fmt.Errorf("invalid name %q: %s", key, strings.Join(errs, "; "))
}
ns, _ := splitNs(key)
// And is not empty
if ns == "" {
return ErrUnprefixedKeysNotAllowed
}
// And is not a denied namespace
if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") {
// And is not a default namespace
if ns != nfdv1alpha1.ExtendedResourceNs && !strings.HasSuffix(ns, nfdv1alpha1.ExtendedResourceSubNsSuffix) {
return ErrNSNotAllowed
}
}
// Static Value (Pre-Defined at the NodeFeatureRule)
_, err := k8sQuantity.ParseQuantity(value)
if err != nil {
return fmt.Errorf("invalid value %s (from %s): %w", value, value, err)
}
return nil
}
// splitNs splits a name into its namespace and name parts
func splitNs(fullname string) (string, string) {
split := strings.SplitN(fullname, "/", 2)
if len(split) == 2 {
return split[0], split[1]
}
return "", fullname
}

View file

@ -0,0 +1,236 @@
package validate
import (
"fmt"
"testing"
corev1 "k8s.io/api/core/v1"
)
func TestAnnotation(t *testing.T) {
tests := []struct {
name string
key string
value string
want error
fail bool
}{
{
name: "Valid annotation",
key: "feature.node.kubernetes.io/feature",
value: "true",
want: nil,
},
{
name: "Invalid annotation key",
key: "invalid-key",
value: "true",
want: ErrUnprefixedKeysNotAllowed,
},
{
name: "Invalid annotation value",
key: "feature.node.kubernetes.io/feature",
value: "invalid value",
want: fmt.Errorf("invalid value \"invalid value\": value must be a valid label value"),
fail: true,
},
{
name: "Denied annotation key",
key: "kubernetes.io/denied",
value: "true",
want: ErrNSNotAllowed,
fail: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Annotation(tt.key, tt.value)
if got != tt.want {
if tt.fail {
return
}
t.Errorf("Annotation() = %v, want %v", got, tt.want)
}
})
}
}
func TestTaint(t *testing.T) {
tests := []struct {
name string
taint *corev1.Taint
want error
}{
{
name: "Valid taint",
taint: &corev1.Taint{
Key: "feature.node.kubernetes.io/taint",
Value: "true",
Effect: corev1.TaintEffectNoSchedule,
},
want: nil,
},
{
name: "UNPREFIXED taint key",
taint: &corev1.Taint{
Key: "invalid-key",
Value: "true",
Effect: corev1.TaintEffectNoSchedule,
},
want: ErrUnprefixedKeysNotAllowed,
},
{
name: "Invalid taint key",
taint: &corev1.Taint{
Key: "invalid.kubernetes.io/invalid-key",
Value: "true",
Effect: corev1.TaintEffectNoSchedule,
},
want: ErrNSNotAllowed,
},
{
name: "Empty taint effect",
taint: &corev1.Taint{
Key: "feature.node.kubernetes.io/taint",
Value: "true",
Effect: "",
},
want: ErrEmptyTaintEffect,
},
{
name: "Invalid taint effect",
taint: &corev1.Taint{
Key: "feature.node.kubernetes.io/taint",
Value: "true",
Effect: "invalid-effect",
},
want: ErrInvalidTaintEffect,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Taint(tt.taint)
if got != tt.want {
t.Errorf("Taint() = %v, want %v", got, tt.want)
}
})
}
}
func TestLabel(t *testing.T) {
tests := []struct {
name string
key string
value string
want error
fail bool
}{
{
name: "Valid label",
key: "feature.node.kubernetes.io/label",
value: "true",
want: nil,
fail: false,
},
{
name: "Valid vendor label",
key: "vendor.io/label",
value: "true",
want: nil,
fail: false,
},
{
name: "Denied label with prefix",
key: "kubernetes.io/label",
value: "true",
want: ErrNSNotAllowed,
fail: true,
},
{
name: "Invalid label key",
key: "invalid-key",
value: "true",
want: ErrNSNotAllowed,
fail: true,
},
{
name: "Invalid label value",
key: "feature.node.kubernetes.io/label",
value: "invalid value",
want: fmt.Errorf("invalid value \"invalid value\": value must be a valid label value"),
fail: true,
},
{
name: "Valid value label",
key: "feature.node.kubernetes.io/label",
value: "true",
want: nil,
fail: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Label(tt.key, tt.value)
if err != tt.want {
if tt.fail {
return
}
t.Errorf("Label() = %v, want %v", err, tt.want)
}
})
}
}
func TestExtendedResource(t *testing.T) {
tests := []struct {
name string
key string
value string
want error
fail bool
}{
{
name: "Valid extended resource",
key: "feature.node.kubernetes.io/extended-resource",
value: "123",
want: nil,
fail: false,
},
{
name: "Invalid extended resource key",
key: "invalid-key",
value: "123",
want: ErrNSNotAllowed,
fail: true,
},
{
name: "Invalid extended resource value",
key: "feature.node.kubernetes.io/extended-resource",
value: "invalid value",
want: fmt.Errorf("invalid value \"invalid value\": value must be a valid label value"),
fail: true,
},
{
name: "Denied extended resource key",
key: "kubernetes.io/extended-resource",
value: "123",
want: ErrNSNotAllowed,
fail: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ExtendedResource(tt.key, tt.value)
if err != tt.want {
if tt.fail {
return
}
t.Errorf("ExtendedResource() = %v, want %v", err, tt.want)
}
})
}
}

View file

@ -382,6 +382,7 @@ func TestSetLabels(t *testing.T) {
expectedStatusPatches := []apihelper.JsonPatch{} expectedStatusPatches := []apihelper.JsonPatch{}
Convey("When node update succeeds", func() { Convey("When node update succeeds", func() {
mockMaster.config.ExtraLabelNs = map[string]struct{}{"example.io": {}}
expectedPatches := []apihelper.JsonPatch{ expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, strings.Join(mockLabelNames, ",")), apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, strings.Join(mockLabelNames, ",")),
} }
@ -400,6 +401,7 @@ func TestSetLabels(t *testing.T) {
}) })
Convey("When -label-whitelist is specified", func() { Convey("When -label-whitelist is specified", func() {
mockMaster.config.ExtraLabelNs = map[string]struct{}{"example.io": {}}
expectedPatches := []apihelper.JsonPatch{ expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, "example.io/feature-2"), apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, "example.io/feature-2"),
apihelper.NewJsonPatch("add", "/metadata/labels", "example.io/feature-2", mockLabels["example.io/feature-2"]), apihelper.NewJsonPatch("add", "/metadata/labels", "example.io/feature-2", mockLabels["example.io/feature-2"]),
@ -443,7 +445,6 @@ func TestSetLabels(t *testing.T) {
} }
mockMaster.deniedNs.normal = map[string]struct{}{"random.denied.ns": {}} mockMaster.deniedNs.normal = map[string]struct{}{"random.denied.ns": {}}
mockMaster.deniedNs.wildcard = map[string]struct{}{"kubernetes.io": {}}
mockMaster.config.ExtraLabelNs = map[string]struct{}{"valid.ns": {}} mockMaster.config.ExtraLabelNs = map[string]struct{}{"valid.ns": {}}
mockMaster.args.Instance = instance mockMaster.args.Instance = instance
mockHelper.On("GetClient").Return(mockClient, nil) mockHelper.On("GetClient").Return(mockClient, nil)
@ -459,10 +460,11 @@ func TestSetLabels(t *testing.T) {
}) })
Convey("When -resource-labels is specified", func() { Convey("When -resource-labels is specified", func() {
mockMaster.config.ExtraLabelNs = map[string]struct{}{"example.io": {}}
expectedPatches := []apihelper.JsonPatch{ expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, "example.io/feature-2"), apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, "example.io/feature-2"),
apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.ExtendedResourceAnnotation, "feature-1,feature-3"),
apihelper.NewJsonPatch("add", "/metadata/labels", "example.io/feature-2", mockLabels["example.io/feature-2"]), apihelper.NewJsonPatch("add", "/metadata/labels", "example.io/feature-2", mockLabels["example.io/feature-2"]),
apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.ExtendedResourceAnnotation, "feature-1,feature-3"),
} }
expectedStatusPatches := []apihelper.JsonPatch{ expectedStatusPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/status/capacity", "feature.node.kubernetes.io/feature-1", mockLabels["feature.node.kubernetes.io/feature-1"]), apihelper.NewJsonPatch("add", "/status/capacity", "feature.node.kubernetes.io/feature-1", mockLabels["feature.node.kubernetes.io/feature-1"]),
@ -502,6 +504,7 @@ func TestSetLabels(t *testing.T) {
func TestFilterLabels(t *testing.T) { func TestFilterLabels(t *testing.T) {
mockHelper := &apihelper.MockAPIHelpers{} mockHelper := &apihelper.MockAPIHelpers{}
mockMaster := newMockMaster(mockHelper) mockMaster := newMockMaster(mockHelper)
mockMaster.config.ExtraLabelNs = map[string]struct{}{"example.io": {}}
mockMaster.deniedNs = deniedNs{ mockMaster.deniedNs = deniedNs{
normal: map[string]struct{}{"": struct{}{}, "kubernetes.io": struct{}{}, "denied.ns": struct{}{}}, normal: map[string]struct{}{"": struct{}{}, "kubernetes.io": struct{}{}, "denied.ns": struct{}{}},
wildcard: map[string]struct{}{".kubernetes.io": struct{}{}, ".denied.subns": struct{}{}}, wildcard: map[string]struct{}{".kubernetes.io": struct{}{}, ".denied.subns": struct{}{}},
@ -940,3 +943,62 @@ func removeLabelsWithPrefix(n *corev1.Node, search string) []apihelper.JsonPatch
return p return p
} }
func TestGetDynamicValue(t *testing.T) {
tests := []struct {
name string
value string
features *nfdv1alpha1.Features
want string
fail bool
}{
{
name: "Valid dynamic value",
value: "@test.feature.LSM",
features: &nfdv1alpha1.Features{
Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{
"test.feature": nfdv1alpha1.AttributeFeatureSet{
Elements: map[string]string{
"LSM": "123",
},
},
},
},
want: "123",
fail: false,
},
{
name: "Invalid feature name",
value: "@invalid",
features: &nfdv1alpha1.Features{},
want: "",
fail: true,
},
{
name: "Element not found",
value: "@test.feature.LSM",
features: &nfdv1alpha1.Features{},
want: "",
fail: true,
},
{
name: "Invalid dynamic value",
value: "@test.feature.LSM",
features: &nfdv1alpha1.Features{},
want: "",
fail: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getDynamicValue(tt.value, tt.features)
if err != nil && !tt.fail {
t.Errorf("getDynamicValue() = %v, want %v", err, tt.want)
}
if got != tt.want {
t.Errorf("getDynamicValue() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -38,10 +38,8 @@ import (
"google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/peer" "google.golang.org/grpc/peer"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
k8sQuantity "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sLabels "k8s.io/apimachinery/pkg/labels" k8sLabels "k8s.io/apimachinery/pkg/labels"
k8svalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection"
@ -55,6 +53,7 @@ import (
"sigs.k8s.io/node-feature-discovery/pkg/apihelper" "sigs.k8s.io/node-feature-discovery/pkg/apihelper"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1" nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/utils"
"sigs.k8s.io/node-feature-discovery/pkg/version" "sigs.k8s.io/node-feature-discovery/pkg/version"
@ -553,24 +552,27 @@ func (m *nfdMaster) filterFeatureLabels(labels Labels, features *nfdv1alpha1.Fea
} }
func (m *nfdMaster) filterFeatureLabel(name, value string, features *nfdv1alpha1.Features) (string, error) { func (m *nfdMaster) filterFeatureLabel(name, value string, features *nfdv1alpha1.Features) (string, error) {
//Validate label name // Check if Value is dynamic
if errs := k8svalidation.IsQualifiedName(name); len(errs) > 0 { var filteredValue string
return "", fmt.Errorf("invalid name %q: %s", name, strings.Join(errs, "; ")) if strings.HasPrefix(value, "@") {
dynamicValue, err := getDynamicValue(value, features)
if err != nil {
return "", err
}
filteredValue = dynamicValue
} else {
filteredValue = value
} }
// Check label namespace, filter out if ns is not whitelisted // Validate
ns, base := splitNs(name) ns, base := splitNs(name)
if ns == "" { err := validate.Label(name, filteredValue)
return "", fmt.Errorf("labels without namespace (prefix/) not allowed") if err == validate.ErrNSNotAllowed || isNamespaceDenied(ns, m.deniedNs.wildcard, m.deniedNs.normal) {
} if _, ok := m.config.ExtraLabelNs[ns]; !ok {
if ns != nfdv1alpha1.FeatureLabelNs && ns != nfdv1alpha1.ProfileLabelNs && return "", fmt.Errorf("namespace %q is not allowed", ns)
!strings.HasSuffix(ns, nfdv1alpha1.FeatureLabelSubNsSuffix) && !strings.HasSuffix(ns, nfdv1alpha1.ProfileLabelSubNsSuffix) {
// If the namespace is denied, and not present in the extraLabelNs, label will be ignored
if isNamespaceDenied(ns, m.deniedNs.wildcard, m.deniedNs.normal) {
if _, ok := m.config.ExtraLabelNs[ns]; !ok {
return "", fmt.Errorf("namespace %q is not allowed", ns)
}
} }
} else if err != nil {
return "", err
} }
// Skip if label doesn't match labelWhiteList // Skip if label doesn't match labelWhiteList
@ -578,24 +580,7 @@ func (m *nfdMaster) filterFeatureLabel(name, value string, features *nfdv1alpha1
return "", fmt.Errorf("%s (%s) does not match the whitelist (%s)", base, name, m.config.LabelWhiteList.Regexp.String()) return "", fmt.Errorf("%s (%s) does not match the whitelist (%s)", base, name, m.config.LabelWhiteList.Regexp.String())
} }
var filteredLabel string return filteredValue, nil
// Dynamic Value
if strings.HasPrefix(value, "@") {
dynamicValue, err := getDynamicValue(value, features)
if err != nil {
return "", err
}
filteredLabel = dynamicValue
} else {
filteredLabel = value
}
// Validate the label value
if errs := k8svalidation.IsValidLabelValue(filteredLabel); len(errs) > 0 {
return "", fmt.Errorf("invalid value %q: %s", filteredLabel, strings.Join(errs, "; "))
}
return filteredLabel, nil
} }
func getDynamicValue(value string, features *nfdv1alpha1.Features) (string, error) { func getDynamicValue(value string, features *nfdv1alpha1.Features) (string, error) {
@ -621,7 +606,7 @@ func filterTaints(taints []corev1.Taint) []corev1.Taint {
outTaints := []corev1.Taint{} outTaints := []corev1.Taint{}
for _, taint := range taints { for _, taint := range taints {
if err := filterTaint(&taint); err != nil { if err := validate.Taint(&taint); err != nil {
klog.ErrorS(err, "ignoring taint", "taint", taint) klog.ErrorS(err, "ignoring taint", "taint", taint)
nodeTaintsRejected.Inc() nodeTaintsRejected.Inc()
} else { } else {
@ -631,19 +616,6 @@ func filterTaints(taints []corev1.Taint) []corev1.Taint {
return outTaints return outTaints
} }
func filterTaint(taint *corev1.Taint) error {
// Check prefix of the key, filter out disallowed ones
ns, _ := splitNs(taint.Key)
if ns == "" {
return fmt.Errorf("taint keys without namespace (prefix/) are not allowed")
}
if ns != nfdv1alpha1.TaintNs && !strings.HasSuffix(ns, nfdv1alpha1.TaintSubNsSuffix) &&
(ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io")) {
return fmt.Errorf("prefix %q is not allowed for taint key", ns)
}
return nil
}
func verifyNodeName(cert *x509.Certificate, nodeName string) error { func verifyNodeName(cert *x509.Certificate, nodeName string) error {
if cert.Subject.CommonName == nodeName { if cert.Subject.CommonName == nodeName {
return nil return nil
@ -809,35 +781,25 @@ func (m *nfdMaster) filterExtendedResources(features *nfdv1alpha1.Features, exte
} }
func filterExtendedResource(name, value string, features *nfdv1alpha1.Features) (string, error) { func filterExtendedResource(name, value string, features *nfdv1alpha1.Features) (string, error) {
// Check if given NS is allowed // Dynamic Value
ns, _ := splitNs(name) var filteredValue string
if ns == "" { if strings.HasPrefix(value, "@") {
return "", fmt.Errorf("extended resource without namespace (prefix/) not allowed") dynamicValue, err := getDynamicValue(value, features)
} if err != nil {
if ns != nfdv1alpha1.ExtendedResourceNs && !strings.HasSuffix(ns, nfdv1alpha1.ExtendedResourceSubNsSuffix) { return "", err
if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") {
return "", fmt.Errorf("namespace %q is not allowed", ns)
} }
filteredValue = dynamicValue
} else {
filteredValue = value
} }
// Dynamic Value // Validate
if strings.HasPrefix(value, "@") { err := validate.ExtendedResource(name, filteredValue)
if element, err := getDynamicValue(value, features); err != nil {
return "", err
} else {
q, err := k8sQuantity.ParseQuantity(element)
if err != nil {
return "", fmt.Errorf("invalid value %s (from %s): %w", element, value, err)
}
return q.String(), nil
}
}
// Static Value (Pre-Defined at the NodeFeatureRule)
q, err := k8sQuantity.ParseQuantity(value)
if err != nil { if err != nil {
return "", fmt.Errorf("invalid value %s: %w", value, err) return "", err
} }
return q.String(), nil
return filteredValue, nil
} }
func (m *nfdMaster) refreshNodeFeatures(cli *kubernetes.Clientset, nodeName string, labels map[string]string, features *nfdv1alpha1.Features) error { func (m *nfdMaster) refreshNodeFeatures(cli *kubernetes.Clientset, nodeName string, labels map[string]string, features *nfdv1alpha1.Features) error {
@ -1331,6 +1293,7 @@ func addNs(src string, nsToAdd string) string {
} }
// splitNs splits a name into its namespace and name parts // splitNs splits a name into its namespace and name parts
// Ported to Validate
func splitNs(fullname string) (string, string) { func splitNs(fullname string) (string, string) {
split := strings.SplitN(fullname, "/", 2) split := strings.SplitN(fullname, "/", 2)
if len(split) == 2 { if len(split) == 2 {
@ -1441,22 +1404,11 @@ func (m *nfdMaster) filterFeatureAnnotations(annotations map[string]string) map[
outAnnotations := make(map[string]string) outAnnotations := make(map[string]string)
for annotation, value := range annotations { for annotation, value := range annotations {
ns, _ := splitNs(annotation)
if ns == "" {
klog.ErrorS(fmt.Errorf("annotations without namespace (prefix/) not allowed"), fmt.Sprintf("Ignoring annotation %s", annotation))
continue
}
// Check annotation namespace, filter out if ns is not whitelisted // Check annotation namespace, filter out if ns is not whitelisted
if ns != nfdv1alpha1.FeatureAnnotationNs && !strings.HasSuffix(ns, nfdv1alpha1.FeatureAnnotationSubNsSuffix) { err := validate.Annotation(annotation, value)
// If the namespace is denied the annotation will be ignored if err != nil {
if ns == "" { klog.ErrorS(err, "ignoring annotation", "annotationKey", annotation, "annotationValue", value)
klog.ErrorS(fmt.Errorf("labels without namespace (prefix/) not allowed"), fmt.Sprintf("Ignoring annotation %s", annotation)) continue
continue
}
if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") || ns == nfdv1alpha1.AnnotationNs {
klog.ErrorS(fmt.Errorf("namespace %q is not allowed", ns), fmt.Sprintf("Ignoring annotation %s", annotation))
continue
}
} }
outAnnotations[annotation] = value outAnnotations[annotation] = value

View file

@ -30,7 +30,5 @@ while true; do
i=$(( $i + 1 )) i=$(( $i + 1 ))
done done
# Configure environment and run tests # Configure environment and run tests
make e2e-test make e2e-test