diff --git a/pkg/controlplane/apiserver/config.go b/pkg/controlplane/apiserver/config.go index b81cb296876fc..b9204103c5c39 100644 --- a/pkg/controlplane/apiserver/config.go +++ b/pkg/controlplane/apiserver/config.go @@ -40,6 +40,7 @@ import ( "k8s.io/apiserver/pkg/server/egressselector" "k8s.io/apiserver/pkg/server/filters" serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/apiserver/pkg/util/compatibility" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/openapi" utilpeerproxy "k8s.io/apiserver/pkg/util/peerproxy" @@ -47,9 +48,9 @@ import ( clientgoinformers "k8s.io/client-go/informers" clientgoclientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/util/keyutil" + basecompatibility "k8s.io/component-base/compatibility" aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" openapicommon "k8s.io/kube-openapi/pkg/common" - "k8s.io/kubernetes/pkg/api/legacyscheme" controlplaneadmission "k8s.io/kubernetes/pkg/controlplane/apiserver/admission" "k8s.io/kubernetes/pkg/controlplane/apiserver/options" @@ -379,6 +380,7 @@ func CreateConfig( clientgoExternalClient, dynamicExternalClient, utilfeature.DefaultFeatureGate, + compatibility.DefaultComponentGlobalsRegistry.EffectiveVersionFor(basecompatibility.DefaultKubeComponent), append(genericInitializers, additionalInitializers...)..., ) if err != nil { diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 11ce0e789645f..379e35985870e 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -719,12 +719,6 @@ const ( // Denies pod admission if static pods reference other API objects. PreventStaticPodAPIReferences featuregate.Feature = "PreventStaticPodAPIReferences" - // owner: @tssurya - // kep: https://kep.k8s.io/4559 - // - // Enables probe host enforcement for Pod Security Standards. - ProbeHostPodSecurityStandards featuregate.Feature = "ProbeHostPodSecurityStandards" - // owner: @jessfraz // // Enables control over ProcMountType for containers. @@ -1552,11 +1546,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, }, - // Policy is GA in first release, this gate only exists to disable the enforcement when emulating older minors - ProbeHostPodSecurityStandards: { - {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, - }, - ProcMountType: { {Version: version.MustParse("1.12"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Beta}, diff --git a/pkg/kubeapiserver/options/admission.go b/pkg/kubeapiserver/options/admission.go index d70df9742a6d0..de0c0b6d33ce7 100644 --- a/pkg/kubeapiserver/options/admission.go +++ b/pkg/kubeapiserver/options/admission.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/spf13/pflag" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -29,6 +30,7 @@ import ( "k8s.io/apiserver/pkg/server" genericoptions "k8s.io/apiserver/pkg/server/options" "k8s.io/client-go/informers" + "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" ) @@ -116,6 +118,7 @@ func (a *AdmissionOptions) ApplyTo( kubeClient kubernetes.Interface, dynamicClient dynamic.Interface, features featuregate.FeatureGate, + effectiveVersion compatibility.EffectiveVersion, pluginInitializers ...admission.PluginInitializer, ) error { if a == nil { @@ -127,7 +130,7 @@ func (a *AdmissionOptions) ApplyTo( a.GenericAdmission.EnablePlugins, a.GenericAdmission.DisablePlugins = computePluginNames(a.PluginNames, a.GenericAdmission.RecommendedPluginOrder) } - return a.GenericAdmission.ApplyTo(c, informers, kubeClient, dynamicClient, features, pluginInitializers...) + return a.GenericAdmission.ApplyTo(c, informers, kubeClient, dynamicClient, features, effectiveVersion, pluginInitializers...) } // explicitly disable all plugins that are not in the enabled list diff --git a/plugin/pkg/admission/defaulttolerationseconds/admission_test.go b/plugin/pkg/admission/defaulttolerationseconds/admission_test.go index ca170305f55bf..1e1443b460346 100644 --- a/plugin/pkg/admission/defaulttolerationseconds/admission_test.go +++ b/plugin/pkg/admission/defaulttolerationseconds/admission_test.go @@ -300,7 +300,7 @@ func TestHandles(t *testing.T) { // newHandlerForTest returns a handler configured for testing. func newHandlerForTest() (*Plugin, error) { handler := NewDefaultTolerationSeconds() - pluginInitializer := initializer.New(nil, nil, nil, nil, nil, nil, nil) + pluginInitializer := initializer.New(nil, nil, nil, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) return handler, admission.ValidateInitialization(handler) } diff --git a/plugin/pkg/admission/gc/gc_admission_test.go b/plugin/pkg/admission/gc/gc_admission_test.go index 1127e5385259b..bc02435e5696e 100644 --- a/plugin/pkg/admission/gc/gc_admission_test.go +++ b/plugin/pkg/admission/gc/gc_admission_test.go @@ -138,7 +138,7 @@ func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) { return nil, fmt.Errorf("unexpected error while constructing resource list from fake discovery client: %v", err) } restMapper := restmapper.NewDiscoveryRESTMapper(restMapperRes) - genericPluginInitializer := initializer.New(nil, nil, nil, fakeAuthorizer{}, nil, nil, restMapper) + genericPluginInitializer := initializer.New(nil, nil, nil, fakeAuthorizer{}, nil, nil, nil, restMapper) pluginInitializer := controlplaneadmission.NewPluginInitializer(nil, nil) initializersChain := apiserveradmission.PluginInitializers{} initializersChain = append(initializersChain, genericPluginInitializer) diff --git a/plugin/pkg/admission/limitranger/admission_test.go b/plugin/pkg/admission/limitranger/admission_test.go index 17fc3211af358..67cf34d12b8eb 100644 --- a/plugin/pkg/admission/limitranger/admission_test.go +++ b/plugin/pkg/admission/limitranger/admission_test.go @@ -891,7 +891,7 @@ func newHandlerForTest(c clientset.Interface) (*LimitRanger, informers.SharedInf if err != nil { return nil, f, err } - pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil) + pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) err = admission.ValidateInitialization(handler) return handler, f, err diff --git a/plugin/pkg/admission/namespace/autoprovision/admission_test.go b/plugin/pkg/admission/namespace/autoprovision/admission_test.go index 935a1357677a6..2b34d1be38dab 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission_test.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission_test.go @@ -41,7 +41,7 @@ import ( func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) { f := informers.NewSharedInformerFactory(c, 5*time.Minute) handler := NewProvision() - pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil) + pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) err := admission.ValidateInitialization(handler) return handler, f, err diff --git a/plugin/pkg/admission/namespace/exists/admission_test.go b/plugin/pkg/admission/namespace/exists/admission_test.go index 2937cb3948e2c..7d90a431b22e8 100644 --- a/plugin/pkg/admission/namespace/exists/admission_test.go +++ b/plugin/pkg/admission/namespace/exists/admission_test.go @@ -39,7 +39,7 @@ import ( func newHandlerForTest(c kubernetes.Interface) (admission.ValidationInterface, informers.SharedInformerFactory, error) { f := informers.NewSharedInformerFactory(c, 5*time.Minute) handler := NewExists() - pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil) + pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) err := admission.ValidateInitialization(handler) return handler, f, err diff --git a/plugin/pkg/admission/podnodeselector/admission_test.go b/plugin/pkg/admission/podnodeselector/admission_test.go index b1e191622ef50..e2ead7c1c4101 100644 --- a/plugin/pkg/admission/podnodeselector/admission_test.go +++ b/plugin/pkg/admission/podnodeselector/admission_test.go @@ -198,7 +198,7 @@ func TestHandles(t *testing.T) { func newHandlerForTest(c kubernetes.Interface) (*Plugin, informers.SharedInformerFactory, error) { f := informers.NewSharedInformerFactory(c, 5*time.Minute) handler := NewPodNodeSelector(nil) - pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil) + pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) err := admission.ValidateInitialization(handler) return handler, f, err diff --git a/plugin/pkg/admission/podtolerationrestriction/admission_test.go b/plugin/pkg/admission/podtolerationrestriction/admission_test.go index 9a23c03546293..f4bf95454c32c 100644 --- a/plugin/pkg/admission/podtolerationrestriction/admission_test.go +++ b/plugin/pkg/admission/podtolerationrestriction/admission_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -355,7 +356,7 @@ func newHandlerForTest(c kubernetes.Interface) (*Plugin, informers.SharedInforme return nil, nil, err } handler := NewPodTolerationsPlugin(pluginConfig) - pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil) + pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) err = admission.ValidateInitialization(handler) return handler, f, err diff --git a/plugin/pkg/admission/podtopologylabels/admission_test.go b/plugin/pkg/admission/podtopologylabels/admission_test.go index 581363c24bb70..f4fc9f76c48e8 100644 --- a/plugin/pkg/admission/podtopologylabels/admission_test.go +++ b/plugin/pkg/admission/podtopologylabels/admission_test.go @@ -226,7 +226,7 @@ func TestPodTopology(t *testing.T) { func newHandlerForTest(c kubernetes.Interface) (*Plugin, informers.SharedInformerFactory, error) { factory := informers.NewSharedInformerFactory(c, 5*time.Minute) handler := NewPodTopologyPlugin(defaultConfig) // todo: write additional test cases with non-default config. - pluginInitializer := genericadmissioninitializer.New(c, nil, factory, nil, feature.DefaultFeatureGate, nil, nil) + pluginInitializer := genericadmissioninitializer.New(c, nil, factory, nil, feature.DefaultFeatureGate, nil, nil, nil) pluginInitializer.Initialize(handler) return handler, factory, admission.ValidateInitialization(handler) } diff --git a/plugin/pkg/admission/resourcequota/admission_test.go b/plugin/pkg/admission/resourcequota/admission_test.go index 74223fae59de3..2a9c9461f9783 100644 --- a/plugin/pkg/admission/resourcequota/admission_test.go +++ b/plugin/pkg/admission/resourcequota/admission_test.go @@ -118,7 +118,7 @@ func createHandlerWithConfig(kubeClient kubernetes.Interface, informerFactory in } initializers := admission.PluginInitializers{ - genericadmissioninitializer.New(kubeClient, nil, informerFactory, nil, nil, stopCh, nil), + genericadmissioninitializer.New(kubeClient, nil, informerFactory, nil, nil, nil, stopCh, nil), controlplaneadmission.NewPluginInitializer(quotaConfiguration, nil), } initializers.Initialize(handler) diff --git a/plugin/pkg/admission/security/podsecurity/admission.go b/plugin/pkg/admission/security/podsecurity/admission.go index 64940994e62b2..d86af3e9e1eb2 100644 --- a/plugin/pkg/admission/security/podsecurity/admission.go +++ b/plugin/pkg/admission/security/podsecurity/admission.go @@ -43,6 +43,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" "k8s.io/component-base/metrics/legacyregistry" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -72,6 +73,9 @@ type Plugin struct { inspectedFeatureGates bool + inspectedEffectiveVersion bool + emulationVersion *podsecurityadmissionapi.Version + client kubernetes.Interface namespaceLister corev1listers.NamespaceLister podLister corev1listers.PodLister @@ -104,16 +108,10 @@ func newPlugin(reader io.Reader) (*Plugin, error) { return nil, err } - evaluator, err := policy.NewEvaluator(policy.DefaultChecks()) - if err != nil { - return nil, fmt.Errorf("could not create PodSecurityRegistry: %w", err) - } - return &Plugin{ Handler: admission.NewHandler(admission.Create, admission.Update), delegate: &podsecurityadmission.Admission{ Configuration: config, - Evaluator: evaluator, Metrics: getDefaultRecorder(), PodSpecExtractor: podsecurityadmission.DefaultPodSpecExtractor{}, }, @@ -146,24 +144,45 @@ func (p *Plugin) updateDelegate() { if p.client == nil { return } - p.delegate.PodLister = podsecurityadmission.PodListerFromInformer(p.podLister) - p.delegate.NamespaceGetter = podsecurityadmission.NamespaceGetterFromListerAndClient(p.namespaceLister, p.client) + if !p.inspectedEffectiveVersion { + return + } + if p.delegate.PodLister == nil { + p.delegate.PodLister = podsecurityadmission.PodListerFromInformer(p.podLister) + } + if p.delegate.NamespaceGetter == nil { + p.delegate.NamespaceGetter = podsecurityadmission.NamespaceGetterFromListerAndClient(p.namespaceLister, p.client) + } + if p.delegate.Evaluator == nil { + evaluator, err := policy.NewEvaluator(policy.DefaultChecks(), p.emulationVersion) + if err != nil { + panic(fmt.Errorf("could not create PodSecurityRegistry: %w", err)) + } + p.delegate.Evaluator = evaluator + } } -func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) { - c.inspectedFeatureGates = true - policy.RelaxPolicyForUserNamespacePods(featureGates.Enabled(features.UserNamespacesPodSecurityStandards)) - - if !featureGates.Enabled(features.ProbeHostPodSecurityStandards) { - policy.SkipProbeHostEnforcement() +func (p *Plugin) InspectEffectiveVersion(version compatibility.EffectiveVersion) { + p.inspectedEffectiveVersion = true + if !version.EmulationVersion().EqualTo(version.BinaryVersion()) { + emulationVersion := podsecurityadmissionapi.MajorMinorVersion(int(version.EmulationVersion().Major()), int(version.EmulationVersion().Minor())) + p.emulationVersion = &emulationVersion } } +func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) { + p.inspectedFeatureGates = true + policy.RelaxPolicyForUserNamespacePods(featureGates.Enabled(features.UserNamespacesPodSecurityStandards)) +} + // ValidateInitialization ensures all required options are set func (p *Plugin) ValidateInitialization() error { if !p.inspectedFeatureGates { return fmt.Errorf("%s did not see feature gates", PluginName) } + if !p.inspectedEffectiveVersion { + return fmt.Errorf("%s did not see effective version", PluginName) + } if err := p.delegate.CompleteConfiguration(); err != nil { return fmt.Errorf("%s configuration error: %w", PluginName, err) } diff --git a/plugin/pkg/admission/security/podsecurity/admission_test.go b/plugin/pkg/admission/security/podsecurity/admission_test.go index ed4c4b92918b0..e4a6c253cf0ed 100644 --- a/plugin/pkg/admission/security/podsecurity/admission_test.go +++ b/plugin/pkg/admission/security/podsecurity/admission_test.go @@ -23,6 +23,8 @@ import ( "strings" "testing" + "sigs.k8s.io/yaml" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -30,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/util/compatibility" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/warning" "k8s.io/client-go/informers" @@ -40,7 +43,6 @@ import ( v1 "k8s.io/kubernetes/pkg/apis/core/v1" podsecurityadmission "k8s.io/pod-security-admission/admission" "k8s.io/utils/ptr" - "sigs.k8s.io/yaml" ) func TestConvert(t *testing.T) { @@ -81,6 +83,7 @@ func BenchmarkVerifyPod(b *testing.B) { b.Fatal(err) } + p.InspectEffectiveVersion(compatibility.DefaultBuildEffectiveVersion()) p.InspectFeatureGates(utilfeature.DefaultFeatureGate) enforceImplicitPrivilegedNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "enforce-implicit", Labels: map[string]string{}}} @@ -189,6 +192,7 @@ func BenchmarkVerifyNamespace(b *testing.B) { b.Fatal(err) } + p.InspectEffectiveVersion(compatibility.DefaultBuildEffectiveVersion()) p.InspectFeatureGates(utilfeature.DefaultFeatureGate) namespace := "enforce" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go index 21ee8c801219b..b7880296896f2 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go @@ -23,6 +23,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" + "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" ) @@ -32,6 +33,7 @@ type pluginInitializer struct { externalInformers informers.SharedInformerFactory authorizer authorizer.Authorizer featureGates featuregate.FeatureGate + effectiveVersion compatibility.EffectiveVersion stopCh <-chan struct{} restMapper meta.RESTMapper } @@ -45,6 +47,7 @@ func New( extInformers informers.SharedInformerFactory, authz authorizer.Authorizer, featureGates featuregate.FeatureGate, + effectiveVersion compatibility.EffectiveVersion, stopCh <-chan struct{}, restMapper meta.RESTMapper, ) pluginInitializer { @@ -54,6 +57,7 @@ func New( externalInformers: extInformers, authorizer: authz, featureGates: featureGates, + effectiveVersion: effectiveVersion, stopCh: stopCh, restMapper: restMapper, } @@ -68,6 +72,9 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) { } // Second tell the plugin about enabled features, so it can decide whether to start informers or not + if wants, ok := plugin.(WantsEffectiveVersion); ok { + wants.InspectEffectiveVersion(i.effectiveVersion) + } if wants, ok := plugin.(WantsFeatures); ok { wants.InspectFeatureGates(i.featureGates) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go index d80ce6dd431f5..d503001d650b6 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go @@ -34,7 +34,7 @@ import ( // TestWantsAuthorizer ensures that the authorizer is injected // when the WantsAuthorizer interface is implemented by a plugin. func TestWantsAuthorizer(t *testing.T) { - target := initializer.New(nil, nil, nil, &TestAuthorizer{}, nil, nil, nil) + target := initializer.New(nil, nil, nil, &TestAuthorizer{}, nil, nil, nil, nil) wantAuthorizerAdmission := &WantAuthorizerAdmission{} target.Initialize(wantAuthorizerAdmission) if wantAuthorizerAdmission.auth == nil { @@ -46,7 +46,7 @@ func TestWantsAuthorizer(t *testing.T) { // when the WantsExternalKubeClientSet interface is implemented by a plugin. func TestWantsExternalKubeClientSet(t *testing.T) { cs := &fake.Clientset{} - target := initializer.New(cs, nil, nil, &TestAuthorizer{}, nil, nil, nil) + target := initializer.New(cs, nil, nil, &TestAuthorizer{}, nil, nil, nil, nil) wantExternalKubeClientSet := &WantExternalKubeClientSet{} target.Initialize(wantExternalKubeClientSet) if wantExternalKubeClientSet.cs != cs { @@ -59,7 +59,7 @@ func TestWantsExternalKubeClientSet(t *testing.T) { func TestWantsExternalKubeInformerFactory(t *testing.T) { cs := &fake.Clientset{} sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second) - target := initializer.New(cs, nil, sf, &TestAuthorizer{}, nil, nil, nil) + target := initializer.New(cs, nil, sf, &TestAuthorizer{}, nil, nil, nil, nil) wantExternalKubeInformerFactory := &WantExternalKubeInformerFactory{} target.Initialize(wantExternalKubeInformerFactory) if wantExternalKubeInformerFactory.sf != sf { @@ -71,7 +71,7 @@ func TestWantsExternalKubeInformerFactory(t *testing.T) { // when the WantsShutdownSignal interface is implemented by a plugin. func TestWantsShutdownNotification(t *testing.T) { stopCh := make(chan struct{}) - target := initializer.New(nil, nil, nil, &TestAuthorizer{}, nil, stopCh, nil) + target := initializer.New(nil, nil, nil, &TestAuthorizer{}, nil, nil, stopCh, nil) wantDrainedNotification := &WantDrainedNotification{} target.Initialize(wantDrainedNotification) if wantDrainedNotification.stopCh == nil { @@ -153,7 +153,7 @@ func (t *TestAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) } func TestRESTMapperAdmissionPlugin(t *testing.T) { - initializer := initializer.New(nil, nil, nil, &TestAuthorizer{}, nil, nil, &doNothingRESTMapper{}) + initializer := initializer.New(nil, nil, nil, &TestAuthorizer{}, nil, nil, nil, &doNothingRESTMapper{}) wantsRESTMapperAdmission := &WantsRESTMapperAdmissionPlugin{} initializer.Initialize(wantsRESTMapperAdmission) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go b/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go index 21202bd7920d4..a66d62dff06ba 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go @@ -26,6 +26,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" + "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" ) @@ -73,6 +74,12 @@ type WantsFeatures interface { admission.InitializationValidator } +// WantsEffectiveVersion defines a function which passes the effective version for inspection by an admission plugin. +type WantsEffectiveVersion interface { + InspectEffectiveVersion(compatibility.EffectiveVersion) + admission.InitializationValidator +} + type WantsDynamicClient interface { SetDynamicClient(dynamic.Interface) admission.InitializationValidator diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go index 22c693d2dd133..ea6a10218d64d 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,7 +54,7 @@ func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) ( if err != nil { return nil, f, err } - pluginInitializer := kubeadmission.New(c, nil, f, nil, nil, nil, nil) + pluginInitializer := kubeadmission.New(c, nil, f, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) err = admission.ValidateInitialization(handler) return handler, f, err diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go index d9a737a60ae70..4ad4c6343ac70 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go @@ -45,6 +45,7 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/util/compatibility" ) // Logger allows t.Testing and b.Testing to be passed to PolicyTestContext @@ -203,6 +204,7 @@ func NewPolicyTestContext[P, B runtime.Object, E Evaluator]( plugin.SetEnabled(true) featureGate := featuregate.NewFeatureGate() + effectiveVersion := compatibility.DefaultBuildEffectiveVersion() testContext, testCancel := context.WithCancel(context.Background()) genericInitializer := initializer.New( nativeClient, @@ -210,6 +212,7 @@ func NewPolicyTestContext[P, B runtime.Object, E Evaluator]( fakeInformerFactory, fakeAuthorizer{}, featureGate, + effectiveVersion, testContext.Done(), fakeRestMapper, ) diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/admission.go b/staging/src/k8s.io/apiserver/pkg/server/options/admission.go index 6b4669e450637..2a7ddf4d8e012 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/admission.go @@ -44,6 +44,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/restmapper" + "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" ) @@ -130,6 +131,7 @@ func (a *AdmissionOptions) ApplyTo( kubeClient kubernetes.Interface, dynamicClient dynamic.Interface, features featuregate.FeatureGate, + effectiveVersion compatibility.EffectiveVersion, pluginInitializers ...admission.PluginInitializer, ) error { if a == nil { @@ -154,7 +156,7 @@ func (a *AdmissionOptions) ApplyTo( discoveryClient := cacheddiscovery.NewMemCacheClient(kubeClient.Discovery()) discoveryRESTMapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) genericInitializer := initializer.New(kubeClient, dynamicClient, informers, c.Authorization.Authorizer, features, - c.DrainedNotify(), discoveryRESTMapper) + effectiveVersion, c.DrainedNotify(), discoveryRESTMapper) initializersChain := admission.PluginInitializers{genericInitializer} initializersChain = append(initializersChain, pluginInitializers...) diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go b/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go index 2ead600f83c19..a80c3f9ed42f9 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/recommended.go @@ -141,6 +141,7 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error { return err } if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, kubeClient, dynamicClient, o.FeatureGate, + config.EffectiveVersion, initializers...); err != nil { return err } diff --git a/staging/src/k8s.io/pod-security-admission/admission/admission_test.go b/staging/src/k8s.io/pod-security-admission/admission/admission_test.go index 868950ce5eebe..38bd4dac80aa2 100644 --- a/staging/src/k8s.io/pod-security-admission/admission/admission_test.go +++ b/staging/src/k8s.io/pod-security-admission/admission/admission_test.go @@ -702,7 +702,7 @@ func TestValidatePodAndController(t *testing.T) { config.Exemptions.RuntimeClasses = []string{exemptRuntimeClass} config.Exemptions.Usernames = []string{exemptUser} - evaluator, err := policy.NewEvaluator(policy.DefaultChecks()) + evaluator, err := policy.NewEvaluator(policy.DefaultChecks(), nil) assert.NoError(t, err) type testCase struct { diff --git a/staging/src/k8s.io/pod-security-admission/cmd/webhook/server/server.go b/staging/src/k8s.io/pod-security-admission/cmd/webhook/server/server.go index c357ab231a776..3d71a23e3ecea 100644 --- a/staging/src/k8s.io/pod-security-admission/cmd/webhook/server/server.go +++ b/staging/src/k8s.io/pod-security-admission/cmd/webhook/server/server.go @@ -281,7 +281,7 @@ func Setup(c *Config) (*Server, error) { namespaceInformer := s.informerFactory.Core().V1().Namespaces() namespaceLister := namespaceInformer.Lister() - evaluator, err := policy.NewEvaluator(policy.DefaultChecks()) + evaluator, err := policy.NewEvaluator(policy.DefaultChecks(), nil) if err != nil { return nil, fmt.Errorf("could not create PodSecurityRegistry: %w", err) } diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle.go b/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle.go index 43387c052192a..ad25d8eab2832 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle.go +++ b/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle.go @@ -18,7 +18,6 @@ package policy import ( "fmt" - "sync/atomic" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -74,21 +73,7 @@ func CheckHostProbesAndHostLifecycle() Check { } } -// TODO(liggitt): rework this to make emulation version influence "latest" across all checks, instead of piece-mill feature gate checking. -var skipProbeHostEnforcement = &atomic.Bool{} - -// SkipProbeHostEnforcement allows opting out of probe host enforcement in baseline policies. -// This should only be done in clusters emulating minor versions prior to introduction of this check. -func SkipProbeHostEnforcement() { - skipProbeHostEnforcement.Store(true) -} - func hostProbesAndHostLifecycleV1Dot34(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { - // cluster is emulating a minor prior to this check existing - if skipProbeHostEnforcement.Load() { - return CheckResult{Allowed: true} - } - badContainers := sets.New[string]() forbidden := sets.New[string]() visitContainers(podSpec, func(container *corev1.Container) { diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle_test.go b/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle_test.go index 11e040680bdb8..f6d5034ecf13a 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle_test.go +++ b/staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle_test.go @@ -20,8 +20,63 @@ import ( "testing" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/pod-security-admission/api" + "k8s.io/utils/ptr" ) +func TestHostProbesAndHostLifecycleEmulation(t *testing.T) { + testcases := []struct { + name string + emulateVersion *api.Version + hostCheckActive bool + }{ + { + name: "no emulation", + emulateVersion: nil, + hostCheckActive: true, + }, + { + name: "emulate 1.34", + emulateVersion: ptr.To(api.MajorMinorVersion(1, 34)), + hostCheckActive: true, + }, + { + name: "emulate 1.33", + emulateVersion: ptr.To(api.MajorMinorVersion(1, 33)), + hostCheckActive: false, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + e, err := NewEvaluator(DefaultChecks(), tc.emulateVersion) + if err != nil { + t.Fatal(err) + } + + // pod that uses a probe with an explicit host + podMetadata := &metav1.ObjectMeta{} + podSpec := &corev1.PodSpec{Containers: []corev1.Container{{StartupProbe: &corev1.Probe{ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{Host: "localhost"}}}}}} + + // evaluating "version=latest" and "version=1.34" should only allow if the host check is not active + expectAllowed := tc.hostCheckActive == false + if result := AggregateCheckResults(e.EvaluatePod(api.LevelVersion{Level: api.LevelBaseline, Version: api.LatestVersion()}, podMetadata, podSpec)); result.Allowed != expectAllowed { + t.Fatalf("evaluating with 'latest' expected allowed=%v, got %v: %v", expectAllowed, result.Allowed, result.ForbiddenReasons) + } + if result := AggregateCheckResults(e.EvaluatePod(api.LevelVersion{Level: api.LevelBaseline, Version: api.MajorMinorVersion(1, 34)}, podMetadata, podSpec)); result.Allowed != expectAllowed { + t.Fatalf("evaluating with '1.34' expected allowed=%v, got %v: %v", expectAllowed, result.Allowed, result.ForbiddenReasons) + } + + // evaluating "version=1.33" should always allow + if result := AggregateCheckResults(e.EvaluatePod(api.LevelVersion{Level: api.LevelBaseline, Version: api.MajorMinorVersion(1, 33)}, podMetadata, podSpec)); !result.Allowed { + t.Fatalf("evaluating with '1.33' expected allowed=true, got %v: %v", result.Allowed, result.ForbiddenReasons) + } + }) + } + +} + func TestHostProbesAndHostLifecycle(t *testing.T) { tests := []struct { name string diff --git a/staging/src/k8s.io/pod-security-admission/policy/registry.go b/staging/src/k8s.io/pod-security-admission/policy/registry.go index 4b91bef8875d4..1648ff020cc60 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/registry.go +++ b/staging/src/k8s.io/pod-security-admission/policy/registry.go @@ -46,7 +46,7 @@ type checkRegistry struct { // 2. Check.Level must be either Baseline or Restricted // 3. Checks must have a non-empty set of versions, sorted in a strictly increasing order // 4. Check.Versions cannot include 'latest' -func NewEvaluator(checks []Check) (Evaluator, error) { +func NewEvaluator(checks []Check, emulationVersion *api.Version) (*checkRegistry, error) { if err := validateChecks(checks); err != nil { return nil, err } @@ -55,6 +55,12 @@ func NewEvaluator(checks []Check) (Evaluator, error) { restrictedChecks: map[api.Version][]CheckPodFn{}, } populate(r, checks) + + // lower the max version if we're emulating an older minor + if emulationVersion != nil && (*emulationVersion).Older(r.maxVersion) { + r.maxVersion = *emulationVersion + } + return r, nil } diff --git a/staging/src/k8s.io/pod-security-admission/policy/registry_test.go b/staging/src/k8s.io/pod-security-admission/policy/registry_test.go index 039b83b25b8a1..ee6c078bec2e4 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/registry_test.go +++ b/staging/src/k8s.io/pod-security-admission/policy/registry_test.go @@ -43,7 +43,7 @@ func TestCheckRegistry(t *testing.T) { multiOverride.Versions[1].OverrideCheckIDs = []CheckID{"d"} checks = append(checks, multiOverride) - reg, err := NewEvaluator(checks) + reg, err := NewEvaluator(checks, nil) require.NoError(t, err) levelCases := []registryTestCase{ @@ -76,7 +76,7 @@ func TestCheckRegistry_NoBaseline(t *testing.T) { withOverrides(generateCheck("h", api.LevelRestricted, []string{"v1.0"}), []CheckID{"b"}), } - reg, err := NewEvaluator(checks) + reg, err := NewEvaluator(checks, nil) require.NoError(t, err) levelCases := []registryTestCase{ @@ -103,7 +103,7 @@ func TestCheckRegistry_NoRestricted(t *testing.T) { generateCheck("d", api.LevelBaseline, []string{"v1.11", "v1.15", "v1.20"}), } - reg, err := NewEvaluator(checks) + reg, err := NewEvaluator(checks, nil) require.NoError(t, err) levelCases := []registryTestCase{ @@ -124,7 +124,7 @@ func TestCheckRegistry_NoRestricted(t *testing.T) { } func TestCheckRegistry_Empty(t *testing.T) { - reg, err := NewEvaluator(nil) + reg, err := NewEvaluator(nil, nil) require.NoError(t, err) levelCases := []registryTestCase{ diff --git a/staging/src/k8s.io/pod-security-admission/test/run.go b/staging/src/k8s.io/pod-security-admission/test/run.go index 8e3451e419e4b..c6eb117afcb94 100644 --- a/staging/src/k8s.io/pod-security-admission/test/run.go +++ b/staging/src/k8s.io/pod-security-admission/test/run.go @@ -53,6 +53,10 @@ type Options struct { // If unset, all testcases are run. Features featuregate.FeatureGate + // EmulationVersion optionally indicates a different minor version is being emulated. + // This can lower the effective "latest" version. + EmulationVersion *api.Version + // CreateNamespace is an optional stub for creating a namespace with the given name and labels. // Returning an error fails the test. // If nil, DefaultCreateNamespace is used. @@ -194,7 +198,7 @@ func Run(t *testing.T, opts Options) { if len(opts.Checks) == 0 { opts.Checks = policy.DefaultChecks() } - _, err = policy.NewEvaluator(opts.Checks) + _, err = policy.NewEvaluator(opts.Checks, opts.EmulationVersion) if err != nil { t.Fatalf("invalid checks: %v", err) } diff --git a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml index f399b0035b962..57d3a693c27d4 100644 --- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml +++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -1231,12 +1231,6 @@ lockToDefault: false preRelease: Beta version: "1.34" -- name: ProbeHostPodSecurityStandards - versionedSpecs: - - default: true - lockToDefault: true - preRelease: GA - version: "1.34" - name: ProcMountType versionedSpecs: - default: false diff --git a/test/e2e/framework/pod/utils.go b/test/e2e/framework/pod/utils.go index b1fd25bed6dbd..74d42507a5282 100644 --- a/test/e2e/framework/pod/utils.go +++ b/test/e2e/framework/pod/utils.go @@ -178,7 +178,7 @@ func GetRestrictedContainerSecurityContext() *v1.SecurityContext { } } -var psaEvaluator, _ = psapolicy.NewEvaluator(psapolicy.DefaultChecks()) +var psaEvaluator, _ = psapolicy.NewEvaluator(psapolicy.DefaultChecks(), nil) // MustMixinRestrictedPodSecurity makes the given pod compliant with the restricted pod security level. // If doing so would overwrite existing non-conformant configuration, a test failure is triggered. diff --git a/test/integration/defaulttolerationseconds/defaulttolerationseconds_test.go b/test/integration/defaulttolerationseconds/defaulttolerationseconds_test.go index ccfe0f97a80cb..72c8ced008e7f 100644 --- a/test/integration/defaulttolerationseconds/defaulttolerationseconds_test.go +++ b/test/integration/defaulttolerationseconds/defaulttolerationseconds_test.go @@ -110,7 +110,7 @@ func TestAdmission(t *testing.T) { // newHandlerForTest returns a handler configured for testing. func newHandlerForTest() (*defaulttolerationseconds.Plugin, error) { handler := defaulttolerationseconds.NewDefaultTolerationSeconds() - pluginInitializer := initializer.New(nil, nil, nil, nil, nil, nil, nil) + pluginInitializer := initializer.New(nil, nil, nil, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) return handler, admission.ValidateInitialization(handler) } diff --git a/test/integration/node/lifecycle_test.go b/test/integration/node/lifecycle_test.go index e79f1fe555242..ff1d87a54fef6 100644 --- a/test/integration/node/lifecycle_test.go +++ b/test/integration/node/lifecycle_test.go @@ -467,7 +467,7 @@ func TestTaintBasedEvictions(t *testing.T) { // newHandlerForTest returns a handler configured for testing. func newHandlerForTest() (*defaulttolerationseconds.Plugin, error) { handler := defaulttolerationseconds.NewDefaultTolerationSeconds() - pluginInitializer := initializer.New(nil, nil, nil, nil, nil, nil, nil) + pluginInitializer := initializer.New(nil, nil, nil, nil, nil, nil, nil, nil) pluginInitializer.Initialize(handler) return handler, admission.ValidateInitialization(handler) }