mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Add MixinRestrictedPodSecurity e2e util
This commit is contained in:
parent
f8c77fda0c
commit
ccc69b1e9a
@ -18,9 +18,14 @@ package pod
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||
psaapi "k8s.io/pod-security-admission/api"
|
||||
psapolicy "k8s.io/pod-security-admission/policy"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
@ -115,10 +120,16 @@ func GetLinuxLabel() *v1.SELinuxOptions {
|
||||
Level: "s0:c0,c1"}
|
||||
}
|
||||
|
||||
// GetRestrictedPodSecurityContext returns a minimal restricted pod security context.
|
||||
// DefaultNonRootUser is the default user ID used for running restricted (non-root) containers.
|
||||
const DefaultNonRootUser = 1000
|
||||
|
||||
// GetRestrictedPodSecurityContext returns a restricted pod security context.
|
||||
// This includes setting RunAsUser for convenience, to pass the RunAsNonRoot check.
|
||||
// Tests that require a specific user ID should override this.
|
||||
func GetRestrictedPodSecurityContext() *v1.PodSecurityContext {
|
||||
return &v1.PodSecurityContext{
|
||||
RunAsNonRoot: pointer.BoolPtr(true),
|
||||
RunAsUser: pointer.Int64(DefaultNonRootUser),
|
||||
SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault},
|
||||
}
|
||||
}
|
||||
@ -130,3 +141,69 @@ func GetRestrictedContainerSecurityContext() *v1.SecurityContext {
|
||||
Capabilities: &v1.Capabilities{Drop: []v1.Capability{"ALL"}},
|
||||
}
|
||||
}
|
||||
|
||||
var psaEvaluator, _ = psapolicy.NewEvaluator(psapolicy.DefaultChecks())
|
||||
|
||||
// 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.
|
||||
func MustMixinRestrictedPodSecurity(pod *v1.Pod) *v1.Pod {
|
||||
err := MixinRestrictedPodSecurity(pod)
|
||||
gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred())
|
||||
return pod
|
||||
}
|
||||
|
||||
// MixinRestrictedPodSecurity makes the given pod compliant with the restricted pod security level.
|
||||
// If doing so would overwrite existing non-conformant configuration, an error is returned.
|
||||
// Note that this sets a default RunAsUser. See GetRestrictedPodSecurityContext.
|
||||
// TODO(#105919): Handle PodOS for windows pods.
|
||||
func MixinRestrictedPodSecurity(pod *v1.Pod) error {
|
||||
if pod.Spec.SecurityContext == nil {
|
||||
pod.Spec.SecurityContext = GetRestrictedPodSecurityContext()
|
||||
} else {
|
||||
if pod.Spec.SecurityContext.RunAsNonRoot == nil {
|
||||
pod.Spec.SecurityContext.RunAsNonRoot = pointer.BoolPtr(true)
|
||||
}
|
||||
if pod.Spec.SecurityContext.RunAsUser == nil {
|
||||
pod.Spec.SecurityContext.RunAsUser = pointer.Int64Ptr(DefaultNonRootUser)
|
||||
}
|
||||
if pod.Spec.SecurityContext.SeccompProfile == nil {
|
||||
pod.Spec.SecurityContext.SeccompProfile = &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.Containers {
|
||||
mixinRestrictedContainerSecurityContext(&pod.Spec.Containers[i])
|
||||
}
|
||||
for i := range pod.Spec.InitContainers {
|
||||
mixinRestrictedContainerSecurityContext(&pod.Spec.InitContainers[i])
|
||||
}
|
||||
|
||||
// Validate the resulting pod against the restricted profile.
|
||||
restricted := psaapi.LevelVersion{
|
||||
Level: psaapi.LevelRestricted,
|
||||
Version: psaapi.LatestVersion(),
|
||||
}
|
||||
if agg := psapolicy.AggregateCheckResults(psaEvaluator.EvaluatePod(restricted, &pod.ObjectMeta, &pod.Spec)); !agg.Allowed {
|
||||
return fmt.Errorf("failed to make pod %s restricted: %s", pod.Name, agg.ForbiddenDetail())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mixinRestrictedContainerSecurityContext adds the required container security context options to
|
||||
// be compliant with the restricted pod security level. Non-conformance checking is handled by the
|
||||
// caller.
|
||||
func mixinRestrictedContainerSecurityContext(container *v1.Container) {
|
||||
if container.SecurityContext == nil {
|
||||
container.SecurityContext = GetRestrictedContainerSecurityContext()
|
||||
} else {
|
||||
if container.SecurityContext.AllowPrivilegeEscalation == nil {
|
||||
container.SecurityContext.AllowPrivilegeEscalation = pointer.Bool(false)
|
||||
}
|
||||
if container.SecurityContext.Capabilities == nil {
|
||||
container.SecurityContext.Capabilities = &v1.Capabilities{}
|
||||
}
|
||||
if len(container.SecurityContext.Capabilities.Drop) == 0 {
|
||||
container.SecurityContext.Capabilities.Drop = []v1.Capability{"ALL"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
94
test/e2e/framework/pod/utils_test.go
Normal file
94
test/e2e/framework/pod/utils_test.go
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2022 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 pod
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func TestMixinRestrictedPodSecurity(t *testing.T) {
|
||||
restrictablePods := []v1.Pod{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{
|
||||
Name: "pause",
|
||||
Image: "pause",
|
||||
}},
|
||||
},
|
||||
}, {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "already_restricted",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
SecurityContext: GetRestrictedPodSecurityContext(),
|
||||
Containers: []v1.Container{{
|
||||
Name: "pause",
|
||||
Image: "pause",
|
||||
SecurityContext: GetRestrictedContainerSecurityContext(),
|
||||
}},
|
||||
},
|
||||
}, {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "empty_securityContext",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
Containers: []v1.Container{{
|
||||
Name: "pause",
|
||||
Image: "pause",
|
||||
SecurityContext: &v1.SecurityContext{},
|
||||
}},
|
||||
},
|
||||
}}
|
||||
|
||||
for _, pod := range restrictablePods {
|
||||
t.Run(pod.Name, func(t *testing.T) {
|
||||
p := pod // closure
|
||||
assert.NoError(t, MixinRestrictedPodSecurity(&p))
|
||||
assert.Equal(t, GetRestrictedPodSecurityContext(), p.Spec.SecurityContext,
|
||||
"Mixed in PodSecurityContext should equal the from-scratch PodSecurityContext")
|
||||
assert.Equal(t, GetRestrictedContainerSecurityContext(), p.Spec.Containers[0].SecurityContext,
|
||||
"Mixed in SecurityContext should equal the from-scratch SecurityContext")
|
||||
})
|
||||
}
|
||||
|
||||
privilegedPod := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "privileged",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{
|
||||
Name: "pause",
|
||||
Image: "pause",
|
||||
SecurityContext: &v1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
t.Run("privileged", func(t *testing.T) {
|
||||
assert.Error(t, MixinRestrictedPodSecurity(&privilegedPod))
|
||||
})
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user