diff --git a/go.mod b/go.mod index ef71d0060328e..f0ce19bda7812 100644 --- a/go.mod +++ b/go.mod @@ -234,6 +234,7 @@ require ( replace ( github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20241205171354-8006f302fd12 + github.com/openshift/apiserver-library-go => github.com/ibihim/apiserver-library-go v0.0.0-20250502090902-95ba0ab61d20 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 3c56b908dc7fd..d91caacf5716b 100644 --- a/go.sum +++ b/go.sum @@ -344,6 +344,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/ibihim/apiserver-library-go v0.0.0-20250320171516-825cdf748cf4 h1:0EgzZ1ut1kq2a6mKz0/POaF31cu1a2//JjR3eeKQ7oM= +github.com/ibihim/apiserver-library-go v0.0.0-20250320171516-825cdf748cf4/go.mod h1:kkSwH4osgejnRIyHfsfkv0V0xfmgH4Yk/VDObaJukHU= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -429,8 +431,6 @@ github.com/openshift-eng/openshift-tests-extension v0.0.0-20250220212757-b9c4d98 github.com/openshift-eng/openshift-tests-extension v0.0.0-20250220212757-b9c4d98a0c45/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M= github.com/openshift/api v0.0.0-20250129162653-107848b719c5 h1:PzJJmofv/P9R1JUxO8X6tAMxKACVS6Quxo/xBzMkSmI= github.com/openshift/api v0.0.0-20250129162653-107848b719c5/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw= -github.com/openshift/apiserver-library-go v0.0.0-20250127121756-dc9a973f14ce h1:w0Up6YV1APcn20v/1h5IfuToz96o2pVqZyjzbw0yotU= -github.com/openshift/apiserver-library-go v0.0.0-20250127121756-dc9a973f14ce/go.mod h1:kkSwH4osgejnRIyHfsfkv0V0xfmgH4Yk/VDObaJukHU= github.com/openshift/build-machinery-go v0.0.0-20250102153059-e85a1a7ecb5c/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= github.com/openshift/client-go v0.0.0-20250125113824-8e1f0b8fa9a7 h1:4iliLcvr1P9EUMZgIaSNEKNQQzBn+L6PSequlFOuB6Q= github.com/openshift/client-go v0.0.0-20250125113824-8e1f0b8fa9a7/go.mod h1:2tcufBE4Cu6RNgDCxcUJepa530kGo5GFVfR9BSnndhI= 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 701d978f7ca45..63d669ba92edb 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 @@ -17,8 +17,6 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission/initializer" - "k8s.io/apiserver/pkg/authentication/serviceaccount" - "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/client-go/informers" corev1listers "k8s.io/client-go/listers/core/v1" @@ -103,7 +101,7 @@ func (c *constraint) Admit(ctx context.Context, a admission.Attributes, _ admiss specMutationAllowed := a.GetOperation() == admission.Create ephemeralContainersMutationAllowed := specMutationAllowed || (a.GetOperation() == admission.Update && a.GetSubresource() == "ephemeralcontainers") - allowedPod, sccName, validationErrs, err := c.computeSecurityContext(ctx, a, pod, specMutationAllowed, ephemeralContainersMutationAllowed, pod.ObjectMeta.Annotations[securityv1.RequiredSCCAnnotation], "") + allowedPod, podAnnotations, validationErrs, err := c.computeSecurityContext(ctx, a, pod, specMutationAllowed, ephemeralContainersMutationAllowed, pod.ObjectMeta.Annotations[securityv1.RequiredSCCAnnotation], "") if err != nil { return admission.NewForbidden(a, err) } @@ -111,11 +109,19 @@ func (c *constraint) Admit(ctx context.Context, a admission.Attributes, _ admiss if allowedPod != nil { *pod = *allowedPod // annotate and accept the pod - klog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, sccName) + klog.V(4).Infof( + "pod %s (generate: %s) validated against provider %s", + pod.Name, pod.GenerateName, podAnnotations[securityv1.ValidatedSCCAnnotation], + ) + if pod.ObjectMeta.Annotations == nil { pod.ObjectMeta.Annotations = map[string]string{} } - pod.ObjectMeta.Annotations[securityv1.ValidatedSCCAnnotation] = sccName + + for key, value := range podAnnotations { + pod.ObjectMeta.Annotations[key] = value + } + return nil } @@ -188,93 +194,33 @@ func requireStandardSCCs(sccs []*securityv1.SecurityContextConstraints, err erro return fmt.Errorf("securitycontextconstraints.security.openshift.io cache is missing %v", strings.Join(missingSCCs.List(), ", ")) } -func (c *constraint) areListersSynced() bool { - for _, syncFunc := range c.listersSynced { - if !syncFunc() { - return false - } - } - return true -} - func (c *constraint) computeSecurityContext( ctx context.Context, a admission.Attributes, pod *coreapi.Pod, specMutationAllowed, ephemeralContainersMutationAllowed bool, requiredSCCName, validatedSCCHint string, -) (*coreapi.Pod, string, field.ErrorList, error) { +) (*coreapi.Pod, map[string]string, field.ErrorList, error) { // get all constraints that are usable by the user klog.V(4).Infof("getting security context constraints for pod %s (generate: %s) in namespace %s with user info %v", pod.Name, pod.GenerateName, a.GetNamespace(), a.GetUserInfo()) - err := wait.PollImmediateWithContext(ctx, 1*time.Second, 10*time.Second, func(context.Context) (bool, error) { - return c.areListersSynced(), nil - }) - if err != nil { - return nil, "", nil, admission.NewForbidden(a, fmt.Errorf("securitycontextconstraints.security.openshift.io cache is not synchronized")) + if err := c.waitForReadyState(ctx); err != nil { + return nil, nil, nil, admission.NewForbidden(a, err) } - // wait a few seconds until the synchronized list returns all the required SCCs created by the kas-o. - // If this doesn't happen, then indicate which ones are missing. This seems odd, but our CI system suggests that this happens occasionally. - // If the SCCs were all deleted, then no pod will pass SCC admission until the SCCs are recreated, but the kas-o (which recreates them) - // bypasses SCC admission, so this does not create a cycle. - var requiredSCCErr error - err = wait.PollImmediateWithContext(ctx, 1*time.Second, 10*time.Second, func(context.Context) (bool, error) { - if requiredSCCErr = requireStandardSCCs(c.sccLister.List(labels.Everything())); requiredSCCErr != nil { - return false, nil - } - return true, nil - }) + constraints, err := c.listOrderedSCCs(requiredSCCName, validatedSCCHint, specMutationAllowed) if err != nil { - if requiredSCCErr != nil { - return nil, "", nil, admission.NewForbidden(a, requiredSCCErr) - } - return nil, "", nil, admission.NewForbidden(a, fmt.Errorf("securitycontextconstraints.security.openshift.io required check failed oddly")) + return nil, nil, nil, admission.NewForbidden(a, err) } - var constraints []*securityv1.SecurityContextConstraints - if len(requiredSCCName) > 0 { - requiredSCC, err := c.sccLister.Get(requiredSCCName) - if err != nil { - return nil, "", nil, admission.NewForbidden(a, fmt.Errorf("failed to retrieve the required SCC %q: %w", requiredSCCName, err)) - } - constraints = []*securityv1.SecurityContextConstraints{requiredSCC} - } else { - constraints, err = c.sccLister.List(labels.Everything()) - if err != nil { - return nil, "", nil, admission.NewForbidden(a, err) - } - } - - if len(constraints) == 0 { - return nil, "", nil, admission.NewForbidden(a, fmt.Errorf("no SecurityContextConstraints found in cluster")) - } - sort.Sort(sccsort.ByPriority(constraints)) - - // If mutation is not allowed and validatedSCCHint is provided, check the validated policy first. - // Keep the order the same for everything else - sort.SliceStable(constraints, func(i, j int) bool { - // disregard the ephemeral containers here, the rest of the pod should still - // not get mutated and so we are primarily interested in the SCC that matched previously - if !specMutationAllowed { - if constraints[i].Name == validatedSCCHint { - return true - } - if constraints[j].Name == validatedSCCHint { - return false - } - } - return i < j - }) - providers, errs := sccmatching.CreateProvidersFromConstraints(ctx, a.GetNamespace(), constraints, c.namespaceLister) logProviders(pod, providers, errs) if len(errs) > 0 { - return nil, "", nil, kutilerrors.NewAggregate(errs) + return nil, nil, nil, kutilerrors.NewAggregate(errs) } if len(providers) == 0 { - return nil, "", nil, admission.NewForbidden(a, fmt.Errorf("no SecurityContextConstraintsProvider available to validate pod request")) + return nil, nil, nil, admission.NewForbidden(a, fmt.Errorf("no SecurityContextConstraintsProvider available to validate pod request")) } // all containers in a single pod must validate under a single provider or we will reject the request @@ -282,21 +228,9 @@ func (c *constraint) computeSecurityContext( allowedPod *coreapi.Pod allowingProvider sccmatching.SecurityContextConstraintsProvider validationErrs field.ErrorList - saUserInfo user.Info ) - userInfo := a.GetUserInfo() - if len(pod.Spec.ServiceAccountName) > 0 { - saUserInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") - } - - allowedForUserOrSA := func(provider sccmatching.SecurityContextConstraintsProvider) bool { - sccName := provider.GetSCCName() - sccUsers := provider.GetSCCUsers() - sccGroups := provider.GetSCCGroups() - return sccmatching.ConstraintAppliesTo(ctx, sccName, sccUsers, sccGroups, userInfo, a.GetNamespace(), c.authorizer) || - (saUserInfo != nil && sccmatching.ConstraintAppliesTo(ctx, sccName, sccUsers, sccGroups, saUserInfo, a.GetNamespace(), c.authorizer)) - } + sccChecker := newSCCAuthorizerChecker(ctx, c.authorizer, a, pod.Spec.ServiceAccountName) appliesToPod := func(provider sccmatching.SecurityContextConstraintsProvider, pod *coreapi.Pod) (podCopy *coreapi.Pod, errs field.ErrorList) { podCopy = pod.DeepCopy() @@ -323,7 +257,7 @@ loop: restrictedV2SCCProvider = providers[i] } - if !allowedForUserOrSA(provider) { + if !sccChecker.allowedForUserOrSA(provider) { denied = append(denied, provider.GetSCCName()) // this will cause every security context constraint attempted, in order, to the failure validationErrs = append(validationErrs, @@ -402,7 +336,7 @@ loop: // find next provider that was not chosen var nextNotChosenProvider sccmatching.SecurityContextConstraintsProvider for _, provider := range providers[i+1:] { - if !allowedForUserOrSA(provider) { + if !sccChecker.allowedForUserOrSA(provider) { continue } if _, errs := appliesToPod(provider, pod); len(errs) == 0 { @@ -450,7 +384,7 @@ loop: } if allowedPod == nil || allowingProvider == nil { - return nil, "", validationErrs, nil + return nil, nil, validationErrs, nil } if !specMutationAllowed { @@ -458,7 +392,12 @@ loop: a.AddAnnotation("securitycontextconstraints.admission.openshift.io/chosen", allowingProvider.GetSCCName()) } - return allowedPod, allowingProvider.GetSCCName(), validationErrs, nil + podAnnotations := map[string]string{ + securityv1.ValidatedSCCAnnotation: allowingProvider.GetSCCName(), + "security.openshift.io/validated-scc-subject-type": sccChecker.allowedForType(provider), + } + + return allowedPod, podAnnotations, validationErrs, nil } var ignoredSubresources = sets.NewString( @@ -589,6 +528,95 @@ func (c *constraint) ValidateInitialization() error { return nil } +func (c *constraint) listOrderedSCCs( + requiredSCCName, validatedSCCHint string, + specMutationAllowed bool, +) ([]*securityv1.SecurityContextConstraints, error) { + var err error + var constraints []*securityv1.SecurityContextConstraints + + if len(requiredSCCName) > 0 { + requiredSCC, err := c.sccLister.Get(requiredSCCName) + if err != nil { + return nil, fmt.Errorf("failed to retrieve the required SCC %q: %w", requiredSCCName, err) + } + constraints = []*securityv1.SecurityContextConstraints{requiredSCC} + } else { + constraints, err = c.sccLister.List(labels.Everything()) + if err != nil { + return nil, err + } + } + + if len(constraints) == 0 { + return nil, fmt.Errorf("no SecurityContextConstraints found in cluster") + } + + sort.Sort(sccsort.ByPriority(constraints)) + + if specMutationAllowed { + return constraints, nil + } + + // If mutation is not allowed and validatedSCCHint is provided, check the validated policy first. + // Keep the order the same for everything else + sort.SliceStable(constraints, func(i, j int) bool { + // disregard the ephemeral containers here, the rest of the pod should still + // not get mutated and so we are primarily interested in the SCC that matched previously + if constraints[i].Name == validatedSCCHint { + return true + } + if constraints[j].Name == validatedSCCHint { + return false + } + return i < j + }) + + return constraints, nil +} + +func (c *constraint) waitForReadyState(ctx context.Context) error { + const ( + interval = 1 * time.Second + timeout = 10 * time.Second + immediate = true + ) + + err := wait.PollUntilContextTimeout(ctx, interval, timeout, immediate, c.areListersSynced) + if err != nil { + return fmt.Errorf("securitycontextconstraints.security.openshift.io cache is not synchronized") + } + + // wait a few seconds until the synchronized list returns all the required SCCs created by the kas-o. + // If this doesn't happen, then indicate which ones are missing. This seems odd, but our CI system suggests that this happens occasionally. + // If the SCCs were all deleted, then no pod will pass SCC admission until the SCCs are recreated, but the kas-o (which recreates them) + // bypasses SCC admission, so this does not create a cycle. + var requiredSCCErr error + err = wait.PollUntilContextTimeout(ctx, interval, timeout, immediate, func(context.Context) (bool, error) { + if requiredSCCErr = requireStandardSCCs(c.sccLister.List(labels.Everything())); requiredSCCErr != nil { + return false, nil + } + return true, nil + }) + if err != nil { + if requiredSCCErr != nil { + return requiredSCCErr + } + return fmt.Errorf("securitycontextconstraints.security.openshift.io required check failed oddly") + } + + return nil +} + +func (c *constraint) areListersSynced(_ context.Context) (bool, error) { + for _, syncFunc := range c.listersSynced { + if !syncFunc() { + return false, nil + } + } + return true, nil +} + // logProviders logs what providers were found for the pod as well as any errors that were encountered // while creating providers. func logProviders(pod *coreapi.Pod, providers []sccmatching.SecurityContextConstraintsProvider, providerCreationErrs []error) { diff --git a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccadmission/scc_authz_check.go b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccadmission/scc_authz_check.go new file mode 100644 index 0000000000000..392906954179a --- /dev/null +++ b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccadmission/scc_authz_check.go @@ -0,0 +1,75 @@ +package sccadmission + +import ( + "context" + + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authentication/serviceaccount" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching" +) + +type sccAuthorizationChecker struct { + ctx context.Context + authz authorizer.Authorizer + userInfo user.Info + namespace string + serviceAccountName string +} + +func newSCCAuthorizerChecker(ctx context.Context, authz authorizer.Authorizer, attr admission.Attributes, serviceAccountName string) *sccAuthorizationChecker { + return &sccAuthorizationChecker{ + ctx: ctx, + authz: authz, + userInfo: attr.GetUserInfo(), + namespace: attr.GetNamespace(), + serviceAccountName: serviceAccountName, + } +} + +func (c *sccAuthorizationChecker) allowedForUser(provider sccmatching.SecurityContextConstraintsProvider) bool { + sccName := provider.GetSCCName() + sccUsers := provider.GetSCCUsers() + sccGroups := provider.GetSCCGroups() + + return sccmatching.ConstraintAppliesTo( + c.ctx, + sccName, sccUsers, sccGroups, + c.userInfo, c.namespace, c.authz, + ) +} + +func (c *sccAuthorizationChecker) allowedForSA(provider sccmatching.SecurityContextConstraintsProvider) bool { + sccName := provider.GetSCCName() + sccUsers := provider.GetSCCUsers() + sccGroups := provider.GetSCCGroups() + + if len(c.serviceAccountName) == 0 { + return false + } + + saUserInfo := serviceaccount.UserInfo(c.namespace, c.serviceAccountName, "") + return sccmatching.ConstraintAppliesTo( + c.ctx, + sccName, sccUsers, sccGroups, + saUserInfo, c.namespace, c.authz, + ) +} + +func (c *sccAuthorizationChecker) allowedForType(provider sccmatching.SecurityContextConstraintsProvider) string { + if c.allowedForSA(provider) { + return "serviceaccount" + } + + if c.allowedForUser(provider) { + return "user" + } + + return "" +} + +func (c *sccAuthorizationChecker) allowedForUserOrSA(provider sccmatching.SecurityContextConstraintsProvider) bool { + return c.allowedForUser(provider) || c.allowedForSA(provider) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c0da386bec691..7844dec13cef0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -530,7 +530,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-20250127121756-dc9a973f14ce +# github.com/openshift/apiserver-library-go v0.0.0-20250127121756-dc9a973f14ce => github.com/ibihim/apiserver-library-go v0.0.0-20250320171516-825cdf748cf4 ## explicit; go 1.23.0 github.com/openshift/apiserver-library-go/pkg/admission/imagepolicy github.com/openshift/apiserver-library-go/pkg/admission/imagepolicy/apis/imagepolicy/v1 @@ -1524,3 +1524,4 @@ sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 sigs.k8s.io/yaml/goyaml.v3 # github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20241205171354-8006f302fd12 +# github.com/openshift/apiserver-library-go => github.com/ibihim/apiserver-library-go v0.0.0-20250320171516-825cdf748cf4