diff --git a/go.mod b/go.mod index 1de91416f6f7b..babc81f770b81 100644 --- a/go.mod +++ b/go.mod @@ -236,6 +236,7 @@ require ( replace ( github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 + github.com/openshift/apiserver-library-go => github.com/jubittajohn/apiserver-library-go v0.0.0-20251201210649-a39660da613e k8s.io/api => ./staging/src/k8s.io/api k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery diff --git a/go.sum b/go.sum index 1dcfa9c29b8fe..3b1daa8f81394 100644 --- a/go.sum +++ b/go.sum @@ -232,6 +232,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jubittajohn/apiserver-library-go v0.0.0-20251201210649-a39660da613e h1:1Uye9+b2YHXLRU746t0hq9AGe8vDoK/LT0UeFi+ldpw= +github.com/jubittajohn/apiserver-library-go v0.0.0-20251201210649-a39660da613e/go.mod h1:zm2/rIUp0p83pz0/1kkSoKTqhTr3uUKSKQ9fP7Z3g7Y= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= @@ -310,8 +312,6 @@ github.com/openshift-eng/openshift-tests-extension v0.0.0-20250916161632-d81c090 github.com/openshift-eng/openshift-tests-extension v0.0.0-20250916161632-d81c09058835/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M= github.com/openshift/api v0.0.0-20251015095338-264e80a2b6e7 h1:Ot2fbEEPmF3WlPQkyEW/bUCV38GMugH/UmZvxpWceNc= github.com/openshift/api v0.0.0-20251015095338-264e80a2b6e7/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY= -github.com/openshift/apiserver-library-go v0.0.0-20251015164739-79d04067059d h1:Mfya3RxHWvidOrKyHj3bmFn5x2B89DLZIvDAhwm+C2s= -github.com/openshift/apiserver-library-go v0.0.0-20251015164739-79d04067059d/go.mod h1:zm2/rIUp0p83pz0/1kkSoKTqhTr3uUKSKQ9fP7Z3g7Y= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235 h1:9JBeIXmnHlpXTQPi7LPmu1jdxznBhAE7bb1K+3D8gxY= github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235/go.mod h1:L49W6pfrZkfOE5iC1PqEkuLkXG4W0BX4w8b+L2Bv7fM= diff --git a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccadmission/admission.go b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccadmission/admission.go index 02ceaacd4026e..ec383db5cbcd2 100644 --- a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccadmission/admission.go +++ b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccadmission/admission.go @@ -47,6 +47,7 @@ type constraint struct { *admission.Handler sccLister securityv1listers.SecurityContextConstraintsLister namespaceLister corev1listers.NamespaceLister + nodeLister corev1listers.NodeLister listersSynced []cache.InformerSynced authorizer authorizer.Authorizer } @@ -81,7 +82,7 @@ func NewConstraint() *constraint { // any change that claims the pod is no longer privileged will be removed. That should hold until // we get a true old/new set of objects in. func (c *constraint) Admit(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error { - if ignore, err := shouldIgnore(a); err != nil { + if ignore, err := shouldSkipSCCEvaluation(a); err != nil { return err } else if ignore { return nil @@ -131,7 +132,7 @@ func (c *constraint) Admit(ctx context.Context, a admission.Attributes, _ admiss } func (c *constraint) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error { - if ignore, err := shouldIgnore(a); err != nil { + if ignore, err := shouldSkipSCCEvaluation(a); err != nil { return err } else if ignore { return nil @@ -213,7 +214,7 @@ func (c *constraint) computeSecurityContext( return nil, nil, nil, admission.NewForbidden(a, err) } - providers, errs := sccmatching.CreateProvidersFromConstraints(ctx, a.GetNamespace(), constraints, c.namespaceLister) + providers, errs := sccmatching.CreateProvidersFromConstraints(ctx, a.GetNamespace(), constraints, c.namespaceLister, c.nodeLister) logProviders(pod, providers, errs) if len(errs) > 0 { return nil, nil, nil, kutilerrors.NewAggregate(errs) @@ -420,7 +421,14 @@ var ignoredAnnotations = sets.NewString( "k8s.ovn.org/pod-networks", ) -func shouldIgnore(a admission.Attributes) (bool, error) { +// shouldSkipSCCEvaluation skips evaluation for: +// - non-Pod resources, +// - specific subresources that don't affect Pod security context (e.g., exec, attach, log), +// - Windows Pods (SCC is not applied), +// - update operations that only change fields like SchedulingGates or non-critical metadata. +// If the request is malformed (e.g., object can't be cast to a Pod), it fails closed to avoid +// bypassing security enforcement unintentionally. +func shouldSkipSCCEvaluation(a admission.Attributes) (bool, error) { if a.GetResource().GroupResource() != coreapi.Resource("pods") { return true, nil } @@ -448,14 +456,17 @@ func shouldIgnore(a admission.Attributes) (bool, error) { return false, admission.NewForbidden(a, fmt.Errorf("object was marked as kind pod but was unable to be converted: %v", a.GetOldObject())) } - // never ignore any spec changes - if !kapihelper.Semantic.DeepEqual(pod.Spec, oldPod.Spec) { + // Create deep copies to avoid mutating the original objects + podWithoutSchedulingGates := pod.DeepCopy() + // Skip SchedulingGates when comparing specs + podWithoutSchedulingGates.Spec.SchedulingGates = oldPod.Spec.SchedulingGates + if !kapihelper.Semantic.DeepEqual(podWithoutSchedulingGates.Spec, oldPod.Spec) { return false, nil } // see if we are only doing meta changes that should be ignored during admission // for example, the OVN controller adds informative networking annotations that shouldn't cause the pod to go through admission again - if shouldIgnoreMetaChanges(pod, oldPod) { + if shouldIgnoreMetaChanges(podWithoutSchedulingGates, oldPod) { return true, nil } } @@ -463,9 +474,10 @@ func shouldIgnore(a admission.Attributes) (bool, error) { return false, nil } -func shouldIgnoreMetaChanges(newPod, oldPod *coreapi.Pod) bool { +// newPodCopy is expected to be a copy of the original Pod that we can safely mutate +func shouldIgnoreMetaChanges(newPodCopy, oldPod *coreapi.Pod) bool { // check if we're adding or changing only annotations from the ignore list - for key, newVal := range newPod.ObjectMeta.Annotations { + for key, newVal := range newPodCopy.ObjectMeta.Annotations { if oldVal, ok := oldPod.ObjectMeta.Annotations[key]; ok && newVal == oldVal { continue } @@ -477,7 +489,7 @@ func shouldIgnoreMetaChanges(newPod, oldPod *coreapi.Pod) bool { // check if we're removing only annotations from the ignore list for key := range oldPod.ObjectMeta.Annotations { - if _, ok := newPod.ObjectMeta.Annotations[key]; ok { + if _, ok := newPodCopy.ObjectMeta.Annotations[key]; ok { continue } @@ -486,12 +498,11 @@ func shouldIgnoreMetaChanges(newPod, oldPod *coreapi.Pod) bool { } } - newPodCopy := newPod.DeepCopyObject() - newPodCopyMeta, err := meta.Accessor(newPodCopy) + newPodCopyWithoutSchedulingGatesCopyMeta, err := meta.Accessor(newPodCopy) if err != nil { return false } - newPodCopyMeta.SetAnnotations(oldPod.ObjectMeta.Annotations) + newPodCopyWithoutSchedulingGatesCopyMeta.SetAnnotations(oldPod.ObjectMeta.Annotations) // see if we are only updating the ownerRef. Garbage collection does this // and we should allow it in general, since you had the power to update and the power to delete. @@ -509,7 +520,12 @@ func (c *constraint) SetSecurityInformers(informers securityv1informer.SecurityC func (c *constraint) SetExternalKubeInformerFactory(informers informers.SharedInformerFactory) { c.namespaceLister = informers.Core().V1().Namespaces().Lister() - c.listersSynced = append(c.listersSynced, informers.Core().V1().Namespaces().Informer().HasSynced) + c.nodeLister = informers.Core().V1().Nodes().Lister() + c.listersSynced = append( + c.listersSynced, + informers.Core().V1().Namespaces().Informer().HasSynced, + informers.Core().V1().Nodes().Informer().HasSynced, + ) } func (c *constraint) SetAuthorizer(authorizer authorizer.Authorizer) { @@ -527,6 +543,9 @@ func (c *constraint) ValidateInitialization() error { if c.namespaceLister == nil { return fmt.Errorf("%s requires a namespaceLister", PluginName) } + if c.nodeLister == nil { + return fmt.Errorf("%s requires a nodeLister", PluginName) + } if c.authorizer == nil { return fmt.Errorf("%s requires an authorizer", PluginName) } diff --git a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/matcher.go b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/matcher.go index 09a084483d631..ce2b9aab825db 100644 --- a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/matcher.go +++ b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/matcher.go @@ -20,6 +20,7 @@ import ( "github.com/openshift/api/security" securityv1 "github.com/openshift/api/security/v1" + "github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sysctl" sccsort "github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/util/sort" securityv1listers "github.com/openshift/client-go/security/listers/security/v1" "github.com/openshift/library-go/pkg/security/uid" @@ -164,7 +165,7 @@ func constraintSupportsGroup(group string, constraintGroups []string) bool { // CreateProvidersFromConstraints creates providers from the constraints supplied, including // looking up pre-allocated values if necessary using the pod's namespace. -func CreateProvidersFromConstraints(ctx context.Context, namespaceName string, sccs []*securityv1.SecurityContextConstraints, namespaceLister corev1listers.NamespaceLister) ([]SecurityContextConstraintsProvider, []error) { +func CreateProvidersFromConstraints(ctx context.Context, namespaceName string, sccs []*securityv1.SecurityContextConstraints, namespaceLister corev1listers.NamespaceLister, nodeLister corev1listers.NodeLister) ([]SecurityContextConstraintsProvider, []error) { var ( // namespace is declared here for reuse but we will not fetch it unless required by the matched constraints namespace *corev1.Namespace @@ -207,7 +208,7 @@ func CreateProvidersFromConstraints(ctx context.Context, namespaceName string, s provider SecurityContextConstraintsProvider err error ) - provider, err = CreateProviderFromConstraint(namespace, constraint) + provider, err = CreateProviderFromConstraint(namespace, constraint, nodeLister) if err != nil { errs = append(errs, err) continue @@ -218,7 +219,7 @@ func CreateProvidersFromConstraints(ctx context.Context, namespaceName string, s } // CreateProviderFromConstraint creates a SecurityContextConstraintProvider from a SecurityContextConstraint -func CreateProviderFromConstraint(namespace *corev1.Namespace, constraint *securityv1.SecurityContextConstraints) (SecurityContextConstraintsProvider, error) { +func CreateProviderFromConstraint(namespace *corev1.Namespace, constraint *securityv1.SecurityContextConstraints, nodeLister corev1listers.NodeLister) (SecurityContextConstraintsProvider, error) { var err error // Make a copy of the constraint so we don't mutate the store's cache @@ -258,7 +259,7 @@ func CreateProviderFromConstraint(namespace *corev1.Namespace, constraint *secur } // Create the provider - provider, err := NewSimpleProvider(constraint) + provider, err := NewSimpleProvider(constraint, sysctl.SafeSysctlAllowlist(nodeLister)) if err != nil { return nil, fmt.Errorf("error creating provider for SCC %s in namespace %s: %v", constraint.Name, namespace.GetName(), err) } diff --git a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go index 6acd3913d2c2d..99f51b190c51e 100644 --- a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go +++ b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go @@ -44,7 +44,7 @@ type simpleProvider struct { var _ SecurityContextConstraintsProvider = &simpleProvider{} // NewSimpleProvider creates a new SecurityContextConstraintsProvider instance. -func NewSimpleProvider(scc *securityv1.SecurityContextConstraints) (SecurityContextConstraintsProvider, error) { +func NewSimpleProvider(scc *securityv1.SecurityContextConstraints, availableSysCtls []string) (SecurityContextConstraintsProvider, error) { if scc == nil { return nil, fmt.Errorf("NewSimpleProvider requires a SecurityContextConstraints") } @@ -79,7 +79,7 @@ func NewSimpleProvider(scc *securityv1.SecurityContextConstraints) (SecurityCont return nil, err } - sysctlsStrat, err := createSysctlsStrategy(sysctl.SafeSysctlAllowlist(), scc.AllowedUnsafeSysctls, scc.ForbiddenSysctls) + sysctlsStrat, err := createSysctlsStrategy(availableSysCtls, scc.AllowedUnsafeSysctls, scc.ForbiddenSysctls) if err != nil { return nil, err } diff --git a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sysctl/mustmatchpatterns.go b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sysctl/mustmatchpatterns.go index bd307b42498f0..3a34e0d9f0ff7 100644 --- a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sysctl/mustmatchpatterns.go +++ b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sysctl/mustmatchpatterns.go @@ -21,8 +21,10 @@ import ( "slices" "strings" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/version" + corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/klog/v2" api "k8s.io/kubernetes/pkg/apis/core" utilkernel "k8s.io/kubernetes/pkg/util/kernel" @@ -70,8 +72,10 @@ var newerSysctls = []sysctl{ // A sysctl is called safe iff // - it is namespaced in the container or the pod // - it is isolated, i.e. has no influence on any other pod on the same node. -func SafeSysctlAllowlist() []string { - return getSafeSysctlAllowlist(utilkernel.GetVersion) +func SafeSysctlAllowlist(nodeLister corev1listers.NodeLister) []string { + return getSafeSysctlAllowlist(func() (*version.Version, error) { + return getMinKernelVersionAcrossAllNodes(nodeLister) + }) } // getSafeSysctlAllowlist returns the list of safe sysctls that can be used. @@ -79,12 +83,16 @@ func SafeSysctlAllowlist() []string { // 1. Always return the legacy list (known safe sysctls from previous releases). // 2. Conditionally add newer sysctls only if the detected kernel version // is at least as new as required. +// +// The kernel version here refers to the minimum kernel version across all the nodes, +// which ensures that any sysctls allowed are supported on every node in the cluster, +// irrespective of where a pod is scheduled. func getSafeSysctlAllowlist(getVersion func() (*version.Version, error)) []string { safeSysctlAllowlist := slices.Clone(legacySafeSysctls) kernelVersion, err := getVersion() if err != nil { - klog.Error(err, "failed to get kernel version, falling back to legacy safe sysctl list") + klog.Error(err, "failed to determine the minimum kernel version across all the nodes, falling back to legacy safe sysctl list") return safeSysctlAllowlist } @@ -183,3 +191,31 @@ func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList { return allErrs } + +// getMinKernelVersionAcrossAllNodes returns the minimum kernel version across all the +// nodes in the cluster +func getMinKernelVersionAcrossAllNodes(nodeLister corev1listers.NodeLister) (*version.Version, error) { + nodes, err := nodeLister.List(labels.Everything()) + if err != nil { + return nil, fmt.Errorf("failed to list the nodes: %v", err) + } + + var minVersion *version.Version + for _, node := range nodes { + nodeVersion, err := version.ParseGeneric(node.Status.NodeInfo.KernelVersion) + if err != nil { + klog.Warningf("failed to parse kernel version for node %s: %v", node.Name, err) + continue + } + + if minVersion == nil || nodeVersion.LessThan(minVersion) { + minVersion = nodeVersion + } + } + + if minVersion == nil { + return nil, fmt.Errorf("no worker nodes found") + } + + return minVersion, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1130105273a0d..4478f53dc75cf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -539,7 +539,7 @@ github.com/openshift/api/security github.com/openshift/api/security/v1 github.com/openshift/api/template/v1 github.com/openshift/api/user/v1 -# github.com/openshift/apiserver-library-go v0.0.0-20251015164739-79d04067059d +# github.com/openshift/apiserver-library-go v0.0.0-20251015164739-79d04067059d => github.com/jubittajohn/apiserver-library-go v0.0.0-20251201210649-a39660da613e ## explicit; go 1.24.0 github.com/openshift/apiserver-library-go/pkg/admission/imagepolicy github.com/openshift/apiserver-library-go/pkg/admission/imagepolicy/apis/imagepolicy/v1 @@ -1583,3 +1583,4 @@ sigs.k8s.io/structured-merge-diff/v6/value sigs.k8s.io/yaml sigs.k8s.io/yaml/kyaml # github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 +# github.com/openshift/apiserver-library-go => github.com/jubittajohn/apiserver-library-go v0.0.0-20251201210649-a39660da613e