mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 20:42:26 +00:00
AppArmor API changes
This commit is contained in:
parent
b0ee334374
commit
94927afb50
@ -20,7 +20,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metavalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
@ -540,12 +539,21 @@ func dropDisabledFields(
|
||||
podSpec = &api.PodSpec{}
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorInUse(oldPodAnnotations) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorInUse(oldPodAnnotations, oldPodSpec) {
|
||||
for k := range podAnnotations {
|
||||
if strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
|
||||
if strings.HasPrefix(k, api.AppArmorContainerAnnotationKeyPrefix) {
|
||||
delete(podAnnotations, k)
|
||||
}
|
||||
}
|
||||
if podSpec.SecurityContext != nil {
|
||||
podSpec.SecurityContext.AppArmorProfile = nil
|
||||
}
|
||||
VisitContainers(podSpec, AllContainers, func(c *api.Container, _ ContainerType) bool {
|
||||
if c.SecurityContext != nil {
|
||||
c.SecurityContext.AppArmorProfile = nil
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// If the feature is disabled and not in use, drop the hostUsers field.
|
||||
@ -940,13 +948,28 @@ func procMountInUse(podSpec *api.PodSpec) bool {
|
||||
}
|
||||
|
||||
// appArmorInUse returns true if the pod has apparmor related information
|
||||
func appArmorInUse(podAnnotations map[string]string) bool {
|
||||
func appArmorInUse(podAnnotations map[string]string, podSpec *api.PodSpec) bool {
|
||||
if podSpec == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for k := range podAnnotations {
|
||||
if strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
|
||||
if strings.HasPrefix(k, api.AppArmorContainerAnnotationKeyPrefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
if podSpec.SecurityContext != nil && podSpec.SecurityContext.AppArmorProfile != nil {
|
||||
return true
|
||||
}
|
||||
hasAppArmorContainer := false
|
||||
VisitContainers(podSpec, AllContainers, func(c *api.Container, _ ContainerType) bool {
|
||||
if c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil {
|
||||
hasAppArmorContainer = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return hasAppArmorContainer
|
||||
}
|
||||
|
||||
// restartableInitContainersInUse returns true if the pod spec is non-nil and
|
||||
|
@ -23,6 +23,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
@ -704,83 +706,84 @@ func TestDropProcMount(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDropAppArmor(t *testing.T) {
|
||||
podWithAppArmor := func() *api.Pod {
|
||||
return &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.AppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}},
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
}
|
||||
podWithoutAppArmor := func() *api.Pod {
|
||||
return &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
}
|
||||
|
||||
podInfo := []struct {
|
||||
tests := []struct {
|
||||
description string
|
||||
hasAppArmor bool
|
||||
pod func() *api.Pod
|
||||
}{
|
||||
{
|
||||
description: "has AppArmor",
|
||||
hasAppArmor: true,
|
||||
pod: podWithAppArmor,
|
||||
pod api.Pod
|
||||
}{{
|
||||
description: "with AppArmor Annotations",
|
||||
hasAppArmor: true,
|
||||
pod: api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.AppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}},
|
||||
Spec: api.PodSpec{},
|
||||
},
|
||||
{
|
||||
description: "does not have AppArmor",
|
||||
hasAppArmor: false,
|
||||
pod: podWithoutAppArmor,
|
||||
}, {
|
||||
description: "with pod AppArmor profile",
|
||||
hasAppArmor: true,
|
||||
pod: api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
|
||||
Spec: api.PodSpec{
|
||||
SecurityContext: &api.PodSecurityContext{
|
||||
AppArmorProfile: &api.AppArmorProfile{
|
||||
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "is nil",
|
||||
hasAppArmor: false,
|
||||
pod: func() *api.Pod { return nil },
|
||||
}, {
|
||||
description: "with container AppArmor profile",
|
||||
hasAppArmor: true,
|
||||
pod: api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{
|
||||
SecurityContext: &api.SecurityContext{
|
||||
AppArmorProfile: &api.AppArmorProfile{
|
||||
Type: api.AppArmorProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}, {
|
||||
description: "without AppArmor",
|
||||
hasAppArmor: false,
|
||||
pod: api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
|
||||
Spec: api.PodSpec{},
|
||||
},
|
||||
}}
|
||||
|
||||
for _, enabled := range []bool{true, false} {
|
||||
for _, oldPodInfo := range podInfo {
|
||||
for _, newPodInfo := range podInfo {
|
||||
oldPodHasAppArmor, oldPod := oldPodInfo.hasAppArmor, oldPodInfo.pod()
|
||||
newPodHasAppArmor, newPod := newPodInfo.hasAppArmor, newPodInfo.pod()
|
||||
if newPod == nil {
|
||||
continue
|
||||
for _, test := range tests {
|
||||
for _, enabled := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%v/enabled=%v", test.description, enabled), func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)()
|
||||
|
||||
newPod := test.pod.DeepCopy()
|
||||
|
||||
if actual := appArmorInUse(newPod.Annotations, &newPod.Spec); actual != test.hasAppArmor {
|
||||
t.Errorf("appArmorInUse does not match expectation: %t != %t", actual, test.hasAppArmor)
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)()
|
||||
DropDisabledPodFields(newPod, newPod)
|
||||
require.Equal(t, &test.pod, newPod, "unchanged pod should never be mutated")
|
||||
|
||||
DropDisabledPodFields(newPod, oldPod)
|
||||
DropDisabledPodFields(newPod, nil)
|
||||
|
||||
// old pod should never be changed
|
||||
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
|
||||
t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod()))
|
||||
if enabled {
|
||||
assert.Equal(t, &test.pod, newPod, "pod should not be mutated when AppArmor is enabled")
|
||||
} else {
|
||||
if appArmorInUse(newPod.Annotations, &newPod.Spec) {
|
||||
t.Errorf("newPod should not be using appArmor after dropping disabled fields")
|
||||
}
|
||||
|
||||
switch {
|
||||
case enabled || oldPodHasAppArmor:
|
||||
// new pod should not be changed if the feature is enabled, or if the old pod had AppArmor
|
||||
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
|
||||
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
|
||||
}
|
||||
case newPodHasAppArmor:
|
||||
// new pod should be changed
|
||||
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
|
||||
t.Errorf("new pod was not changed")
|
||||
}
|
||||
// new pod should not have AppArmor
|
||||
if !reflect.DeepEqual(newPod, podWithoutAppArmor()) {
|
||||
t.Errorf("new pod had EmptyDir SizeLimit: %v", cmp.Diff(newPod, podWithoutAppArmor()))
|
||||
}
|
||||
default:
|
||||
// new pod should not need to be changed
|
||||
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
|
||||
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
|
||||
}
|
||||
if test.hasAppArmor {
|
||||
assert.NotEqual(t, &test.pod, newPod, "pod should be mutated to drop AppArmor")
|
||||
} else {
|
||||
assert.Equal(t, &test.pod, newPod, "pod without AppArmor should not be mutated")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,19 @@ const (
|
||||
// Deprecated: set a pod or container security context `seccompProfile` of type "RuntimeDefault" instead.
|
||||
DeprecatedSeccompProfileDockerDefault string = "docker/default"
|
||||
|
||||
// AppArmorContainerAnnotationKeyPrefix is the prefix to an annotation key specifying a container's apparmor profile.
|
||||
// Deprecated: use a pod or container security context `appArmorProfile` field instead.
|
||||
AppArmorContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/"
|
||||
|
||||
// AppArmorProfileRuntimeDefault is the profile specifying the runtime default.
|
||||
AppArmorProfileRuntimeDefault = "runtime/default"
|
||||
|
||||
// AppArmorProfileLocalhostPrefix is the prefix for specifying profiles loaded on the node.
|
||||
AppArmorProfileLocalhostPrefix = "localhost/"
|
||||
|
||||
// AppArmorProfileNameUnconfined is the Unconfined AppArmor profile
|
||||
AppArmorProfileNameUnconfined = "unconfined"
|
||||
|
||||
// PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized)
|
||||
// in the Annotations of a Node.
|
||||
PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"
|
||||
|
@ -3325,6 +3325,7 @@ type PodSpec struct {
|
||||
// - spec.hostPID
|
||||
// - spec.hostIPC
|
||||
// - spec.hostUsers
|
||||
// - spec.securityContext.appArmorProfile
|
||||
// - spec.securityContext.seLinuxOptions
|
||||
// - spec.securityContext.seccompProfile
|
||||
// - spec.securityContext.fsGroup
|
||||
@ -3334,6 +3335,7 @@ type PodSpec struct {
|
||||
// - spec.securityContext.runAsUser
|
||||
// - spec.securityContext.runAsGroup
|
||||
// - spec.securityContext.supplementalGroups
|
||||
// - spec.containers[*].securityContext.appArmorProfile
|
||||
// - spec.containers[*].securityContext.seLinuxOptions
|
||||
// - spec.containers[*].securityContext.seccompProfile
|
||||
// - spec.containers[*].securityContext.capabilities
|
||||
@ -3598,6 +3600,10 @@ type PodSecurityContext struct {
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
SeccompProfile *SeccompProfile
|
||||
// appArmorProfile is the AppArmor options to use by the containers in this pod.
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
AppArmorProfile *AppArmorProfile
|
||||
}
|
||||
|
||||
// SeccompProfile defines a pod/container's seccomp profile settings.
|
||||
@ -3625,6 +3631,37 @@ const (
|
||||
SeccompProfileTypeLocalhost SeccompProfileType = "Localhost"
|
||||
)
|
||||
|
||||
// AppArmorProfile defines a pod or container's AppArmor settings.
|
||||
// +union
|
||||
type AppArmorProfile struct {
|
||||
// type indicates which kind of AppArmor profile will be applied.
|
||||
// Valid options are:
|
||||
// Localhost - a profile pre-loaded on the node.
|
||||
// RuntimeDefault - the container runtime's default profile.
|
||||
// Unconfined - no AppArmor enforcement.
|
||||
// +unionDescriminator
|
||||
Type AppArmorProfileType
|
||||
|
||||
// localhostProfile indicates a profile loaded on the node that should be used.
|
||||
// The profile must be preconfigured on the node to work.
|
||||
// Must match the loaded name of the profile.
|
||||
// Must be set if and only if type is "Localhost".
|
||||
// +optional
|
||||
LocalhostProfile *string
|
||||
}
|
||||
|
||||
type AppArmorProfileType string
|
||||
|
||||
const (
|
||||
// AppArmorProfileTypeUnconfined indicates that no AppArmor profile should be enforced.
|
||||
AppArmorProfileTypeUnconfined AppArmorProfileType = "Unconfined"
|
||||
// AppArmorProfileTypeRuntimeDefault indicates that the container runtime's default AppArmor
|
||||
// profile should be used.
|
||||
AppArmorProfileTypeRuntimeDefault AppArmorProfileType = "RuntimeDefault"
|
||||
// AppArmorProfileTypeLocalhost indicates that a profile pre-loaded on the node should be used.
|
||||
AppArmorProfileTypeLocalhost AppArmorProfileType = "Localhost"
|
||||
)
|
||||
|
||||
// PodQOSClass defines the supported qos classes of Pods.
|
||||
type PodQOSClass string
|
||||
|
||||
@ -6028,6 +6065,11 @@ type SecurityContext struct {
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
SeccompProfile *SeccompProfile
|
||||
// appArmorProfile is the AppArmor options to use by this container. If set, this profile
|
||||
// overrides the pod's appArmorProfile.
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
AppArmorProfile *AppArmorProfile
|
||||
}
|
||||
|
||||
// ProcMountType defines the type of proc mount
|
||||
|
@ -4234,6 +4234,9 @@ func validateWindows(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
securityContext := spec.SecurityContext
|
||||
// validate Pod SecurityContext
|
||||
if securityContext != nil {
|
||||
if securityContext.AppArmorProfile != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("appArmorProfile"), "cannot be set for a windows pod"))
|
||||
}
|
||||
if securityContext.SELinuxOptions != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("seLinuxOptions"), "cannot be set for a windows pod"))
|
||||
}
|
||||
@ -4280,6 +4283,9 @@ func validateWindows(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
// TODO: Think if we need to relax this restriction or some of the restrictions
|
||||
if sc != nil {
|
||||
fldPath := cFldPath.Child("securityContext")
|
||||
if sc.AppArmorProfile != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("appArmorProfile"), "cannot be set for a windows pod"))
|
||||
}
|
||||
if sc.SELinuxOptions != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("seLinuxOptions"), "cannot be set for a windows pod"))
|
||||
}
|
||||
@ -4657,6 +4663,43 @@ func validateSeccompProfileType(fldPath *field.Path, seccompProfileType core.Sec
|
||||
}
|
||||
}
|
||||
|
||||
func validateAppArmorProfileField(profile *core.AppArmorProfile, fldPath *field.Path) field.ErrorList {
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
switch profile.Type {
|
||||
case core.AppArmorProfileTypeLocalhost:
|
||||
if profile.LocalhostProfile == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when AppArmor type is Localhost"))
|
||||
} else {
|
||||
localhostProfile := strings.TrimSpace(*profile.LocalhostProfile)
|
||||
if localhostProfile != *profile.LocalhostProfile {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), *profile.LocalhostProfile, "must not be padded with whitespace"))
|
||||
} else if localhostProfile == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when AppArmor type is Localhost"))
|
||||
}
|
||||
}
|
||||
|
||||
case core.AppArmorProfileTypeRuntimeDefault, core.AppArmorProfileTypeUnconfined:
|
||||
if profile.LocalhostProfile != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), profile.LocalhostProfile, "can only be set when AppArmor type is Localhost"))
|
||||
}
|
||||
|
||||
case "":
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "type is required when appArmorProfile is set"))
|
||||
|
||||
default:
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), profile.Type,
|
||||
[]core.AppArmorProfileType{core.AppArmorProfileTypeLocalhost, core.AppArmorProfileTypeRuntimeDefault, core.AppArmorProfileTypeUnconfined}))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
||||
}
|
||||
|
||||
func ValidateAppArmorPodAnnotations(annotations map[string]string, spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for k, p := range annotations {
|
||||
@ -4799,6 +4842,7 @@ func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, sp
|
||||
|
||||
allErrs = append(allErrs, validateSeccompProfileField(securityContext.SeccompProfile, fldPath.Child("seccompProfile"))...)
|
||||
allErrs = append(allErrs, validateWindowsSecurityContextOptions(securityContext.WindowsOptions, fldPath.Child("windowsOptions"))...)
|
||||
allErrs = append(allErrs, validateAppArmorProfileField(securityContext.AppArmorProfile, fldPath.Child("appArmorProfile"))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
@ -7084,6 +7128,7 @@ func ValidateSecurityContext(sc *core.SecurityContext, fldPath *field.Path) fiel
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateWindowsSecurityContextOptions(sc.WindowsOptions, fldPath.Child("windowsOptions"))...)
|
||||
allErrs = append(allErrs, validateAppArmorProfileField(sc.AppArmorProfile, fldPath.Child("appArmorProfile"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
@ -10289,7 +10289,7 @@ func TestValidatePod(t *testing.T) {
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
"default AppArmor profile for a container": {
|
||||
"default AppArmor annotation for a container": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
@ -10299,7 +10299,7 @@ func TestValidatePod(t *testing.T) {
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
"default AppArmor profile for an init container": {
|
||||
"default AppArmor annotation for an init container": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
@ -10314,7 +10314,7 @@ func TestValidatePod(t *testing.T) {
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
"localhost AppArmor profile for a container": {
|
||||
"localhost AppArmor annotation for a container": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
@ -10324,6 +10324,107 @@ func TestValidatePod(t *testing.T) {
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
"runtime default AppArmor profile for a pod": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"runtime default AppArmor profile for a container": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
"unconfined AppArmor profile for a pod": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeUnconfined,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"unconfined AppArmor profile for a container": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeUnconfined,
|
||||
},
|
||||
},
|
||||
}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
"localhost AppArmor profile for a pod": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeLocalhost,
|
||||
LocalhostProfile: ptr.To("example-org/application-foo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"localhost AppArmor profile for a container field": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeLocalhost,
|
||||
LocalhostProfile: ptr.To("example-org/application-foo"),
|
||||
},
|
||||
},
|
||||
}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
"syntactically valid sysctls": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
@ -11880,6 +11981,143 @@ func TestValidatePod(t *testing.T) {
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
},
|
||||
"unsupported pod AppArmor profile type": {
|
||||
expectedError: `Unsupported value: "test"`,
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"unsupported container AppArmor profile type": {
|
||||
expectedError: `Unsupported value: "test"`,
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: "test",
|
||||
},
|
||||
},
|
||||
}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
"missing pod AppArmor profile type": {
|
||||
expectedError: "Required value: type is required when appArmorProfile is set",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"missing AppArmor localhost profile": {
|
||||
expectedError: "Required value: must be set when AppArmor type is Localhost",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeLocalhost,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"empty AppArmor localhost profile": {
|
||||
expectedError: "Required value: must be set when AppArmor type is Localhost",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeLocalhost,
|
||||
LocalhostProfile: ptr.To(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid AppArmor localhost profile type": {
|
||||
expectedError: `Invalid value: "foo-bar"`,
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeRuntimeDefault,
|
||||
LocalhostProfile: ptr.To("foo-bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid AppArmor localhost profile": {
|
||||
expectedError: `Invalid value: "foo-bar "`,
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
AppArmorProfile: &core.AppArmorProfile{
|
||||
Type: core.AppArmorProfileTypeLocalhost,
|
||||
LocalhostProfile: ptr.To("foo-bar "),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid extended resource name in container request": {
|
||||
expectedError: "must be a standard resource for containers",
|
||||
spec: core.Pod{
|
||||
@ -21579,6 +21817,12 @@ func TestValidateWindowsSecurityContext(t *testing.T) {
|
||||
expectError: true,
|
||||
errorMsg: "cannot be set for a windows pod",
|
||||
errorType: "FieldValueForbidden",
|
||||
}, {
|
||||
name: "pod with AppArmorProfile",
|
||||
sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{AppArmorProfile: &core.AppArmorProfile{Type: core.AppArmorProfileTypeRuntimeDefault}}}}},
|
||||
expectError: true,
|
||||
errorMsg: "cannot be set for a windows pod",
|
||||
errorType: "FieldValueForbidden",
|
||||
}, {
|
||||
name: "pod with WindowsOptions, no error",
|
||||
sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}},
|
||||
@ -21613,6 +21857,7 @@ func TestValidateOSFields(t *testing.T) {
|
||||
// - Add documentation to the os field in the api
|
||||
// - Add validation logic validateLinux, validateWindows functions to make sure the field is only set for eligible OSes
|
||||
osSpecificFields := sets.NewString(
|
||||
"Containers[*].SecurityContext.AppArmorProfile",
|
||||
"Containers[*].SecurityContext.AllowPrivilegeEscalation",
|
||||
"Containers[*].SecurityContext.Capabilities",
|
||||
"Containers[*].SecurityContext.Privileged",
|
||||
@ -21623,6 +21868,7 @@ func TestValidateOSFields(t *testing.T) {
|
||||
"Containers[*].SecurityContext.SELinuxOptions",
|
||||
"Containers[*].SecurityContext.SeccompProfile",
|
||||
"Containers[*].SecurityContext.WindowsOptions",
|
||||
"InitContainers[*].SecurityContext.AppArmorProfile",
|
||||
"InitContainers[*].SecurityContext.AllowPrivilegeEscalation",
|
||||
"InitContainers[*].SecurityContext.Capabilities",
|
||||
"InitContainers[*].SecurityContext.Privileged",
|
||||
@ -21633,6 +21879,7 @@ func TestValidateOSFields(t *testing.T) {
|
||||
"InitContainers[*].SecurityContext.SELinuxOptions",
|
||||
"InitContainers[*].SecurityContext.SeccompProfile",
|
||||
"InitContainers[*].SecurityContext.WindowsOptions",
|
||||
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AppArmorProfile",
|
||||
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AllowPrivilegeEscalation",
|
||||
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Capabilities",
|
||||
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Privileged",
|
||||
@ -21644,6 +21891,7 @@ func TestValidateOSFields(t *testing.T) {
|
||||
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SeccompProfile",
|
||||
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.WindowsOptions",
|
||||
"OS",
|
||||
"SecurityContext.AppArmorProfile",
|
||||
"SecurityContext.FSGroup",
|
||||
"SecurityContext.FSGroupChangePolicy",
|
||||
"SecurityContext.HostIPC",
|
||||
|
@ -55,11 +55,8 @@ const (
|
||||
SeccompLocalhostProfileNamePrefix = "localhost/"
|
||||
|
||||
// AppArmorBetaContainerAnnotationKeyPrefix is the prefix to an annotation key specifying a container's apparmor profile.
|
||||
// Deprecated: use a pod or container security context `appArmorProfile` field instead.
|
||||
AppArmorBetaContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/"
|
||||
// AppArmorBetaDefaultProfileAnnotationKey is the annotation key specifying the default AppArmor profile.
|
||||
AppArmorBetaDefaultProfileAnnotationKey = "apparmor.security.beta.kubernetes.io/defaultProfileName"
|
||||
// AppArmorBetaAllowedProfilesAnnotationKey is the annotation key specifying the allowed AppArmor profiles.
|
||||
AppArmorBetaAllowedProfilesAnnotationKey = "apparmor.security.beta.kubernetes.io/allowedProfileNames"
|
||||
|
||||
// AppArmorBetaProfileRuntimeDefault is the profile specifying the runtime default.
|
||||
AppArmorBetaProfileRuntimeDefault = "runtime/default"
|
||||
|
@ -3757,6 +3757,7 @@ type PodSpec struct {
|
||||
// - spec.hostPID
|
||||
// - spec.hostIPC
|
||||
// - spec.hostUsers
|
||||
// - spec.securityContext.appArmorProfile
|
||||
// - spec.securityContext.seLinuxOptions
|
||||
// - spec.securityContext.seccompProfile
|
||||
// - spec.securityContext.fsGroup
|
||||
@ -3766,6 +3767,7 @@ type PodSpec struct {
|
||||
// - spec.securityContext.runAsUser
|
||||
// - spec.securityContext.runAsGroup
|
||||
// - spec.securityContext.supplementalGroups
|
||||
// - spec.containers[*].securityContext.appArmorProfile
|
||||
// - spec.containers[*].securityContext.seLinuxOptions
|
||||
// - spec.containers[*].securityContext.seccompProfile
|
||||
// - spec.containers[*].securityContext.capabilities
|
||||
@ -4158,6 +4160,10 @@ type PodSecurityContext struct {
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
SeccompProfile *SeccompProfile `json:"seccompProfile,omitempty" protobuf:"bytes,10,opt,name=seccompProfile"`
|
||||
// appArmorProfile is the AppArmor options to use by the containers in this pod.
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
AppArmorProfile *AppArmorProfile `json:"appArmorProfile,omitempty"`
|
||||
}
|
||||
|
||||
// SeccompProfile defines a pod/container's seccomp profile settings.
|
||||
@ -4194,6 +4200,38 @@ const (
|
||||
SeccompProfileTypeLocalhost SeccompProfileType = "Localhost"
|
||||
)
|
||||
|
||||
// AppArmorProfile defines a pod or container's AppArmor settings.
|
||||
// +union
|
||||
type AppArmorProfile struct {
|
||||
// type indicates which kind of AppArmor profile will be applied.
|
||||
// Valid options are:
|
||||
// Localhost - a profile pre-loaded on the node.
|
||||
// RuntimeDefault - the container runtime's default profile.
|
||||
// Unconfined - no AppArmor enforcement.
|
||||
// +unionDiscriminator
|
||||
Type AppArmorProfileType `json:"type"`
|
||||
|
||||
// localhostProfile indicates a profile loaded on the node that should be used.
|
||||
// The profile must be preconfigured on the node to work.
|
||||
// Must match the loaded name of the profile.
|
||||
// Must be set if and only if type is "Localhost".
|
||||
// +optional
|
||||
LocalhostProfile *string `json:"localhostProfile,omitempty"`
|
||||
}
|
||||
|
||||
// +enum
|
||||
type AppArmorProfileType string
|
||||
|
||||
const (
|
||||
// AppArmorProfileTypeUnconfined indicates that no AppArmor profile should be enforced.
|
||||
AppArmorProfileTypeUnconfined AppArmorProfileType = "Unconfined"
|
||||
// AppArmorProfileTypeRuntimeDefault indicates that the container runtime's default AppArmor
|
||||
// profile should be used.
|
||||
AppArmorProfileTypeRuntimeDefault AppArmorProfileType = "RuntimeDefault"
|
||||
// AppArmorProfileTypeLocalhost indicates that a profile pre-loaded on the node should be used.
|
||||
AppArmorProfileTypeLocalhost AppArmorProfileType = "Localhost"
|
||||
)
|
||||
|
||||
// PodQOSClass defines the supported qos classes of Pods.
|
||||
// +enum
|
||||
type PodQOSClass string
|
||||
@ -7213,6 +7251,11 @@ type SecurityContext struct {
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
SeccompProfile *SeccompProfile `json:"seccompProfile,omitempty" protobuf:"bytes,11,opt,name=seccompProfile"`
|
||||
// appArmorProfile is the AppArmor options to use by this container. If set, this profile
|
||||
// overrides the pod's appArmorProfile.
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +optional
|
||||
AppArmorProfile *AppArmorProfile `json:"appArmorProfile,omitempty"`
|
||||
}
|
||||
|
||||
// +enum
|
||||
|
Loading…
Reference in New Issue
Block a user