Merge pull request #123435 from tallclair/apparmor-ga

AppArmor fields API
This commit is contained in:
Kubernetes Prow Robot 2024-03-06 15:35:14 -08:00 committed by GitHub
commit bd25605619
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
103 changed files with 4465 additions and 1427 deletions

View file

@ -5389,6 +5389,31 @@
},
"type": "object"
},
"io.k8s.api.core.v1.AppArmorProfile": {
"description": "AppArmorProfile defines a pod or container's AppArmor settings.",
"properties": {
"localhostProfile": {
"description": "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\".",
"type": "string"
},
"type": {
"description": "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.",
"type": "string"
}
},
"required": [
"type"
],
"type": "object",
"x-kubernetes-unions": [
{
"discriminator": "type",
"fields-to-discriminateBy": {
"localhostProfile": "LocalhostProfile"
}
}
]
},
"io.k8s.api.core.v1.AttachedVolume": {
"description": "AttachedVolume describes a volume attached to a node",
"properties": {
@ -9268,6 +9293,10 @@
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
"appArmorProfile": {
"$ref": "#/definitions/io.k8s.api.core.v1.AppArmorProfile",
"description": "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."
},
"fsGroup": {
"description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows.",
"format": "int64",
@ -9450,7 +9479,7 @@
},
"os": {
"$ref": "#/definitions/io.k8s.api.core.v1.PodOS",
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - 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 - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
},
"overhead": {
"additionalProperties": {
@ -10772,6 +10801,10 @@
"description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.",
"type": "boolean"
},
"appArmorProfile": {
"$ref": "#/definitions/io.k8s.api.core.v1.AppArmorProfile",
"description": "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."
},
"capabilities": {
"$ref": "#/definitions/io.k8s.api.core.v1.Capabilities",
"description": "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. Note that this field cannot be set when spec.os.name is windows."

View file

@ -263,6 +263,32 @@
},
"type": "object"
},
"io.k8s.api.core.v1.AppArmorProfile": {
"description": "AppArmorProfile defines a pod or container's AppArmor settings.",
"properties": {
"localhostProfile": {
"description": "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\".",
"type": "string"
},
"type": {
"default": "",
"description": "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.",
"type": "string"
}
},
"required": [
"type"
],
"type": "object",
"x-kubernetes-unions": [
{
"discriminator": "type",
"fields-to-discriminateBy": {
"localhostProfile": "LocalhostProfile"
}
}
]
},
"io.k8s.api.core.v1.AttachedVolume": {
"description": "AttachedVolume describes a volume attached to a node",
"properties": {
@ -5161,6 +5187,14 @@
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
"appArmorProfile": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.AppArmorProfile"
}
],
"description": "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."
},
"fsGroup": {
"description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows.",
"format": "int64",
@ -5399,7 +5433,7 @@
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodOS"
}
],
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - 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 - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
},
"overhead": {
"additionalProperties": {
@ -6999,6 +7033,14 @@
"description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.",
"type": "boolean"
},
"appArmorProfile": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.AppArmorProfile"
}
],
"description": "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."
},
"capabilities": {
"allOf": [
{

View file

@ -1391,6 +1391,32 @@
},
"type": "object"
},
"io.k8s.api.core.v1.AppArmorProfile": {
"description": "AppArmorProfile defines a pod or container's AppArmor settings.",
"properties": {
"localhostProfile": {
"description": "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\".",
"type": "string"
},
"type": {
"default": "",
"description": "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.",
"type": "string"
}
},
"required": [
"type"
],
"type": "object",
"x-kubernetes-unions": [
{
"discriminator": "type",
"fields-to-discriminateBy": {
"localhostProfile": "LocalhostProfile"
}
}
]
},
"io.k8s.api.core.v1.AzureDiskVolumeSource": {
"description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.",
"properties": {
@ -3578,6 +3604,14 @@
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
"appArmorProfile": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.AppArmorProfile"
}
],
"description": "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."
},
"fsGroup": {
"description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows.",
"format": "int64",
@ -3816,7 +3850,7 @@
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodOS"
}
],
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - 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 - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
},
"overhead": {
"additionalProperties": {
@ -4524,6 +4558,14 @@
"description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.",
"type": "boolean"
},
"appArmorProfile": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.AppArmorProfile"
}
],
"description": "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."
},
"capabilities": {
"allOf": [
{

View file

@ -695,6 +695,32 @@
},
"type": "object"
},
"io.k8s.api.core.v1.AppArmorProfile": {
"description": "AppArmorProfile defines a pod or container's AppArmor settings.",
"properties": {
"localhostProfile": {
"description": "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\".",
"type": "string"
},
"type": {
"default": "",
"description": "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.",
"type": "string"
}
},
"required": [
"type"
],
"type": "object",
"x-kubernetes-unions": [
{
"discriminator": "type",
"fields-to-discriminateBy": {
"localhostProfile": "LocalhostProfile"
}
}
]
},
"io.k8s.api.core.v1.AzureDiskVolumeSource": {
"description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.",
"properties": {
@ -2737,6 +2763,14 @@
"io.k8s.api.core.v1.PodSecurityContext": {
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
"properties": {
"appArmorProfile": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.AppArmorProfile"
}
],
"description": "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."
},
"fsGroup": {
"description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows.",
"format": "int64",
@ -2975,7 +3009,7 @@
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodOS"
}
],
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
"description": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - 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 - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup"
},
"overhead": {
"additionalProperties": {
@ -3683,6 +3717,14 @@
"description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.",
"type": "boolean"
},
"appArmorProfile": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.AppArmorProfile"
}
],
"description": "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."
},
"capabilities": {
"allOf": [
{

View file

@ -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"
"k8s.io/apimachinery/pkg/util/sets"
@ -624,13 +623,24 @@ func dropDisabledFields(
podSpec = &api.PodSpec{}
}
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorInUse(oldPodAnnotations) {
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorAnnotationsInUse(oldPodAnnotations) {
for k := range podAnnotations {
if strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) {
delete(podAnnotations, k)
}
}
}
if (!utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) || !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields)) && !appArmorFieldsInUse(oldPodSpec) {
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.
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport) && !hostUsersInUse(oldPodSpec) {
@ -1023,16 +1033,35 @@ func procMountInUse(podSpec *api.PodSpec) bool {
return inUse
}
// appArmorInUse returns true if the pod has apparmor related information
func appArmorInUse(podAnnotations map[string]string) bool {
// appArmorAnnotationsInUse returns true if the pod has apparmor annotations
func appArmorAnnotationsInUse(podAnnotations map[string]string) bool {
for k := range podAnnotations {
if strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
if strings.HasPrefix(k, api.DeprecatedAppArmorAnnotationKeyPrefix) {
return true
}
}
return false
}
// appArmorFieldsInUse returns true if the pod has apparmor fields set
func appArmorFieldsInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
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
// it has any init container with ContainerRestartPolicyAlways.
func restartableInitContainersInUse(podSpec *api.PodSpec) bool {

View file

@ -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,80 +706,104 @@ 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"}},
tests := []struct {
description string
hasAnnotations bool
hasFields bool
pod api.Pod
}{{
description: "with AppArmor Annotations",
hasAnnotations: true,
pod: api.Pod{
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}},
Spec: api.PodSpec{},
}
}
podWithoutAppArmor := func() *api.Pod {
return &api.Pod{
},
}, {
description: "with AppArmor Annotations & fields",
hasAnnotations: true,
hasFields: true,
pod: api.Pod{
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1", v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "foo": "default"}},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
},
},
}, {
description: "with pod AppArmor profile",
hasFields: 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: "with container AppArmor profile",
hasFields: 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",
pod: api.Pod{
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"a": "1"}},
Spec: api.PodSpec{},
}
}
podInfo := []struct {
description string
hasAppArmor bool
pod func() *api.Pod
}{
{
description: "has AppArmor",
hasAppArmor: true,
pod: podWithAppArmor,
},
{
description: "does not have AppArmor",
hasAppArmor: false,
pod: podWithoutAppArmor,
},
{
description: "is nil",
hasAppArmor: false,
pod: func() *api.Pod { return nil },
},
}
}}
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
}
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
for _, test := range tests {
for _, enabled := range []bool{true, false} {
for _, fieldsEnabled := range []bool{true, false} {
t.Run(fmt.Sprintf("%v/enabled=%v/fields=%v", test.description, enabled, fieldsEnabled), func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmor, enabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AppArmorFields, fieldsEnabled)()
DropDisabledPodFields(newPod, oldPod)
newPod := test.pod.DeepCopy()
// old pod should never be changed
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod()))
if hasAnnotations := appArmorAnnotationsInUse(newPod.Annotations); hasAnnotations != test.hasAnnotations {
t.Errorf("appArmorAnnotationsInUse does not match expectation: %t != %t", hasAnnotations, test.hasAnnotations)
}
if hasFields := appArmorFieldsInUse(&newPod.Spec); hasFields != test.hasFields {
t.Errorf("appArmorFieldsInUse does not match expectation: %t != %t", hasFields, test.hasFields)
}
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()))
}
DropDisabledPodFields(newPod, newPod)
require.Equal(t, &test.pod, newPod, "unchanged pod should never be mutated")
DropDisabledPodFields(newPod, nil)
if enabled && fieldsEnabled {
assert.Equal(t, &test.pod, newPod, "pod should not be mutated when both feature gates are enabled")
return
}
expectAnnotations := test.hasAnnotations && enabled
assert.Equal(t, expectAnnotations, appArmorAnnotationsInUse(newPod.Annotations), "AppArmor annotations expectation")
if expectAnnotations == test.hasAnnotations {
assert.Equal(t, test.pod.Annotations, newPod.Annotations, "annotations should not be mutated")
}
expectFields := test.hasFields && enabled && fieldsEnabled
assert.Equal(t, expectFields, appArmorFieldsInUse(&newPod.Spec), "AppArmor fields expectation")
if expectFields == test.hasFields {
assert.Equal(t, &test.pod.Spec, &newPod.Spec, "PodSpec should not be mutated")
}
})
}

View file

@ -52,6 +52,19 @@ const (
// Deprecated: set a pod or container security context `seccompProfile` of type "RuntimeDefault" instead.
DeprecatedSeccompProfileDockerDefault string = "docker/default"
// DeprecatedAppArmorAnnotationKeyPrefix is the prefix to an annotation key specifying a container's apparmor profile.
// Deprecated: use a pod or container security context `appArmorProfile` field instead.
DeprecatedAppArmorAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/"
// DeprecatedAppArmorAnnotationValueRuntimeDefault is the profile specifying the runtime default.
DeprecatedAppArmorAnnotationValueRuntimeDefault = "runtime/default"
// DeprecatedAppArmorAnnotationValueLocalhostPrefix is the prefix for specifying profiles loaded on the node.
DeprecatedAppArmorAnnotationValueLocalhostPrefix = "localhost/"
// DeprecatedAppArmorAnnotationValueUnconfined is the Unconfined AppArmor profile
DeprecatedAppArmorAnnotationValueUnconfined = "unconfined"
// PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized)
// in the Annotations of a Node.
PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"

View file

@ -3329,6 +3329,7 @@ type PodSpec struct {
// - spec.hostPID
// - spec.hostIPC
// - spec.hostUsers
// - spec.securityContext.appArmorProfile
// - spec.securityContext.seLinuxOptions
// - spec.securityContext.seccompProfile
// - spec.securityContext.fsGroup
@ -3338,6 +3339,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
@ -3602,6 +3604,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.
@ -3629,6 +3635,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.
// +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
}
// +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.
type PodQOSClass string
@ -6032,6 +6070,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

View file

@ -62,6 +62,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.AppArmorProfile)(nil), (*core.AppArmorProfile)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_AppArmorProfile_To_core_AppArmorProfile(a.(*v1.AppArmorProfile), b.(*core.AppArmorProfile), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*core.AppArmorProfile)(nil), (*v1.AppArmorProfile)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_core_AppArmorProfile_To_v1_AppArmorProfile(a.(*core.AppArmorProfile), b.(*v1.AppArmorProfile), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.AttachedVolume)(nil), (*core.AttachedVolume)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_AttachedVolume_To_core_AttachedVolume(a.(*v1.AttachedVolume), b.(*core.AttachedVolume), scope)
}); err != nil {
@ -2375,6 +2385,28 @@ func Convert_core_Affinity_To_v1_Affinity(in *core.Affinity, out *v1.Affinity, s
return autoConvert_core_Affinity_To_v1_Affinity(in, out, s)
}
func autoConvert_v1_AppArmorProfile_To_core_AppArmorProfile(in *v1.AppArmorProfile, out *core.AppArmorProfile, s conversion.Scope) error {
out.Type = core.AppArmorProfileType(in.Type)
out.LocalhostProfile = (*string)(unsafe.Pointer(in.LocalhostProfile))
return nil
}
// Convert_v1_AppArmorProfile_To_core_AppArmorProfile is an autogenerated conversion function.
func Convert_v1_AppArmorProfile_To_core_AppArmorProfile(in *v1.AppArmorProfile, out *core.AppArmorProfile, s conversion.Scope) error {
return autoConvert_v1_AppArmorProfile_To_core_AppArmorProfile(in, out, s)
}
func autoConvert_core_AppArmorProfile_To_v1_AppArmorProfile(in *core.AppArmorProfile, out *v1.AppArmorProfile, s conversion.Scope) error {
out.Type = v1.AppArmorProfileType(in.Type)
out.LocalhostProfile = (*string)(unsafe.Pointer(in.LocalhostProfile))
return nil
}
// Convert_core_AppArmorProfile_To_v1_AppArmorProfile is an autogenerated conversion function.
func Convert_core_AppArmorProfile_To_v1_AppArmorProfile(in *core.AppArmorProfile, out *v1.AppArmorProfile, s conversion.Scope) error {
return autoConvert_core_AppArmorProfile_To_v1_AppArmorProfile(in, out, s)
}
func autoConvert_v1_AttachedVolume_To_core_AttachedVolume(in *v1.AttachedVolume, out *core.AttachedVolume, s conversion.Scope) error {
out.Name = core.UniqueVolumeName(in.Name)
out.DevicePath = in.DevicePath
@ -6382,6 +6414,7 @@ func autoConvert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecu
out.Sysctls = *(*[]core.Sysctl)(unsafe.Pointer(&in.Sysctls))
out.FSGroupChangePolicy = (*core.PodFSGroupChangePolicy)(unsafe.Pointer(in.FSGroupChangePolicy))
out.SeccompProfile = (*core.SeccompProfile)(unsafe.Pointer(in.SeccompProfile))
out.AppArmorProfile = (*core.AppArmorProfile)(unsafe.Pointer(in.AppArmorProfile))
return nil
}
@ -6406,6 +6439,7 @@ func autoConvert_core_PodSecurityContext_To_v1_PodSecurityContext(in *core.PodSe
out.FSGroupChangePolicy = (*v1.PodFSGroupChangePolicy)(unsafe.Pointer(in.FSGroupChangePolicy))
out.Sysctls = *(*[]v1.Sysctl)(unsafe.Pointer(&in.Sysctls))
out.SeccompProfile = (*v1.SeccompProfile)(unsafe.Pointer(in.SeccompProfile))
out.AppArmorProfile = (*v1.AppArmorProfile)(unsafe.Pointer(in.AppArmorProfile))
return nil
}
@ -7759,6 +7793,7 @@ func autoConvert_v1_SecurityContext_To_core_SecurityContext(in *v1.SecurityConte
out.AllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.AllowPrivilegeEscalation))
out.ProcMount = (*core.ProcMountType)(unsafe.Pointer(in.ProcMount))
out.SeccompProfile = (*core.SeccompProfile)(unsafe.Pointer(in.SeccompProfile))
out.AppArmorProfile = (*core.AppArmorProfile)(unsafe.Pointer(in.AppArmorProfile))
return nil
}
@ -7779,6 +7814,7 @@ func autoConvert_core_SecurityContext_To_v1_SecurityContext(in *core.SecurityCon
out.AllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.AllowPrivilegeEscalation))
out.ProcMount = (*v1.ProcMountType)(unsafe.Pointer(in.ProcMount))
out.SeccompProfile = (*v1.SeccompProfile)(unsafe.Pointer(in.SeccompProfile))
out.AppArmorProfile = (*v1.AppArmorProfile)(unsafe.Pointer(in.AppArmorProfile))
return nil
}

View file

@ -204,7 +204,7 @@ func ValidatePodSpecificAnnotationUpdates(newPod, oldPod *core.Pod, fldPath *fie
if newVal, exists := newAnnotations[k]; exists && newVal == oldVal {
continue // No change.
}
if strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
if strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) {
allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update AppArmor annotations"))
}
if k == core.MirrorPodAnnotationKey {
@ -216,7 +216,7 @@ func ValidatePodSpecificAnnotationUpdates(newPod, oldPod *core.Pod, fldPath *fie
if _, ok := oldAnnotations[k]; ok {
continue // No change.
}
if strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
if strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) {
allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add AppArmor annotations"))
}
if k == core.MirrorPodAnnotationKey {
@ -4248,6 +4248,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"))
}
@ -4294,6 +4297,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"))
}
@ -4671,13 +4677,55 @@ 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"))
}
const maxLocalhostProfileLength = 4095 // PATH_MAX - 1
if len(*profile.LocalhostProfile) > maxLocalhostProfileLength {
allErrs = append(allErrs, field.TooLongMaxLength(fldPath.Child("localhostProfile"), *profile.LocalhostProfile, maxLocalhostProfileLength))
}
}
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 {
if !strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
if !strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) {
continue
}
containerName := strings.TrimPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix)
containerName := strings.TrimPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix)
if !podSpecHasContainer(spec, containerName) {
allErrs = append(allErrs, field.Invalid(fldPath.Key(k), containerName, "container not found"))
}
@ -4691,15 +4739,70 @@ func ValidateAppArmorPodAnnotations(annotations map[string]string, spec *core.Po
}
func ValidateAppArmorProfileFormat(profile string) error {
if profile == "" || profile == v1.AppArmorBetaProfileRuntimeDefault || profile == v1.AppArmorBetaProfileNameUnconfined {
if profile == "" || profile == v1.DeprecatedAppArmorBetaProfileRuntimeDefault || profile == v1.DeprecatedAppArmorBetaProfileNameUnconfined {
return nil
}
if !strings.HasPrefix(profile, v1.AppArmorBetaProfileNamePrefix) {
if !strings.HasPrefix(profile, v1.DeprecatedAppArmorBetaProfileNamePrefix) {
return fmt.Errorf("invalid AppArmor profile name: %q", profile)
}
return nil
}
// validateAppArmorAnnotationsAndFieldsMatchOnCreate validates that AppArmor fields and annotations are consistent.
func validateAppArmorAnnotationsAndFieldsMatchOnCreate(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList {
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
return nil
}
if podSpec.OS != nil && podSpec.OS.Name == core.Windows {
// Skip consistency check for windows pods.
return nil
}
allErrs := field.ErrorList{}
var podProfile *core.AppArmorProfile
if podSpec.SecurityContext != nil {
podProfile = podSpec.SecurityContext.AppArmorProfile
}
podshelper.VisitContainersWithPath(podSpec, specPath, func(c *core.Container, cFldPath *field.Path) bool {
containerProfile := podProfile
if c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil {
containerProfile = c.SecurityContext.AppArmorProfile
}
if containerProfile == nil {
return true
}
key := core.DeprecatedAppArmorAnnotationKeyPrefix + c.Name
if annotation, found := objectMeta.Annotations[key]; found {
apparmorPath := cFldPath.Child("securityContext").Child("appArmorProfile")
switch containerProfile.Type {
case core.AppArmorProfileTypeUnconfined:
if annotation != core.DeprecatedAppArmorAnnotationValueUnconfined {
allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match"))
}
case core.AppArmorProfileTypeRuntimeDefault:
if annotation != core.DeprecatedAppArmorAnnotationValueRuntimeDefault {
allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match"))
}
case core.AppArmorProfileTypeLocalhost:
if !strings.HasPrefix(annotation, core.DeprecatedAppArmorAnnotationValueLocalhostPrefix) {
allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match"))
} else if containerProfile.LocalhostProfile == nil || strings.TrimPrefix(annotation, core.DeprecatedAppArmorAnnotationValueLocalhostPrefix) != *containerProfile.LocalhostProfile {
allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("localhostProfile"), "apparmor profile in annotation and field must match"))
}
}
}
return true
})
return allErrs
}
func podSpecHasContainer(spec *core.PodSpec, containerName string) bool {
var hasContainer bool
podshelper.VisitContainersWithPath(spec, field.NewPath("spec"), func(c *core.Container, _ *field.Path) bool {
@ -4813,6 +4916,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
@ -4853,6 +4957,7 @@ func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared"))
}
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
allErrs = append(allErrs, validateAppArmorAnnotationsAndFieldsMatchOnCreate(pod.ObjectMeta, &pod.Spec, fldPath)...)
return allErrs
}
@ -5830,6 +5935,7 @@ func ValidatePodTemplateSpec(spec *core.PodTemplateSpec, fldPath *field.Path, op
allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"), opts)...)
allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, nil, fldPath.Child("spec"), opts)...)
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...)
allErrs = append(allErrs, validateAppArmorAnnotationsAndFieldsMatchOnCreate(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...)
if len(spec.Spec.EphemeralContainers) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "ephemeralContainers"), "ephemeral containers not allowed in pod template"))
@ -7098,6 +7204,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
}

View file

@ -10892,22 +10892,22 @@ 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",
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileRuntimeDefault,
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
},
},
Spec: validPodSpec(nil),
},
"default AppArmor profile for an init container": {
"default AppArmor annotation for an init container": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.AppArmorBetaProfileRuntimeDefault,
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
},
},
Spec: core.PodSpec{
@ -10917,16 +10917,158 @@ 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",
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileNamePrefix + "foo",
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo",
},
},
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,
},
},
"matching AppArmor fields and annotations": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
},
},
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("foo"),
},
},
}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSDefault,
},
},
"matching AppArmor pod field and annotations": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
},
},
Spec: core.PodSpec{
SecurityContext: &core.PodSecurityContext{
AppArmorProfile: &core.AppArmorProfile{
Type: core.AppArmorProfileTypeLocalhost,
LocalhostProfile: ptr.To("foo"),
},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSDefault,
},
},
"syntactically valid sysctls": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
@ -12444,9 +12586,9 @@ func TestValidatePod(t *testing.T) {
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileRuntimeDefault,
v1.AppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.AppArmorBetaProfileRuntimeDefault,
v1.AppArmorBetaContainerAnnotationKeyPrefix + "fake-ctr": v1.AppArmorBetaProfileRuntimeDefault,
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "fake-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
},
},
Spec: core.PodSpec{
@ -12464,7 +12606,7 @@ func TestValidatePod(t *testing.T) {
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": "bad-name",
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "bad-name",
},
},
Spec: validPodSpec(nil),
@ -12477,12 +12619,238 @@ func TestValidatePod(t *testing.T) {
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": "runtime/foo",
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "runtime/foo",
},
},
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 "),
},
},
},
},
},
"too long AppArmor localhost profile": {
expectedError: "Too long: may not be longer than 4095",
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(strings.Repeat("a", 4096)),
},
},
},
},
},
"mismatched AppArmor field and annotation types": {
expectedError: "Forbidden: apparmor type in annotation and field must match",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
},
},
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,
},
},
},
"mismatched AppArmor pod field and annotation types": {
expectedError: "Forbidden: apparmor type in annotation and field must match",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
},
},
Spec: core.PodSpec{
SecurityContext: &core.PodSecurityContext{
AppArmorProfile: &core.AppArmorProfile{
Type: core.AppArmorProfileTypeUnconfined,
},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSDefault,
},
},
},
"mismatched AppArmor localhost profiles": {
expectedError: "Forbidden: apparmor profile in annotation and field must match",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Annotations: map[string]string{
core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
},
},
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("bar"),
},
},
}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSDefault,
},
},
},
"invalid extended resource name in container request": {
expectedError: "must be a standard resource for containers",
spec: core.Pod{
@ -22182,6 +22550,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")}}}}},
@ -22216,6 +22590,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",
@ -22226,6 +22601,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",
@ -22236,6 +22612,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",
@ -22247,6 +22624,7 @@ func TestValidateOSFields(t *testing.T) {
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SeccompProfile",
"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.WindowsOptions",
"OS",
"SecurityContext.AppArmorProfile",
"SecurityContext.FSGroup",
"SecurityContext.FSGroupChangePolicy",
"SecurityContext.HostIPC",
@ -25431,11 +25809,11 @@ func TestValidateAppArmorProfileFormat(t *testing.T) {
expectValid bool
}{
{"", true},
{v1.AppArmorBetaProfileRuntimeDefault, true},
{v1.AppArmorBetaProfileNameUnconfined, true},
{v1.DeprecatedAppArmorBetaProfileRuntimeDefault, true},
{v1.DeprecatedAppArmorBetaProfileNameUnconfined, true},
{"baz", false}, // Missing local prefix.
{v1.AppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
{v1.AppArmorBetaProfileNamePrefix + "foo-bar", true},
{v1.DeprecatedAppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
{v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo-bar", true},
}
for _, test := range tests {

View file

@ -74,6 +74,27 @@ func (in *Affinity) DeepCopy() *Affinity {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppArmorProfile) DeepCopyInto(out *AppArmorProfile) {
*out = *in
if in.LocalhostProfile != nil {
in, out := &in.LocalhostProfile, &out.LocalhostProfile
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppArmorProfile.
func (in *AppArmorProfile) DeepCopy() *AppArmorProfile {
if in == nil {
return nil
}
out := new(AppArmorProfile)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AttachedVolume) DeepCopyInto(out *AttachedVolume) {
*out = *in
@ -4010,6 +4031,11 @@ func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
*out = new(SeccompProfile)
(*in).DeepCopyInto(*out)
}
if in.AppArmorProfile != nil {
in, out := &in.AppArmorProfile, &out.AppArmorProfile
*out = new(AppArmorProfile)
(*in).DeepCopyInto(*out)
}
return
}
@ -5378,6 +5404,11 @@ func (in *SecurityContext) DeepCopyInto(out *SecurityContext) {
*out = new(SeccompProfile)
(*in).DeepCopyInto(*out)
}
if in.AppArmorProfile != nil {
in, out := &in.AppArmorProfile, &out.AppArmorProfile
*out = new(AppArmorProfile)
(*in).DeepCopyInto(*out)
}
return
}

View file

@ -63,6 +63,10 @@ const (
// beta: v1.4
AppArmor featuregate.Feature = "AppArmor"
// owner: @tallclair
// beta: v1.30
AppArmorFields featuregate.Feature = "AppArmorFields"
// owner: @danwinship
// alpha: v1.27
// beta: v1.29
@ -984,6 +988,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
AppArmor: {Default: true, PreRelease: featuregate.Beta},
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
CloudDualStackNodeIPs: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.32
ClusterTrustBundle: {Default: false, PreRelease: featuregate.Alpha},

View file

@ -376,6 +376,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/api/coordination/v1beta1.LeaseSpec": schema_k8sio_api_coordination_v1beta1_LeaseSpec(ref),
"k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref),
"k8s.io/api/core/v1.Affinity": schema_k8sio_api_core_v1_Affinity(ref),
"k8s.io/api/core/v1.AppArmorProfile": schema_k8sio_api_core_v1_AppArmorProfile(ref),
"k8s.io/api/core/v1.AttachedVolume": schema_k8sio_api_core_v1_AttachedVolume(ref),
"k8s.io/api/core/v1.AvoidPods": schema_k8sio_api_core_v1_AvoidPods(ref),
"k8s.io/api/core/v1.AzureDiskVolumeSource": schema_k8sio_api_core_v1_AzureDiskVolumeSource(ref),
@ -18870,6 +18871,48 @@ func schema_k8sio_api_core_v1_Affinity(ref common.ReferenceCallback) common.Open
}
}
func schema_k8sio_api_core_v1_AppArmorProfile(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "AppArmorProfile defines a pod or container's AppArmor settings.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Description: "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.\n\nPossible enum values:\n - `\"Localhost\"` indicates that a profile pre-loaded on the node should be used.\n - `\"RuntimeDefault\"` indicates that the container runtime's default AppArmor profile should be used.\n - `\"Unconfined\"` indicates that no AppArmor profile should be enforced.",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"Localhost", "RuntimeDefault", "Unconfined"},
},
},
"localhostProfile": {
SchemaProps: spec.SchemaProps{
Description: "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\".",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"type"},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-unions": []interface{}{
map[string]interface{}{
"discriminator": "type",
"fields-to-discriminateBy": map[string]interface{}{
"localhostProfile": "LocalhostProfile",
},
},
},
},
},
},
}
}
func schema_k8sio_api_core_v1_AttachedVolume(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@ -26891,11 +26934,17 @@ func schema_k8sio_api_core_v1_PodSecurityContext(ref common.ReferenceCallback) c
Ref: ref("k8s.io/api/core/v1.SeccompProfile"),
},
},
"appArmorProfile": {
SchemaProps: spec.SchemaProps{
Description: "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.",
Ref: ref("k8s.io/api/core/v1.AppArmorProfile"),
},
},
},
},
},
Dependencies: []string{
"k8s.io/api/core/v1.SELinuxOptions", "k8s.io/api/core/v1.SeccompProfile", "k8s.io/api/core/v1.Sysctl", "k8s.io/api/core/v1.WindowsSecurityContextOptions"},
"k8s.io/api/core/v1.AppArmorProfile", "k8s.io/api/core/v1.SELinuxOptions", "k8s.io/api/core/v1.SeccompProfile", "k8s.io/api/core/v1.Sysctl", "k8s.io/api/core/v1.WindowsSecurityContextOptions"},
}
}
@ -27339,7 +27388,7 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA
},
"os": {
SchemaProps: spec.SchemaProps{
Description: "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup",
Description: "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - 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 - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup",
Ref: ref("k8s.io/api/core/v1.PodOS"),
},
},
@ -29768,11 +29817,17 @@ func schema_k8sio_api_core_v1_SecurityContext(ref common.ReferenceCallback) comm
Ref: ref("k8s.io/api/core/v1.SeccompProfile"),
},
},
"appArmorProfile": {
SchemaProps: spec.SchemaProps{
Description: "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.",
Ref: ref("k8s.io/api/core/v1.AppArmorProfile"),
},
},
},
},
},
Dependencies: []string{
"k8s.io/api/core/v1.Capabilities", "k8s.io/api/core/v1.SELinuxOptions", "k8s.io/api/core/v1.SeccompProfile", "k8s.io/api/core/v1.WindowsSecurityContextOptions"},
"k8s.io/api/core/v1.AppArmorProfile", "k8s.io/api/core/v1.Capabilities", "k8s.io/api/core/v1.SELinuxOptions", "k8s.io/api/core/v1.SeccompProfile", "k8s.io/api/core/v1.WindowsSecurityContextOptions"},
}
}

View file

@ -732,10 +732,6 @@ func (kl *Kubelet) defaultNodeStatusFuncs() []func(context.Context, *v1.Node) er
if kl.cloud != nil {
nodeAddressesFunc = kl.cloudResourceSyncManager.NodeAddresses
}
var validateHostFunc func() error
if kl.appArmorValidator != nil {
validateHostFunc = kl.appArmorValidator.ValidateHost
}
var setters []func(ctx context.Context, n *v1.Node) error
setters = append(setters,
nodestatus.NodeAddress(kl.nodeIPs, kl.nodeIPValidator, kl.hostname, kl.hostnameOverridden, kl.externalCloudProvider, kl.cloud, nodeAddressesFunc),
@ -754,7 +750,7 @@ func (kl *Kubelet) defaultNodeStatusFuncs() []func(context.Context, *v1.Node) er
nodestatus.DiskPressureCondition(kl.clock.Now, kl.evictionManager.IsUnderDiskPressure, kl.recordNodeStatusEvent),
nodestatus.PIDPressureCondition(kl.clock.Now, kl.evictionManager.IsUnderPIDPressure, kl.recordNodeStatusEvent),
nodestatus.ReadyCondition(kl.clock.Now, kl.runtimeState.runtimeErrors, kl.runtimeState.networkErrors, kl.runtimeState.storageErrors,
validateHostFunc, kl.containerManager.Status, kl.shutdownManager.ShutdownStatus, kl.recordNodeStatusEvent, kl.supportLocalStorageCapacityIsolation()),
kl.containerManager.Status, kl.shutdownManager.ShutdownStatus, kl.recordNodeStatusEvent, kl.supportLocalStorageCapacityIsolation()),
nodestatus.VolumesInUse(kl.volumeManager.ReconcilerStatesHasBeenSynced, kl.volumeManager.GetVolumesInUse),
// TODO(mtaufen): I decided not to move this setter for now, since all it does is send an event
// and record state back to the Kubelet runtime object. In the future, I'd like to isolate

View file

@ -18,6 +18,7 @@ package kuberuntime
import (
"context"
"errors"
"fmt"
"path/filepath"
"strconv"
@ -28,6 +29,7 @@ import (
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
"k8s.io/klog/v2"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/security/apparmor"
)
type podsByID []*kubecontainer.Pod
@ -285,3 +287,35 @@ func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]str
ProfileType: runtimeapi.SecurityProfile_Unconfined,
}, nil
}
func getAppArmorProfile(pod *v1.Pod, container *v1.Container) (*runtimeapi.SecurityProfile, error) {
profile := apparmor.GetProfile(pod, container)
if profile == nil {
return nil, nil
}
switch profile.Type {
case v1.AppArmorProfileTypeRuntimeDefault:
return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
}, nil
case v1.AppArmorProfileTypeUnconfined:
return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined,
}, nil
case v1.AppArmorProfileTypeLocalhost:
if profile.LocalhostProfile == nil {
return nil, errors.New("missing localhost apparmor profile name")
}
return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Localhost,
LocalhostRef: *profile.LocalhostProfile,
}, nil
default:
// Shouldn't happen.
return nil, fmt.Errorf("unknown apparmor profile type: %q", profile.Type)
}
}

View file

@ -31,6 +31,7 @@ import (
runtimetesting "k8s.io/cri-api/pkg/apis/testing"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/utils/ptr"
)
type podStatusProviderFunc func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error)
@ -363,3 +364,75 @@ func TestToKubeContainerState(t *testing.T) {
})
}
}
func TestGetAppArmorProfile(t *testing.T) {
tests := []struct {
name string
podProfile *v1.AppArmorProfile
expectedProfile *runtimeapi.SecurityProfile
expectError bool
}{{
name: "no appArmor",
expectedProfile: nil,
}, {
name: "runtime default",
podProfile: &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeRuntimeDefault},
expectedProfile: &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
},
}, {
name: "unconfined",
podProfile: &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeUnconfined},
expectedProfile: &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined,
},
}, {
name: "localhost",
podProfile: &v1.AppArmorProfile{
Type: v1.AppArmorProfileTypeLocalhost,
LocalhostProfile: ptr.To("test"),
},
expectedProfile: &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Localhost,
LocalhostRef: "test",
},
}, {
name: "invalid localhost",
podProfile: &v1.AppArmorProfile{
Type: v1.AppArmorProfileTypeLocalhost,
},
expectError: true,
}, {
name: "invalid type",
podProfile: &v1.AppArmorProfile{
Type: "foo",
},
expectError: true,
}}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
},
Spec: v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
AppArmorProfile: test.podProfile,
},
Containers: []v1.Container{{Name: "foo"}},
},
}
actual, err := getAppArmorProfile(&pod, &pod.Spec.Containers[0])
if test.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.expectedProfile, actual)
})
}
}

View file

@ -20,7 +20,6 @@ import (
v1 "k8s.io/api/core/v1"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
runtimeutil "k8s.io/kubernetes/pkg/kubelet/kuberuntime/util"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/securitycontext"
)
@ -42,7 +41,10 @@ func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Po
}
// set ApparmorProfile.
synthesized.ApparmorProfile = apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name)
synthesized.Apparmor, err = getAppArmorProfile(pod, container)
if err != nil {
return nil, err
}
// set RunAsUser.
if synthesized.RunAsUser == nil {

View file

@ -486,7 +486,6 @@ func ReadyCondition(
runtimeErrorsFunc func() error, // typically Kubelet.runtimeState.runtimeErrors
networkErrorsFunc func() error, // typically Kubelet.runtimeState.networkErrors
storageErrorsFunc func() error, // typically Kubelet.runtimeState.storageErrors
appArmorValidateHostFunc func() error, // typically Kubelet.appArmorValidator.ValidateHost, might be nil depending on whether there was an appArmorValidator
cmStatusFunc func() cm.Status, // typically Kubelet.containerManager.Status
nodeShutdownManagerErrorsFunc func() error, // typically kubelet.shutdownManager.errors.
recordEventFunc func(eventType, event string), // typically Kubelet.recordNodeStatusEvent
@ -527,13 +526,6 @@ func ReadyCondition(
LastHeartbeatTime: currentTime,
}
}
// Append AppArmor status if it's enabled.
// TODO(tallclair): This is a temporary message until node feature reporting is added.
if appArmorValidateHostFunc != nil && newNodeReadyCondition.Status == v1.ConditionTrue {
if err := appArmorValidateHostFunc(); err == nil {
newNodeReadyCondition.Message = fmt.Sprintf("%s. AppArmor enabled", newNodeReadyCondition.Message)
}
}
// Record any soft requirements that were not met in the container manager.
status := cmStatusFunc()

View file

@ -1509,7 +1509,6 @@ func TestReadyCondition(t *testing.T) {
runtimeErrors error
networkErrors error
storageErrors error
appArmorValidateHostFunc func() error
cmStatus cm.Status
nodeShutdownManagerErrors error
expectConditions []v1.NodeCondition
@ -1524,19 +1523,6 @@ func TestReadyCondition(t *testing.T) {
// the reason for this is unclear, so we may want to actually send an event, and change these test cases
// to ensure an event is sent.
},
{
desc: "new, ready: apparmor validator passed",
node: withCapacity.DeepCopy(),
appArmorValidateHostFunc: func() error { return nil },
expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status. AppArmor enabled", now, now)},
},
{
desc: "new, ready: apparmor validator failed",
node: withCapacity.DeepCopy(),
appArmorValidateHostFunc: func() error { return fmt.Errorf("foo") },
// absence of an additional message is understood to mean that AppArmor is disabled
expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)},
},
{
desc: "new, ready: soft requirement warning",
node: withCapacity.DeepCopy(),
@ -1655,7 +1641,7 @@ func TestReadyCondition(t *testing.T) {
})
}
// construct setter
setter := ReadyCondition(nowFunc, runtimeErrorsFunc, networkErrorsFunc, storageErrorsFunc, tc.appArmorValidateHostFunc, cmStatusFunc, nodeShutdownErrorsFunc, recordEventFunc, !tc.disableLocalStorageCapacityIsolation)
setter := ReadyCondition(nowFunc, runtimeErrorsFunc, networkErrorsFunc, storageErrorsFunc, cmStatusFunc, nodeShutdownErrorsFunc, recordEventFunc, !tc.disableLocalStorageCapacityIsolation)
// call setter on node
if err := setter(ctx, tc.node); err != nil {
t.Fatalf("unexpected error: %v", err)

View file

@ -27,6 +27,7 @@ import (
"time"
apiv1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
@ -91,6 +92,7 @@ func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
applySchedulingGatedCondition(pod)
mutatePodAffinity(pod)
applyAppArmorVersionSkew(pod)
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
@ -758,3 +760,117 @@ func applySchedulingGatedCondition(pod *api.Pod) {
Message: "Scheduling is blocked due to non-empty scheduling gates",
})
}
// applyAppArmorVersionSkew implements the version skew behavior described in:
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/24-apparmor#version-skew-strategy
func applyAppArmorVersionSkew(pod *api.Pod) {
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
return
}
if pod.Spec.OS != nil && pod.Spec.OS.Name == api.Windows {
return
}
var podProfile *api.AppArmorProfile
if pod.Spec.SecurityContext != nil {
podProfile = pod.Spec.SecurityContext.AppArmorProfile
}
// Handle the containers of the pod
podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(),
func(ctr *api.Container, _ podutil.ContainerType) bool {
// get possible annotation and field
key := api.DeprecatedAppArmorAnnotationKeyPrefix + ctr.Name
annotation, hasAnnotation := pod.Annotations[key]
var containerProfile *api.AppArmorProfile
if ctr.SecurityContext != nil {
containerProfile = ctr.SecurityContext.AppArmorProfile
}
// sync field and annotation
if !hasAnnotation {
newAnnotation := ""
if containerProfile != nil {
newAnnotation = appArmorAnnotationForField(containerProfile)
} else if podProfile != nil {
newAnnotation = appArmorAnnotationForField(podProfile)
}
if newAnnotation != "" {
if pod.Annotations == nil {
pod.Annotations = map[string]string{}
}
pod.Annotations[key] = newAnnotation
}
} else if containerProfile == nil {
newField := apparmorFieldForAnnotation(annotation)
if errs := corevalidation.ValidateAppArmorProfileField(newField, &field.Path{}); len(errs) > 0 {
// Skip copying invalid value.
newField = nil
}
// Only copy the annotation to the field if it is different from the pod-level profile.
if newField != nil && !apiequality.Semantic.DeepEqual(newField, podProfile) {
if ctr.SecurityContext == nil {
ctr.SecurityContext = &api.SecurityContext{}
}
ctr.SecurityContext.AppArmorProfile = newField
}
}
return true
})
}
// appArmorFieldForAnnotation takes a pod apparmor profile field and returns the
// converted annotation value
func appArmorAnnotationForField(field *api.AppArmorProfile) string {
// If only apparmor fields are specified, add the corresponding annotations.
// This ensures that the fields are enforced even if the node version
// trails the API version
switch field.Type {
case api.AppArmorProfileTypeUnconfined:
return api.DeprecatedAppArmorAnnotationValueUnconfined
case api.AppArmorProfileTypeRuntimeDefault:
return api.DeprecatedAppArmorAnnotationValueRuntimeDefault
case api.AppArmorProfileTypeLocalhost:
if field.LocalhostProfile != nil {
return api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + *field.LocalhostProfile
}
}
// we can only reach this code path if the LocalhostProfile is nil but the
// provided field type is AppArmorProfileTypeLocalhost or if an unrecognized
// type is specified
return ""
}
// apparmorFieldForAnnotation takes a pod annotation and returns the converted
// apparmor profile field.
func apparmorFieldForAnnotation(annotation string) *api.AppArmorProfile {
if annotation == api.DeprecatedAppArmorAnnotationValueUnconfined {
return &api.AppArmorProfile{Type: api.AppArmorProfileTypeUnconfined}
}
if annotation == api.DeprecatedAppArmorAnnotationValueRuntimeDefault {
return &api.AppArmorProfile{Type: api.AppArmorProfileTypeRuntimeDefault}
}
if strings.HasPrefix(annotation, api.DeprecatedAppArmorAnnotationValueLocalhostPrefix) {
localhostProfile := strings.TrimPrefix(annotation, api.DeprecatedAppArmorAnnotationValueLocalhostPrefix)
if localhostProfile != "" {
return &api.AppArmorProfile{
Type: api.AppArmorProfileTypeLocalhost,
LocalhostProfile: &localhostProfile,
}
}
}
// we can only reach this code path if the localhostProfile name has a zero
// length or if the annotation has an unrecognized value
return nil
}

View file

@ -22,10 +22,12 @@ import (
"net/http"
"net/url"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
@ -2106,3 +2108,540 @@ func TestPodLifecycleSleepActionEnablement(t *testing.T) {
})
}
}
func TestApplyAppArmorVersionSkew(t *testing.T) {
testProfile := "test"
tests := []struct {
description string
pod *api.Pod
validation func(*testing.T, *api.Pod)
}{{
description: "Security context nil",
pod: &api.Pod{
Spec: api.PodSpec{
InitContainers: []api.Container{{Name: "init"}},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Empty(t, pod.Annotations)
assert.Nil(t, pod.Spec.SecurityContext)
},
}, {
description: "Security context not nil",
pod: &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{},
InitContainers: []api.Container{{Name: "init"}},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Empty(t, pod.Annotations)
assert.Nil(t, pod.Spec.SecurityContext.AppArmorProfile)
},
}, {
description: "Pod field unconfined and no annotation present",
pod: &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeUnconfined,
},
},
InitContainers: []api.Container{{Name: "init"}},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined,
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
}, pod.Annotations)
},
}, {
description: "Pod field default and no annotation present",
pod: &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
InitContainers: []api.Container{{Name: "init"}},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
},
}, {
description: "Pod field localhost and no annotation present",
pod: &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeLocalhost,
LocalhostProfile: &testProfile,
},
},
InitContainers: []api.Container{{Name: "init"}},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
}, pod.Annotations)
},
}, {
description: "Pod field localhost but profile is nil",
pod: &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeLocalhost,
},
},
InitContainers: []api.Container{{Name: "init"}},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Len(t, pod.Annotations, 0)
},
}, {
description: "Container security context not nil",
pod: &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{{
Name: "ctr",
SecurityContext: &api.SecurityContext{},
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Len(t, pod.Annotations, 0)
},
}, {
description: "Container field RuntimeDefault and no annotation present",
pod: &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{{
Name: "ctr",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
assert.Nil(t, pod.Spec.SecurityContext)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
},
}, {
description: "Container field localhost and no annotation present",
pod: &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{{
Name: "ctr",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeLocalhost,
LocalhostProfile: &testProfile,
},
},
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
}, pod.Annotations)
assert.Nil(t, pod.Spec.SecurityContext)
assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
},
}, {
description: "Container overrides pod profile",
pod: &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
Containers: []api.Container{{
Name: "ctr",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeUnconfined,
},
},
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
},
}, {
description: "Multiple containers with fields (container)",
pod: &api.Pod{
Spec: api.PodSpec{
InitContainers: []api.Container{{
Name: "init",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeLocalhost,
LocalhostProfile: &testProfile,
},
},
}},
Containers: []api.Container{{
Name: "a",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeUnconfined,
},
},
}, {
Name: "b",
}, {
Name: "c",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
api.DeprecatedAppArmorAnnotationKeyPrefix + "a": api.DeprecatedAppArmorAnnotationValueUnconfined,
api.DeprecatedAppArmorAnnotationKeyPrefix + "c": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
assert.Nil(t, pod.Spec.SecurityContext)
assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
assert.Nil(t, pod.Spec.Containers[1].SecurityContext)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[2].SecurityContext.AppArmorProfile.Type)
},
}, {
description: "Annotation 'unconfined' and no fields present",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
},
},
Spec: api.PodSpec{
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
assert.Nil(t, pod.Spec.SecurityContext)
},
}, {
description: "Annotation for non-existent container",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "foo-bar": api.DeprecatedAppArmorAnnotationValueUnconfined,
},
},
Spec: api.PodSpec{
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "foo-bar": api.DeprecatedAppArmorAnnotationValueUnconfined,
}, pod.Annotations)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
assert.Nil(t, pod.Spec.SecurityContext)
},
}, {
description: "Annotation 'runtime/default' and no fields present",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
},
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeUnconfined,
},
},
Containers: []api.Container{{
Name: "ctr",
SecurityContext: &api.SecurityContext{},
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.SecurityContext.AppArmorProfile.Type)
},
}, {
description: "Multiple containers by annotations",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined,
api.DeprecatedAppArmorAnnotationKeyPrefix + "a": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
api.DeprecatedAppArmorAnnotationKeyPrefix + "c": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
},
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
InitContainers: []api.Container{{Name: "init"}},
Containers: []api.Container{
{Name: "a"},
{Name: "b"},
{Name: "c"},
},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined,
api.DeprecatedAppArmorAnnotationKeyPrefix + "a": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
api.DeprecatedAppArmorAnnotationKeyPrefix + "b": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
api.DeprecatedAppArmorAnnotationKeyPrefix + "c": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type)
assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
assert.Equal(t, testProfile, *pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
assert.Nil(t, pod.Spec.Containers[1].SecurityContext)
assert.Nil(t, pod.Spec.Containers[2].SecurityContext)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
},
}, {
description: "Conflicting field and annotations",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
},
},
Spec: api.PodSpec{
Containers: []api.Container{{
Name: "ctr",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
assert.Nil(t, pod.Spec.SecurityContext)
},
}, {
description: "Pod field and matching annotations",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
},
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
Containers: []api.Container{{
Name: "ctr",
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
// Annotation shouldn't be synced to container security context
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
},
}, {
description: "Annotation overrides pod field",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
},
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
Containers: []api.Container{{
Name: "ctr",
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
},
}, {
description: "Mixed annotations and fields",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-annot": api.DeprecatedAppArmorAnnotationValueUnconfined,
},
},
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeRuntimeDefault,
},
},
Containers: []api.Container{{
Name: "unconf-annot",
}, {
Name: "unconf-field",
SecurityContext: &api.SecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: api.AppArmorProfileTypeUnconfined,
},
},
}, {
Name: "default-pod",
}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-annot": api.DeprecatedAppArmorAnnotationValueUnconfined,
api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-field": api.DeprecatedAppArmorAnnotationValueUnconfined,
api.DeprecatedAppArmorAnnotationKeyPrefix + "default-pod": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[1].SecurityContext.AppArmorProfile.Type)
assert.Nil(t, pod.Spec.Containers[2].SecurityContext)
},
}, {
description: "Invalid annotation value",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": "not-a-real-type",
},
},
Spec: api.PodSpec{
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": "not-a-real-type",
}, pod.Annotations)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
assert.Nil(t, pod.Spec.SecurityContext)
},
}, {
description: "Invalid localhost annotation",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + strings.Repeat("a", 4096),
},
},
Spec: api.PodSpec{
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Contains(t, pod.Annotations, api.DeprecatedAppArmorAnnotationKeyPrefix+"ctr")
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
assert.Nil(t, pod.Spec.SecurityContext)
},
}, {
description: "Invalid field type",
pod: &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
AppArmorProfile: &api.AppArmorProfile{
Type: "invalid-type",
},
},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Empty(t, pod.Annotations)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
},
}, {
description: "Ignore annotations on windows",
pod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
},
},
Spec: api.PodSpec{
OS: &api.PodOS{Name: api.Windows},
Containers: []api.Container{{Name: "ctr"}},
},
},
validation: func(t *testing.T, pod *api.Pod) {
assert.Equal(t, map[string]string{
api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
}, pod.Annotations)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
},
}}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
applyAppArmorVersionSkew(test.pod)
test.validation(t, test.pod)
})
}
}

View file

@ -19,26 +19,87 @@ package apparmor
import (
"strings"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/features"
)
// Checks whether app armor is required for pod to be run.
// Checks whether app armor is required for the pod to run. AppArmor is considered required if any
// non-unconfined profiles are specified.
func isRequired(pod *v1.Pod) bool {
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.AppArmorProfile != nil &&
pod.Spec.SecurityContext.AppArmorProfile.Type != v1.AppArmorProfileTypeUnconfined {
return true
}
inUse := !podutil.VisitContainers(&pod.Spec, podutil.AllContainers, func(c *v1.Container, _ podutil.ContainerType) bool {
if c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil &&
c.SecurityContext.AppArmorProfile.Type != v1.AppArmorProfileTypeUnconfined {
return false // is in use; short-circuit
}
return true
})
if inUse {
return true
}
for key, value := range pod.Annotations {
if strings.HasPrefix(key, v1.AppArmorBetaContainerAnnotationKeyPrefix) {
return value != v1.AppArmorBetaProfileNameUnconfined
if strings.HasPrefix(key, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) {
return value != v1.DeprecatedAppArmorBetaProfileNameUnconfined
}
}
return false
}
// GetProfileName returns the name of the profile to use with the container.
func GetProfileName(pod *v1.Pod, containerName string) string {
return GetProfileNameFromPodAnnotations(pod.Annotations, containerName)
func GetProfile(pod *v1.Pod, container *v1.Container) *v1.AppArmorProfile {
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
return getProfileFromPodAnnotations(pod.Annotations, container.Name)
}
if container.SecurityContext != nil && container.SecurityContext.AppArmorProfile != nil {
return container.SecurityContext.AppArmorProfile
}
// Static pods may not have had annotations synced to fields, so fallback to annotations before
// the pod profile.
if profile := getProfileFromPodAnnotations(pod.Annotations, container.Name); profile != nil {
return profile
}
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.AppArmorProfile != nil {
return pod.Spec.SecurityContext.AppArmorProfile
}
return nil
}
// GetProfileNameFromPodAnnotations gets the name of the profile to use with container from
// pod annotations
func GetProfileNameFromPodAnnotations(annotations map[string]string, containerName string) string {
return annotations[v1.AppArmorBetaContainerAnnotationKeyPrefix+containerName]
// getProfileFromPodAnnotations gets the AppArmor profile to use with container from
// (deprecated) pod annotations.
func getProfileFromPodAnnotations(annotations map[string]string, containerName string) *v1.AppArmorProfile {
val, ok := annotations[v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix+containerName]
if !ok {
return nil
}
switch {
case val == v1.DeprecatedAppArmorBetaProfileRuntimeDefault:
return &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeRuntimeDefault}
case val == v1.DeprecatedAppArmorBetaProfileNameUnconfined:
return &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeUnconfined}
case strings.HasPrefix(val, v1.DeprecatedAppArmorBetaProfileNamePrefix):
// Note: an invalid empty localhost profile will be rejected by kubelet admission.
profileName := strings.TrimPrefix(val, v1.DeprecatedAppArmorBetaProfileNamePrefix)
return &v1.AppArmorProfile{
Type: v1.AppArmorProfileTypeLocalhost,
LocalhostProfile: &profileName,
}
default:
// Invalid annotation.
return nil
}
}

View file

@ -0,0 +1,124 @@
/*
Copyright 2024 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 apparmor
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)
func TestGetProfile(t *testing.T) {
runtimeDefault := &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeRuntimeDefault}
unconfined := &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeUnconfined}
localhost := &v1.AppArmorProfile{
Type: v1.AppArmorProfileTypeLocalhost,
LocalhostProfile: ptr.To("test"),
}
tests := []struct {
name string
annotationProfile string
containerProfile *v1.AppArmorProfile
podProfile *v1.AppArmorProfile
expectedProfile *v1.AppArmorProfile
}{{
name: "no appArmor",
expectedProfile: nil,
}, {
name: "pod profile",
podProfile: runtimeDefault,
expectedProfile: runtimeDefault,
}, {
name: "container profile",
containerProfile: unconfined,
expectedProfile: unconfined,
}, {
name: "annotation profile",
annotationProfile: v1.DeprecatedAppArmorBetaProfileNamePrefix + "test",
expectedProfile: localhost,
}, {
name: "invalid annotation",
annotationProfile: "invalid",
expectedProfile: nil,
}, {
name: "invalid annotation with pod field",
annotationProfile: "invalid",
podProfile: runtimeDefault,
expectedProfile: runtimeDefault,
}, {
name: "container field before annotation",
annotationProfile: v1.DeprecatedAppArmorBetaProfileNameUnconfined,
containerProfile: runtimeDefault,
expectedProfile: runtimeDefault,
}, {
name: "container field before pod field",
containerProfile: runtimeDefault,
podProfile: unconfined,
expectedProfile: runtimeDefault,
}, {
name: "annotation before pod field",
annotationProfile: v1.DeprecatedAppArmorBetaProfileNameUnconfined,
podProfile: runtimeDefault,
expectedProfile: unconfined,
}, {
name: "all profiles",
annotationProfile: v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
containerProfile: localhost,
podProfile: unconfined,
expectedProfile: localhost,
}}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
container := v1.Container{
Name: "foo",
}
if test.containerProfile != nil {
container.SecurityContext = &v1.SecurityContext{
AppArmorProfile: test.containerProfile.DeepCopy(),
}
}
pod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Annotations: map[string]string{
"unrelated": "baz",
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "other": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{container},
},
}
if test.annotationProfile != "" {
pod.Annotations[v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix+container.Name] = test.annotationProfile
}
if test.podProfile != nil {
pod.Spec.SecurityContext = &v1.PodSecurityContext{
AppArmorProfile: test.podProfile.DeepCopy(),
}
}
actual := GetProfile(&pod, &container)
assert.Equal(t, test.expectedProfile, actual)
})
}
}

View file

@ -25,7 +25,6 @@ import (
v1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
)
@ -62,15 +61,15 @@ func (v *validator) Validate(pod *v1.Pod) error {
var retErr error
podutil.VisitContainers(&pod.Spec, podutil.AllContainers, func(container *v1.Container, containerType podutil.ContainerType) bool {
profile := GetProfileName(pod, container.Name)
retErr = validation.ValidateAppArmorProfileFormat(profile)
if retErr != nil {
return false
profile := GetProfile(pod, container)
if profile == nil {
return true
}
// TODO(#64841): This would ideally be part of validation.ValidateAppArmorProfileFormat, but
// that is called for API validation, and this is tightening validation.
if strings.HasPrefix(profile, v1.AppArmorBetaProfileNamePrefix) {
if strings.TrimSpace(strings.TrimPrefix(profile, v1.AppArmorBetaProfileNamePrefix)) == "" {
if profile.Type == v1.AppArmorProfileTypeLocalhost {
if profile.LocalhostProfile == nil || strings.TrimSpace(*profile.LocalhostProfile) == "" {
retErr = fmt.Errorf("invalid empty AppArmor profile name: %q", profile)
return false
}

View file

@ -38,8 +38,8 @@ func TestValidateBadHost(t *testing.T) {
expectValid bool
}{
{"", true},
{v1.AppArmorBetaProfileRuntimeDefault, false},
{v1.AppArmorBetaProfileNamePrefix + "docker-default", false},
{v1.DeprecatedAppArmorBetaProfileRuntimeDefault, false},
{v1.DeprecatedAppArmorBetaProfileNamePrefix + "docker-default", false},
}
for _, test := range tests {
@ -60,13 +60,12 @@ func TestValidateValidHost(t *testing.T) {
expectValid bool
}{
{"", true},
{v1.AppArmorBetaProfileRuntimeDefault, true},
{v1.AppArmorBetaProfileNamePrefix + "docker-default", true},
{v1.AppArmorBetaProfileNamePrefix + "foo-container", true},
{v1.AppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
{"docker-default", false},
{v1.AppArmorBetaProfileNamePrefix + "", false}, // Empty profile explicitly forbidden.
{v1.AppArmorBetaProfileNamePrefix + " ", false},
{v1.DeprecatedAppArmorBetaProfileRuntimeDefault, true},
{v1.DeprecatedAppArmorBetaProfileNamePrefix + "docker-default", true},
{v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo-container", true},
{v1.DeprecatedAppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
{v1.DeprecatedAppArmorBetaProfileNamePrefix + "", false}, // Empty profile explicitly forbidden.
{v1.DeprecatedAppArmorBetaProfileNamePrefix + " ", false},
}
for _, test := range tests {
@ -82,9 +81,9 @@ func TestValidateValidHost(t *testing.T) {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "init": v1.AppArmorBetaProfileNamePrefix + "foo-container",
v1.AppArmorBetaContainerAnnotationKeyPrefix + "test1": v1.AppArmorBetaProfileRuntimeDefault,
v1.AppArmorBetaContainerAnnotationKeyPrefix + "test2": v1.AppArmorBetaProfileNamePrefix + "docker-default",
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init": v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo-container",
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "test1": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "test2": v1.DeprecatedAppArmorBetaProfileNamePrefix + "docker-default",
},
},
Spec: v1.PodSpec{
@ -103,7 +102,7 @@ func TestValidateValidHost(t *testing.T) {
func getPodWithProfile(profile string) *v1.Pod {
annotations := map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "test": profile,
v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "test": profile,
}
if profile == "" {
annotations = map[string]string{

View file

@ -54,21 +54,18 @@ const (
// SeccompLocalhostProfileNamePrefix is the prefix for specifying profiles loaded from the node's disk.
SeccompLocalhostProfileNamePrefix = "localhost/"
// AppArmorBetaContainerAnnotationKeyPrefix is the prefix to an annotation key specifying a container's apparmor profile.
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"
// DeprecatedAppArmorBetaContainerAnnotationKeyPrefix is the prefix to an annotation key specifying a container's apparmor profile.
// Deprecated: use a pod or container security context `appArmorProfile` field instead.
DeprecatedAppArmorBetaContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/"
// AppArmorBetaProfileRuntimeDefault is the profile specifying the runtime default.
AppArmorBetaProfileRuntimeDefault = "runtime/default"
// DeprecatedAppArmorBetaProfileRuntimeDefault is the profile specifying the runtime default.
DeprecatedAppArmorBetaProfileRuntimeDefault = "runtime/default"
// AppArmorBetaProfileNamePrefix is the prefix for specifying profiles loaded on the node.
AppArmorBetaProfileNamePrefix = "localhost/"
// DeprecatedAppArmorBetaProfileNamePrefix is the prefix for specifying profiles loaded on the node.
DeprecatedAppArmorBetaProfileNamePrefix = "localhost/"
// AppArmorBetaProfileNameUnconfined is the Unconfined AppArmor profile
AppArmorBetaProfileNameUnconfined = "unconfined"
// DeprecatedAppArmorBetaProfileNameUnconfined is the Unconfined AppArmor profile
DeprecatedAppArmorBetaProfileNameUnconfined = "unconfined"
// DeprecatedSeccompProfileDockerDefault represents the default seccomp profile used by docker.
// Deprecated: set a pod or container security context `seccompProfile` of type "RuntimeDefault" instead.

File diff suppressed because it is too large Load diff

View file

@ -77,6 +77,25 @@ message Affinity {
optional PodAntiAffinity podAntiAffinity = 3;
}
// AppArmorProfile defines a pod or container's AppArmor settings.
// +union
message AppArmorProfile {
// 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
optional string type = 1;
// 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
optional string localhostProfile = 2;
}
// AttachedVolume describes a volume attached to a node
message AttachedVolume {
// Name of the attached volume
@ -3866,6 +3885,11 @@ message PodSecurityContext {
// Note that this field cannot be set when spec.os.name is windows.
// +optional
optional SeccompProfile seccompProfile = 10;
// 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
optional AppArmorProfile appArmorProfile = 11;
}
// Describes the class of pods that should avoid this node.
@ -4154,6 +4178,7 @@ message PodSpec {
// - spec.hostPID
// - spec.hostIPC
// - spec.hostUsers
// - spec.securityContext.appArmorProfile
// - spec.securityContext.seLinuxOptions
// - spec.securityContext.seccompProfile
// - spec.securityContext.fsGroup
@ -4163,6 +4188,7 @@ message PodSpec {
// - 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
@ -5343,6 +5369,12 @@ message SecurityContext {
// Note that this field cannot be set when spec.os.name is windows.
// +optional
optional SeccompProfile seccompProfile = 11;
// 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
optional AppArmorProfile appArmorProfile = 12;
}
// SerializedReference is a reference to serialized object.

View file

@ -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" protobuf:"bytes,11,opt,name=appArmorProfile"`
}
// 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" protobuf:"bytes,1,opt,name=type,casttype=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 `json:"localhostProfile,omitempty" protobuf:"bytes,2,opt,name=localhostProfile"`
}
// +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" protobuf:"bytes,12,opt,name=appArmorProfile"`
}
// +enum

View file

@ -50,6 +50,16 @@ func (Affinity) SwaggerDoc() map[string]string {
return map_Affinity
}
var map_AppArmorProfile = map[string]string{
"": "AppArmorProfile defines a pod or container's AppArmor settings.",
"type": "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.",
"localhostProfile": "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\".",
}
func (AppArmorProfile) SwaggerDoc() map[string]string {
return map_AppArmorProfile
}
var map_AttachedVolume = map[string]string{
"": "AttachedVolume describes a volume attached to a node",
"name": "Name of the attached volume",
@ -1705,6 +1715,7 @@ var map_PodSecurityContext = map[string]string{
"sysctls": "Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch. Note that this field cannot be set when spec.os.name is windows.",
"fsGroupChangePolicy": "fsGroupChangePolicy defines behavior of changing ownership and permission of the volume before being exposed inside Pod. This field will only apply to volume types which support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are \"OnRootMismatch\" and \"Always\". If not specified, \"Always\" is used. Note that this field cannot be set when spec.os.name is windows.",
"seccompProfile": "The seccomp options to use by the containers in this pod. Note that this field cannot be set when spec.os.name is windows.",
"appArmorProfile": "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.",
}
func (PodSecurityContext) SwaggerDoc() map[string]string {
@ -1757,7 +1768,7 @@ var map_PodSpec = map[string]string{
"overhead": "Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md",
"topologySpreadConstraints": "TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed.",
"setHostnameAsFQDN": "If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. If a pod does not have FQDN, this has no effect. Default to false.",
"os": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup",
"os": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - 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 - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup",
"hostUsers": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.",
"schedulingGates": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.",
"resourceClaims": "ResourceClaims defines which ResourceClaims must be allocated and reserved before the Pod is allowed to start. The resources will be made available to those containers which consume them by name.\n\nThis is an alpha field and requires enabling the DynamicResourceAllocation feature gate.\n\nThis field is immutable.",
@ -2274,6 +2285,7 @@ var map_SecurityContext = map[string]string{
"allowPrivilegeEscalation": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.",
"procMount": "procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.",
"seccompProfile": "The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows.",
"appArmorProfile": "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.",
}
func (SecurityContext) SwaggerDoc() map[string]string {

View file

@ -74,6 +74,27 @@ func (in *Affinity) DeepCopy() *Affinity {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppArmorProfile) DeepCopyInto(out *AppArmorProfile) {
*out = *in
if in.LocalhostProfile != nil {
in, out := &in.LocalhostProfile, &out.LocalhostProfile
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppArmorProfile.
func (in *AppArmorProfile) DeepCopy() *AppArmorProfile {
if in == nil {
return nil
}
out := new(AppArmorProfile)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AttachedVolume) DeepCopyInto(out *AttachedVolume) {
*out = *in
@ -3998,6 +4019,11 @@ func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
*out = new(SeccompProfile)
(*in).DeepCopyInto(*out)
}
if in.AppArmorProfile != nil {
in, out := &in.AppArmorProfile, &out.AppArmorProfile
*out = new(AppArmorProfile)
(*in).DeepCopyInto(*out)
}
return
}
@ -5388,6 +5414,11 @@ func (in *SecurityContext) DeepCopyInto(out *SecurityContext) {
*out = new(SeccompProfile)
(*in).DeepCopyInto(*out)
}
if in.AppArmorProfile != nil {
in, out := &in.AppArmorProfile, &out.AppArmorProfile
*out = new(AppArmorProfile)
(*in).DeepCopyInto(*out)
}
return
}

View file

@ -777,6 +777,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1067,6 +1071,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1357,6 +1365,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1410,6 +1422,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -345,6 +345,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -556,6 +559,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -769,6 +775,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -856,6 +865,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -778,6 +778,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1068,6 +1072,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1358,6 +1366,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1411,6 +1423,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -353,6 +353,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -564,6 +567,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -777,6 +783,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -864,6 +873,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -779,6 +779,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1069,6 +1073,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1359,6 +1367,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1412,6 +1424,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -345,6 +345,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -556,6 +559,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -769,6 +775,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -856,6 +865,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -778,6 +778,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1068,6 +1072,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1358,6 +1366,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1411,6 +1423,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -353,6 +353,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -564,6 +567,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -777,6 +783,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -864,6 +873,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -778,6 +778,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1068,6 +1072,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1358,6 +1366,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1411,6 +1423,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -355,6 +355,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -566,6 +569,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -779,6 +785,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -866,6 +875,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -778,6 +778,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1068,6 +1072,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1358,6 +1366,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1411,6 +1423,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -353,6 +353,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -564,6 +567,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -777,6 +783,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -864,6 +873,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -777,6 +777,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1067,6 +1071,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1357,6 +1365,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1410,6 +1422,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -345,6 +345,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -556,6 +559,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -769,6 +775,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -856,6 +865,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -778,6 +778,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1068,6 +1072,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1358,6 +1366,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1411,6 +1423,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -353,6 +353,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -564,6 +567,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -777,6 +783,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -864,6 +873,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -779,6 +779,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1069,6 +1073,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1359,6 +1367,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1412,6 +1424,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -345,6 +345,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -556,6 +559,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -769,6 +775,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -856,6 +865,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -778,6 +778,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1068,6 +1072,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1358,6 +1366,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1411,6 +1423,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -353,6 +353,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -564,6 +567,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -777,6 +783,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -864,6 +873,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -853,6 +853,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1143,6 +1147,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1433,6 +1441,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1486,6 +1498,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -401,6 +401,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -612,6 +615,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -825,6 +831,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -912,6 +921,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -804,6 +804,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1094,6 +1098,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1384,6 +1392,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1437,6 +1449,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -365,6 +365,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -576,6 +579,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -789,6 +795,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -876,6 +885,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -853,6 +853,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1143,6 +1147,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1433,6 +1441,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1486,6 +1498,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -401,6 +401,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -612,6 +615,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -825,6 +831,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -912,6 +921,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -719,6 +719,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1009,6 +1013,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1299,6 +1307,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1352,6 +1364,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -301,6 +301,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -512,6 +515,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -725,6 +731,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -812,6 +821,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -762,6 +762,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1052,6 +1056,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1342,6 +1350,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1395,6 +1407,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -334,6 +334,9 @@ template:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -545,6 +548,9 @@ template:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -758,6 +764,9 @@ template:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -845,6 +854,9 @@ template:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -768,6 +768,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1058,6 +1062,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1348,6 +1356,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1401,6 +1413,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -339,6 +339,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -550,6 +553,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -763,6 +769,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -850,6 +859,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -777,6 +777,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1067,6 +1071,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1357,6 +1365,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1410,6 +1422,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -345,6 +345,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -556,6 +559,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -769,6 +775,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -856,6 +865,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -778,6 +778,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1068,6 +1072,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1358,6 +1366,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1411,6 +1423,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -355,6 +355,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -566,6 +569,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -779,6 +785,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -866,6 +875,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -779,6 +779,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1069,6 +1073,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1359,6 +1367,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"stdin": true,
@ -1412,6 +1424,10 @@
"seccompProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
},
"appArmorProfile": {
"type": "typeValue",
"localhostProfile": "localhostProfileValue"
}
},
"imagePullSecrets": [

View file

@ -345,6 +345,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -556,6 +559,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -769,6 +775,9 @@ spec:
restartPolicy: restartPolicyValue
securityContext:
allowPrivilegeEscalation: true
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
capabilities:
add:
- addValue
@ -856,6 +865,9 @@ spec:
schedulingGates:
- name: nameValue
securityContext:
appArmorProfile:
localhostProfile: localhostProfileValue
type: typeValue
fsGroup: 5
fsGroupChangePolicy: fsGroupChangePolicyValue
runAsGroup: 6

View file

@ -120,7 +120,7 @@ status:
type: PIDPressure
- lastHeartbeatTime: "2019-09-20T19:32:50Z"
lastTransitionTime: "2019-07-09T16:17:49Z"
message: kubelet is posting ready status. AppArmor enabled
message: kubelet is posting ready status
reason: KubeletReady
status: "True"
type: Ready

View file

@ -120,7 +120,7 @@ status:
type: PIDPressure
- lastHeartbeatTime: "2019-09-20T19:32:50Z"
lastTransitionTime: "2019-07-09T16:17:49Z"
message: kubelet is posting ready status. AppArmor enabled
message: kubelet is posting ready status
reason: KubeletReady
status: "True"
type: Ready

View file

@ -0,0 +1,52 @@
/*
Copyright 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.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
import (
v1 "k8s.io/api/core/v1"
)
// AppArmorProfileApplyConfiguration represents an declarative configuration of the AppArmorProfile type for use
// with apply.
type AppArmorProfileApplyConfiguration struct {
Type *v1.AppArmorProfileType `json:"type,omitempty"`
LocalhostProfile *string `json:"localhostProfile,omitempty"`
}
// AppArmorProfileApplyConfiguration constructs an declarative configuration of the AppArmorProfile type for use with
// apply.
func AppArmorProfile() *AppArmorProfileApplyConfiguration {
return &AppArmorProfileApplyConfiguration{}
}
// WithType sets the Type field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Type field is set to the value of the last call.
func (b *AppArmorProfileApplyConfiguration) WithType(value v1.AppArmorProfileType) *AppArmorProfileApplyConfiguration {
b.Type = &value
return b
}
// WithLocalhostProfile sets the LocalhostProfile field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the LocalhostProfile field is set to the value of the last call.
func (b *AppArmorProfileApplyConfiguration) WithLocalhostProfile(value string) *AppArmorProfileApplyConfiguration {
b.LocalhostProfile = &value
return b
}

View file

@ -35,6 +35,7 @@ type PodSecurityContextApplyConfiguration struct {
Sysctls []SysctlApplyConfiguration `json:"sysctls,omitempty"`
FSGroupChangePolicy *corev1.PodFSGroupChangePolicy `json:"fsGroupChangePolicy,omitempty"`
SeccompProfile *SeccompProfileApplyConfiguration `json:"seccompProfile,omitempty"`
AppArmorProfile *AppArmorProfileApplyConfiguration `json:"appArmorProfile,omitempty"`
}
// PodSecurityContextApplyConfiguration constructs an declarative configuration of the PodSecurityContext type for use with
@ -129,3 +130,11 @@ func (b *PodSecurityContextApplyConfiguration) WithSeccompProfile(value *Seccomp
b.SeccompProfile = value
return b
}
// WithAppArmorProfile sets the AppArmorProfile field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the AppArmorProfile field is set to the value of the last call.
func (b *PodSecurityContextApplyConfiguration) WithAppArmorProfile(value *AppArmorProfileApplyConfiguration) *PodSecurityContextApplyConfiguration {
b.AppArmorProfile = value
return b
}

View file

@ -36,6 +36,7 @@ type SecurityContextApplyConfiguration struct {
AllowPrivilegeEscalation *bool `json:"allowPrivilegeEscalation,omitempty"`
ProcMount *corev1.ProcMountType `json:"procMount,omitempty"`
SeccompProfile *SeccompProfileApplyConfiguration `json:"seccompProfile,omitempty"`
AppArmorProfile *AppArmorProfileApplyConfiguration `json:"appArmorProfile,omitempty"`
}
// SecurityContextApplyConfiguration constructs an declarative configuration of the SecurityContext type for use with
@ -131,3 +132,11 @@ func (b *SecurityContextApplyConfiguration) WithSeccompProfile(value *SeccompPro
b.SeccompProfile = value
return b
}
// WithAppArmorProfile sets the AppArmorProfile field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the AppArmorProfile field is set to the value of the last call.
func (b *SecurityContextApplyConfiguration) WithAppArmorProfile(value *AppArmorProfileApplyConfiguration) *SecurityContextApplyConfiguration {
b.AppArmorProfile = value
return b
}

View file

@ -4401,6 +4401,21 @@ var schemaYAML = typed.YAMLObject(`types:
- name: podAntiAffinity
type:
namedType: io.k8s.api.core.v1.PodAntiAffinity
- name: io.k8s.api.core.v1.AppArmorProfile
map:
fields:
- name: localhostProfile
type:
scalar: string
- name: type
type:
scalar: string
default: ""
unions:
- discriminator: type
fields:
- fieldName: localhostProfile
discriminatorValue: LocalhostProfile
- name: io.k8s.api.core.v1.AttachedVolume
map:
fields:
@ -6720,6 +6735,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: io.k8s.api.core.v1.PodSecurityContext
map:
fields:
- name: appArmorProfile
type:
namedType: io.k8s.api.core.v1.AppArmorProfile
- name: fsGroup
type:
scalar: numeric
@ -7614,6 +7632,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: allowPrivilegeEscalation
type:
scalar: boolean
- name: appArmorProfile
type:
namedType: io.k8s.api.core.v1.AppArmorProfile
- name: capabilities
type:
namedType: io.k8s.api.core.v1.Capabilities

View file

@ -613,6 +613,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
// Group=core, Version=v1
case corev1.SchemeGroupVersion.WithKind("Affinity"):
return &applyconfigurationscorev1.AffinityApplyConfiguration{}
case corev1.SchemeGroupVersion.WithKind("AppArmorProfile"):
return &applyconfigurationscorev1.AppArmorProfileApplyConfiguration{}
case corev1.SchemeGroupVersion.WithKind("AttachedVolume"):
return &applyconfigurationscorev1.AttachedVolumeApplyConfiguration{}
case corev1.SchemeGroupVersion.WithKind("AWSElasticBlockStoreVolumeSource"):

View file

@ -23,6 +23,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/pod-security-admission/api"
)
@ -35,6 +36,14 @@ profile, or restrict overrides to an allowed set of profiles.
metadata.annotations['container.apparmor.security.beta.kubernetes.io/*']
**Allowed Values:** 'runtime/default', 'localhost/*', empty, undefined
**Restricted Fields:**
spec.securityContext.appArmorProfile.type
spec.containers[*].securityContext.appArmorProfile.type
spec.initContainers[*].securityContext.appArmorProfile.type
spec.ephemeralContainers[*].securityContext.appArmorProfile.type
**Allowed Values:** 'RuntimeDefault', 'Localhost', undefined
*/
func init() {
addCheck(CheckAppArmorProfile)
@ -55,25 +64,78 @@ func CheckAppArmorProfile() Check {
}
}
func allowedProfile(profile string) bool {
func allowedAnnotationValue(profile string) bool {
return len(profile) == 0 ||
profile == corev1.AppArmorBetaProfileRuntimeDefault ||
strings.HasPrefix(profile, corev1.AppArmorBetaProfileNamePrefix)
profile == corev1.DeprecatedAppArmorBetaProfileRuntimeDefault ||
strings.HasPrefix(profile, corev1.DeprecatedAppArmorBetaProfileNamePrefix)
}
func allowedProfileType(profile corev1.AppArmorProfileType) bool {
switch profile {
case corev1.AppArmorProfileTypeRuntimeDefault,
corev1.AppArmorProfileTypeLocalhost:
return true
default:
return false
}
}
func appArmorProfile_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
var forbiddenValues []string
for k, v := range podMetadata.Annotations {
if strings.HasPrefix(k, corev1.AppArmorBetaContainerAnnotationKeyPrefix) && !allowedProfile(v) {
forbiddenValues = append(forbiddenValues, fmt.Sprintf("%s=%q", k, v))
var badSetters []string // things that explicitly set appArmorProfile.type to a bad value
badValues := sets.NewString()
if podSpec.SecurityContext != nil && podSpec.SecurityContext.AppArmorProfile != nil {
if !allowedProfileType(podSpec.SecurityContext.AppArmorProfile.Type) {
badSetters = append(badSetters, "pod")
badValues.Insert(string(podSpec.SecurityContext.AppArmorProfile.Type))
}
}
if len(forbiddenValues) > 0 {
sort.Strings(forbiddenValues)
var badContainers []string // containers that set apparmorProfile.type to a bad value
visitContainers(podSpec, func(c *corev1.Container) {
if c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil {
if !allowedProfileType(c.SecurityContext.AppArmorProfile.Type) {
badContainers = append(badContainers, c.Name)
badValues.Insert(string(c.SecurityContext.AppArmorProfile.Type))
}
}
})
if len(badContainers) > 0 {
badSetters = append(
badSetters,
fmt.Sprintf(
"%s %s",
pluralize("container", "containers", len(badContainers)),
joinQuote(badContainers),
),
)
}
var forbiddenAnnotations []string
for k, v := range podMetadata.Annotations {
if strings.HasPrefix(k, corev1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) && !allowedAnnotationValue(v) {
forbiddenAnnotations = append(forbiddenAnnotations, fmt.Sprintf("%s=%q", k, v))
}
}
badValueList := badValues.List()
if len(forbiddenAnnotations) > 0 {
sort.Strings(forbiddenAnnotations)
badValueList = append(badValueList, forbiddenAnnotations...)
badSetters = append(badSetters, pluralize("annotation", "annotations", len(forbiddenAnnotations)))
}
// pod or containers explicitly set bad apparmorProfiles
if len(badSetters) > 0 {
return CheckResult{
Allowed: false,
ForbiddenReason: pluralize("forbidden AppArmor profile", "forbidden AppArmor profiles", len(forbiddenValues)),
ForbiddenDetail: strings.Join(forbiddenValues, ", "),
ForbiddenReason: pluralize("forbidden AppArmor profile", "forbidden AppArmor profiles", len(badValueList)),
ForbiddenDetail: fmt.Sprintf(
"%s must not set AppArmor profile type to %s",
strings.Join(badSetters, " and "),
joinQuote(badValueList),
),
}
}

View file

@ -24,69 +24,145 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestCheckAppArmor(t *testing.T) {
func TestCheckAppArmor_Allowed(t *testing.T) {
testCases := []struct {
name string
metaData *metav1.ObjectMeta
podSpec *corev1.PodSpec
expectedResult *CheckResult
name string
metaData *metav1.ObjectMeta
podSpec *corev1.PodSpec
}{
{
name: "container with default AppArmor + extra annotations",
metaData: &metav1.ObjectMeta{Annotations: map[string]string{
corev1.AppArmorBetaProfileNamePrefix + "test": "runtime/default",
corev1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "test": "runtime/default",
"env": "prod",
},
},
podSpec: &corev1.PodSpec{},
expectedResult: &CheckResult{Allowed: true},
}},
podSpec: &corev1.PodSpec{},
},
{
name: "container with local AppArmor + extra annotations",
metaData: &metav1.ObjectMeta{Annotations: map[string]string{
corev1.AppArmorBetaProfileNamePrefix + "test": "localhost/sec-profile01",
corev1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "test": "localhost/sec-profile01",
"env": "dev",
},
},
podSpec: &corev1.PodSpec{},
expectedResult: &CheckResult{Allowed: true},
}},
podSpec: &corev1.PodSpec{},
},
{
name: "container with no AppArmor annotations",
metaData: &metav1.ObjectMeta{Annotations: map[string]string{
"env": "dev",
},
},
podSpec: &corev1.PodSpec{},
expectedResult: &CheckResult{Allowed: true},
}},
podSpec: &corev1.PodSpec{},
},
{
name: "container with no annotations",
metaData: &metav1.ObjectMeta{},
podSpec: &corev1.PodSpec{},
expectedResult: &CheckResult{Allowed: true},
name: "container with no annotations",
metaData: &metav1.ObjectMeta{},
podSpec: &corev1.PodSpec{},
},
{
name: "pod with runtime default",
metaData: &metav1.ObjectMeta{},
podSpec: &corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{
AppArmorProfile: &corev1.AppArmorProfile{
Type: corev1.AppArmorProfileTypeRuntimeDefault,
},
},
},
},
{
name: "container with localhost profile",
metaData: &metav1.ObjectMeta{},
podSpec: &corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
SecurityContext: &corev1.SecurityContext{
AppArmorProfile: &corev1.AppArmorProfile{
Type: corev1.AppArmorProfileTypeRuntimeDefault,
},
},
}},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := appArmorProfile_1_0(testCase.metaData, nil)
if result.Allowed != testCase.expectedResult.Allowed {
t.Errorf("Expected result was Allowed=%v for annotations %v",
testCase.expectedResult.Allowed, testCase.metaData.Annotations)
result := appArmorProfile_1_0(testCase.metaData, testCase.podSpec)
if !result.Allowed {
t.Errorf("Should be allowed")
}
})
}
}
func TestAppArmorProfile(t *testing.T) {
func TestCheckAppArmor_Forbidden(t *testing.T) {
tests := []struct {
name string
pod *corev1.Pod
expectReason string
expectDetail string
}{
{
name: "unconfined pod",
pod: &corev1.Pod{
Spec: corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{
AppArmorProfile: &corev1.AppArmorProfile{
Type: corev1.AppArmorProfileTypeUnconfined,
},
},
},
},
expectReason: "forbidden AppArmor profile",
expectDetail: `pod must not set AppArmor profile type to "Unconfined"`,
},
{
name: "unconfined container",
pod: &corev1.Pod{
Spec: corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{
AppArmorProfile: &corev1.AppArmorProfile{
Type: corev1.AppArmorProfileTypeRuntimeDefault,
},
},
Containers: []corev1.Container{{
Name: "foo",
SecurityContext: &corev1.SecurityContext{
AppArmorProfile: &corev1.AppArmorProfile{
Type: corev1.AppArmorProfileTypeUnconfined,
},
},
}},
},
},
expectReason: "forbidden AppArmor profile",
expectDetail: `container "foo" must not set AppArmor profile type to "Unconfined"`,
},
{
name: "unconfined init container",
pod: &corev1.Pod{
Spec: corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{
AppArmorProfile: &corev1.AppArmorProfile{
Type: corev1.AppArmorProfileTypeRuntimeDefault,
},
},
Containers: []corev1.Container{{
Name: "foo",
}},
InitContainers: []corev1.Container{{
Name: "bar",
SecurityContext: &corev1.SecurityContext{
AppArmorProfile: &corev1.AppArmorProfile{
Type: corev1.AppArmorProfileTypeUnconfined,
},
},
}},
},
},
expectReason: "forbidden AppArmor profile",
expectDetail: `container "bar" must not set AppArmor profile type to "Unconfined"`,
},
{
name: "multiple containers",
pod: &corev1.Pod{
@ -102,11 +178,11 @@ func TestAppArmorProfile(t *testing.T) {
},
},
},
expectReason: `forbidden AppArmor profiles`,
expectDetail: strings.Join([]string{
`container.apparmor.security.beta.kubernetes.io/="bogus"`,
`container.apparmor.security.beta.kubernetes.io/e="unconfined"`,
`container.apparmor.security.beta.kubernetes.io/f="unknown"`,
expectReason: "forbidden AppArmor profiles",
expectDetail: "annotations must not set AppArmor profile type to " + strings.Join([]string{
`"container.apparmor.security.beta.kubernetes.io/="bogus""`,
`"container.apparmor.security.beta.kubernetes.io/e="unconfined""`,
`"container.apparmor.security.beta.kubernetes.io/f="unknown""`,
}, ", "),
},
}

View file

@ -32,10 +32,10 @@ func init() {
// container with localhost/foo annotation
tweak(pod, func(copy *corev1.Pod) {
containerName := copy.Spec.Containers[0].Name
copy.Annotations[corev1.AppArmorBetaContainerAnnotationKeyPrefix+containerName] = "runtime/default"
copy.Annotations[corev1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix+containerName] = "runtime/default"
initContainerName := copy.Spec.Containers[0].Name
copy.Annotations[corev1.AppArmorBetaContainerAnnotationKeyPrefix+initContainerName] = "localhost/foo"
copy.Annotations[corev1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix+initContainerName] = "localhost/foo"
}),
}
},
@ -45,13 +45,13 @@ func init() {
// container with unconfined annotation
tweak(pod, func(copy *corev1.Pod) {
name := copy.Spec.Containers[0].Name
copy.Annotations[corev1.AppArmorBetaContainerAnnotationKeyPrefix+name] = "unconfined"
copy.Annotations[corev1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix+name] = "unconfined"
}),
// initContainer with unconfined annotation
tweak(pod, func(copy *corev1.Pod) {
name := copy.Spec.InitContainers[0].Name
copy.Annotations[corev1.AppArmorBetaContainerAnnotationKeyPrefix+name] = "unconfined"
copy.Annotations[corev1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix+name] = "unconfined"
}),
}
},

View file

@ -48,8 +48,8 @@ func LoadAppArmorProfiles(ctx context.Context, nsName string, clientset clientse
// CreateAppArmorTestPod creates a pod that tests apparmor profile enforcement. The pod exits with
// an error code if the profile is incorrectly enforced. If runOnce is true the pod will exit after
// a single test, otherwise it will repeat the test every 1 second until failure.
func CreateAppArmorTestPod(ctx context.Context, nsName string, clientset clientset.Interface, podClient *e2epod.PodClient, unconfined bool, runOnce bool) *v1.Pod {
profile := "localhost/" + appArmorProfilePrefix + nsName
func AppArmorTestPod(nsName string, unconfined bool, runOnce bool) *v1.Pod {
localhostProfile := appArmorProfilePrefix + nsName
testCmd := fmt.Sprintf(`
if touch %[1]s; then
echo "FAILURE: write to %[1]s should be denied"
@ -64,7 +64,6 @@ elif [[ $(< /proc/self/attr/current) != "%[3]s" ]]; then
fi`, appArmorDeniedPath, appArmorAllowedPath, appArmorProfilePrefix+nsName)
if unconfined {
profile = v1.AppArmorBetaProfileNameUnconfined
testCmd = `
if cat /proc/sysrq-trigger 2>&1 | grep 'Permission denied'; then
echo 'FAILURE: reading /proc/sysrq-trigger should be allowed'
@ -94,17 +93,25 @@ done`, testCmd)
},
}
profile := &v1.AppArmorProfile{}
if unconfined {
profile.Type = v1.AppArmorProfileTypeUnconfined
} else {
profile.Type = v1.AppArmorProfileTypeLocalhost
profile.LocalhostProfile = &localhostProfile
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-apparmor-",
Annotations: map[string]string{
v1.AppArmorBetaContainerAnnotationKeyPrefix + "test": profile,
},
Labels: map[string]string{
"test": "apparmor",
},
},
Spec: v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
AppArmorProfile: profile,
},
Affinity: loaderAffinity,
Containers: []v1.Container{{
Name: "test",
@ -115,20 +122,24 @@ done`, testCmd)
},
}
return pod
}
func RunAppArmorTestPod(ctx context.Context, pod *v1.Pod, clientset clientset.Interface, podClient *e2epod.PodClient, runOnce bool) *v1.Pod {
if runOnce {
pod = podClient.Create(ctx, pod)
framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespace(ctx,
clientset, pod.Name, nsName))
clientset, pod.Name, pod.Namespace))
var err error
pod, err = podClient.Get(ctx, pod.Name, metav1.GetOptions{})
framework.ExpectNoError(err)
} else {
pod = podClient.CreateSync(ctx, pod)
framework.ExpectNoError(e2epod.WaitTimeoutForPodReadyInNamespace(ctx, clientset, pod.Name, nsName, framework.PodStartTimeout))
framework.ExpectNoError(e2epod.WaitTimeoutForPodReadyInNamespace(ctx, clientset, pod.Name, pod.Namespace, framework.PodStartTimeout))
}
// Verify Pod affinity colocated the Pods.
loader := getRunningLoaderPod(ctx, nsName, clientset)
loader := getRunningLoaderPod(ctx, pod.Namespace, clientset)
gomega.Expect(pod.Spec.NodeName).To(gomega.Equal(loader.Spec.NodeName))
return pod

Some files were not shown because too many files have changed in this diff Show more