Skip to content

Commit 22295e2

Browse files
committed
Make pod-security-admission honor emulation version
1 parent af95200 commit 22295e2

File tree

9 files changed

+110
-18
lines changed

9 files changed

+110
-18
lines changed

plugin/pkg/admission/security/podsecurity/admission.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"k8s.io/client-go/informers"
4444
"k8s.io/client-go/kubernetes"
4545
corev1listers "k8s.io/client-go/listers/core/v1"
46+
"k8s.io/component-base/compatibility"
4647
"k8s.io/component-base/featuregate"
4748
"k8s.io/component-base/metrics/legacyregistry"
4849
"k8s.io/kubernetes/pkg/api/legacyscheme"
@@ -72,6 +73,9 @@ type Plugin struct {
7273

7374
inspectedFeatureGates bool
7475

76+
inspectedEffectiveVersion bool
77+
emulationVersion *podsecurityadmissionapi.Version
78+
7579
client kubernetes.Interface
7680
namespaceLister corev1listers.NamespaceLister
7781
podLister corev1listers.PodLister
@@ -104,16 +108,10 @@ func newPlugin(reader io.Reader) (*Plugin, error) {
104108
return nil, err
105109
}
106110

107-
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
108-
if err != nil {
109-
return nil, fmt.Errorf("could not create PodSecurityRegistry: %w", err)
110-
}
111-
112111
return &Plugin{
113112
Handler: admission.NewHandler(admission.Create, admission.Update),
114113
delegate: &podsecurityadmission.Admission{
115114
Configuration: config,
116-
Evaluator: evaluator,
117115
Metrics: getDefaultRecorder(),
118116
PodSpecExtractor: podsecurityadmission.DefaultPodSpecExtractor{},
119117
},
@@ -146,8 +144,30 @@ func (p *Plugin) updateDelegate() {
146144
if p.client == nil {
147145
return
148146
}
149-
p.delegate.PodLister = podsecurityadmission.PodListerFromInformer(p.podLister)
150-
p.delegate.NamespaceGetter = podsecurityadmission.NamespaceGetterFromListerAndClient(p.namespaceLister, p.client)
147+
if !p.inspectedEffectiveVersion {
148+
return
149+
}
150+
if p.delegate.PodLister == nil {
151+
p.delegate.PodLister = podsecurityadmission.PodListerFromInformer(p.podLister)
152+
}
153+
if p.delegate.NamespaceGetter == nil {
154+
p.delegate.NamespaceGetter = podsecurityadmission.NamespaceGetterFromListerAndClient(p.namespaceLister, p.client)
155+
}
156+
if p.delegate.Evaluator == nil {
157+
evaluator, err := policy.NewEvaluator(policy.DefaultChecks(), p.emulationVersion)
158+
if err != nil {
159+
panic(fmt.Errorf("could not create PodSecurityRegistry: %w", err))
160+
}
161+
p.delegate.Evaluator = evaluator
162+
}
163+
}
164+
165+
func (c *Plugin) InspectEffectiveVersion(version compatibility.EffectiveVersion) {
166+
c.inspectedEffectiveVersion = true
167+
if !version.EmulationVersion().EqualTo(version.BinaryVersion()) {
168+
emulationVersion := podsecurityadmissionapi.MajorMinorVersion(int(version.EmulationVersion().Major()), int(version.EmulationVersion().Minor()))
169+
c.emulationVersion = &emulationVersion
170+
}
151171
}
152172

153173
func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
@@ -160,6 +180,9 @@ func (p *Plugin) ValidateInitialization() error {
160180
if !p.inspectedFeatureGates {
161181
return fmt.Errorf("%s did not see feature gates", PluginName)
162182
}
183+
if !p.inspectedEffectiveVersion {
184+
return fmt.Errorf("%s did not see effective version", PluginName)
185+
}
163186
if err := p.delegate.CompleteConfiguration(); err != nil {
164187
return fmt.Errorf("%s configuration error: %w", PluginName, err)
165188
}

plugin/pkg/admission/security/podsecurity/admission_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ import (
2323
"strings"
2424
"testing"
2525

26+
"sigs.k8s.io/yaml"
27+
2628
corev1 "k8s.io/api/core/v1"
2729
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830
"k8s.io/apimachinery/pkg/runtime"
2931
"k8s.io/apimachinery/pkg/runtime/schema"
3032
"k8s.io/apimachinery/pkg/types"
3133
"k8s.io/apiserver/pkg/admission"
3234
"k8s.io/apiserver/pkg/authentication/user"
35+
"k8s.io/apiserver/pkg/util/compatibility"
3336
utilfeature "k8s.io/apiserver/pkg/util/feature"
3437
"k8s.io/apiserver/pkg/warning"
3538
"k8s.io/client-go/informers"
@@ -40,7 +43,6 @@ import (
4043
v1 "k8s.io/kubernetes/pkg/apis/core/v1"
4144
podsecurityadmission "k8s.io/pod-security-admission/admission"
4245
"k8s.io/utils/ptr"
43-
"sigs.k8s.io/yaml"
4446
)
4547

4648
func TestConvert(t *testing.T) {
@@ -81,6 +83,7 @@ func BenchmarkVerifyPod(b *testing.B) {
8183
b.Fatal(err)
8284
}
8385

86+
p.InspectEffectiveVersion(compatibility.DefaultBuildEffectiveVersion())
8487
p.InspectFeatureGates(utilfeature.DefaultFeatureGate)
8588

8689
enforceImplicitPrivilegedNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "enforce-implicit", Labels: map[string]string{}}}
@@ -189,6 +192,7 @@ func BenchmarkVerifyNamespace(b *testing.B) {
189192
b.Fatal(err)
190193
}
191194

195+
p.InspectEffectiveVersion(compatibility.DefaultBuildEffectiveVersion())
192196
p.InspectFeatureGates(utilfeature.DefaultFeatureGate)
193197

194198
namespace := "enforce"

staging/src/k8s.io/pod-security-admission/admission/admission_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ func TestValidatePodAndController(t *testing.T) {
702702
config.Exemptions.RuntimeClasses = []string{exemptRuntimeClass}
703703
config.Exemptions.Usernames = []string{exemptUser}
704704

705-
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
705+
evaluator, err := policy.NewEvaluator(policy.DefaultChecks(), nil)
706706
assert.NoError(t, err)
707707

708708
type testCase struct {

staging/src/k8s.io/pod-security-admission/cmd/webhook/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ func Setup(c *Config) (*Server, error) {
281281
namespaceInformer := s.informerFactory.Core().V1().Namespaces()
282282
namespaceLister := namespaceInformer.Lister()
283283

284-
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
284+
evaluator, err := policy.NewEvaluator(policy.DefaultChecks(), nil)
285285
if err != nil {
286286
return nil, fmt.Errorf("could not create PodSecurityRegistry: %w", err)
287287
}

staging/src/k8s.io/pod-security-admission/policy/check_hostProbesAndhostLifecycle_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,63 @@ import (
2020
"testing"
2121

2222
corev1 "k8s.io/api/core/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/pod-security-admission/api"
25+
"k8s.io/utils/ptr"
2326
)
2427

28+
func TestHostProbesAndHostLifecycleEmulation(t *testing.T) {
29+
testcases := []struct {
30+
name string
31+
emulateVersion *api.Version
32+
hostCheckActive bool
33+
}{
34+
{
35+
name: "no emulation",
36+
emulateVersion: nil,
37+
hostCheckActive: true,
38+
},
39+
{
40+
name: "emulate 1.34",
41+
emulateVersion: ptr.To(api.MajorMinorVersion(1, 34)),
42+
hostCheckActive: true,
43+
},
44+
{
45+
name: "emulate 1.33",
46+
emulateVersion: ptr.To(api.MajorMinorVersion(1, 33)),
47+
hostCheckActive: false,
48+
},
49+
}
50+
51+
for _, tc := range testcases {
52+
t.Run(tc.name, func(t *testing.T) {
53+
e, err := NewEvaluator(DefaultChecks(), tc.emulateVersion)
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
58+
// pod that uses a probe with an explicit host
59+
podMetadata := &metav1.ObjectMeta{}
60+
podSpec := &corev1.PodSpec{Containers: []corev1.Container{{StartupProbe: &corev1.Probe{ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{Host: "localhost"}}}}}}
61+
62+
// evaluating "version=latest" and "version=1.34" should only allow if the host check is not active
63+
expectAllowed := tc.hostCheckActive == false
64+
if result := AggregateCheckResults(e.EvaluatePod(api.LevelVersion{Level: api.LevelBaseline, Version: api.LatestVersion()}, podMetadata, podSpec)); result.Allowed != expectAllowed {
65+
t.Fatalf("evaluating with 'latest' expected allowed=%v, got %v: %v", expectAllowed, result.Allowed, result.ForbiddenReasons)
66+
}
67+
if result := AggregateCheckResults(e.EvaluatePod(api.LevelVersion{Level: api.LevelBaseline, Version: api.MajorMinorVersion(1, 34)}, podMetadata, podSpec)); result.Allowed != expectAllowed {
68+
t.Fatalf("evaluating with '1.34' expected allowed=%v, got %v: %v", expectAllowed, result.Allowed, result.ForbiddenReasons)
69+
}
70+
71+
// evaluating "version=1.33" should always allow
72+
if result := AggregateCheckResults(e.EvaluatePod(api.LevelVersion{Level: api.LevelBaseline, Version: api.MajorMinorVersion(1, 33)}, podMetadata, podSpec)); !result.Allowed {
73+
t.Fatalf("evaluating with '1.33' expected allowed=true, got %v: %v", result.Allowed, result.ForbiddenReasons)
74+
}
75+
})
76+
}
77+
78+
}
79+
2580
func TestHostProbesAndHostLifecycle(t *testing.T) {
2681
tests := []struct {
2782
name string

staging/src/k8s.io/pod-security-admission/policy/registry.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type checkRegistry struct {
4646
// 2. Check.Level must be either Baseline or Restricted
4747
// 3. Checks must have a non-empty set of versions, sorted in a strictly increasing order
4848
// 4. Check.Versions cannot include 'latest'
49-
func NewEvaluator(checks []Check) (Evaluator, error) {
49+
func NewEvaluator(checks []Check, emulationVersion *api.Version) (*checkRegistry, error) {
5050
if err := validateChecks(checks); err != nil {
5151
return nil, err
5252
}
@@ -55,6 +55,12 @@ func NewEvaluator(checks []Check) (Evaluator, error) {
5555
restrictedChecks: map[api.Version][]CheckPodFn{},
5656
}
5757
populate(r, checks)
58+
59+
// lower the max version if we're emulating an older minor
60+
if emulationVersion != nil && (*emulationVersion).Older(r.maxVersion) {
61+
r.maxVersion = *emulationVersion
62+
}
63+
5864
return r, nil
5965
}
6066

staging/src/k8s.io/pod-security-admission/policy/registry_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestCheckRegistry(t *testing.T) {
4343
multiOverride.Versions[1].OverrideCheckIDs = []CheckID{"d"}
4444
checks = append(checks, multiOverride)
4545

46-
reg, err := NewEvaluator(checks)
46+
reg, err := NewEvaluator(checks, nil)
4747
require.NoError(t, err)
4848

4949
levelCases := []registryTestCase{
@@ -76,7 +76,7 @@ func TestCheckRegistry_NoBaseline(t *testing.T) {
7676
withOverrides(generateCheck("h", api.LevelRestricted, []string{"v1.0"}), []CheckID{"b"}),
7777
}
7878

79-
reg, err := NewEvaluator(checks)
79+
reg, err := NewEvaluator(checks, nil)
8080
require.NoError(t, err)
8181

8282
levelCases := []registryTestCase{
@@ -103,7 +103,7 @@ func TestCheckRegistry_NoRestricted(t *testing.T) {
103103
generateCheck("d", api.LevelBaseline, []string{"v1.11", "v1.15", "v1.20"}),
104104
}
105105

106-
reg, err := NewEvaluator(checks)
106+
reg, err := NewEvaluator(checks, nil)
107107
require.NoError(t, err)
108108

109109
levelCases := []registryTestCase{
@@ -124,7 +124,7 @@ func TestCheckRegistry_NoRestricted(t *testing.T) {
124124
}
125125

126126
func TestCheckRegistry_Empty(t *testing.T) {
127-
reg, err := NewEvaluator(nil)
127+
reg, err := NewEvaluator(nil, nil)
128128
require.NoError(t, err)
129129

130130
levelCases := []registryTestCase{

staging/src/k8s.io/pod-security-admission/test/run.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ type Options struct {
5353
// If unset, all testcases are run.
5454
Features featuregate.FeatureGate
5555

56+
// EmulationVersion optionally indicates a different minor version is being emulated.
57+
// This can lower the effective "latest" version.
58+
EmulationVersion *api.Version
59+
5660
// CreateNamespace is an optional stub for creating a namespace with the given name and labels.
5761
// Returning an error fails the test.
5862
// If nil, DefaultCreateNamespace is used.
@@ -194,7 +198,7 @@ func Run(t *testing.T, opts Options) {
194198
if len(opts.Checks) == 0 {
195199
opts.Checks = policy.DefaultChecks()
196200
}
197-
_, err = policy.NewEvaluator(opts.Checks)
201+
_, err = policy.NewEvaluator(opts.Checks, opts.EmulationVersion)
198202
if err != nil {
199203
t.Fatalf("invalid checks: %v", err)
200204
}

test/e2e/framework/pod/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func GetRestrictedContainerSecurityContext() *v1.SecurityContext {
178178
}
179179
}
180180

181-
var psaEvaluator, _ = psapolicy.NewEvaluator(psapolicy.DefaultChecks())
181+
var psaEvaluator, _ = psapolicy.NewEvaluator(psapolicy.DefaultChecks(), nil)
182182

183183
// MustMixinRestrictedPodSecurity makes the given pod compliant with the restricted pod security level.
184184
// If doing so would overwrite existing non-conformant configuration, a test failure is triggered.

0 commit comments

Comments
 (0)