From 1d83e86c1231750831cafe9f26332ee6bf9c9e7a Mon Sep 17 00:00:00 2001 From: shuting Date: Thu, 29 Sep 2022 12:03:13 +0800 Subject: [PATCH] Add PSa policy validations (#4735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Validate PSa control names Signed-off-by: ShutingZhao * Add validation checks for the PSa rule Signed-off-by: ShutingZhao Signed-off-by: ShutingZhao Co-authored-by: Charles-Edouard Brétéché --- api/kyverno/v1/common_types.go | 1 + api/kyverno/v1/rule_test.go | 386 ++++++++++++ api/kyverno/v1/rule_types.go | 38 ++ api/kyverno/v1/utils.go | 9 + charts/kyverno/templates/crds.yaml | 128 ++++ config/crds/kyverno.io_clusterpolicies.yaml | 64 ++ config/crds/kyverno.io_policies.yaml | 64 ++ config/install.yaml | 128 ++++ config/install_debug.yaml | 128 ++++ pkg/pss/evaluate.go | 89 ++- pkg/pss/mapping.go | 599 ------------------- pkg/pss/utils.go | 69 --- pkg/pss/utils/mapping.go | 626 ++++++++++++++++++++ pkg/pss/utils/types.go | 16 + 14 files changed, 1664 insertions(+), 681 deletions(-) delete mode 100644 pkg/pss/mapping.go delete mode 100644 pkg/pss/utils.go create mode 100644 pkg/pss/utils/mapping.go create mode 100644 pkg/pss/utils/types.go diff --git a/api/kyverno/v1/common_types.go b/api/kyverno/v1/common_types.go index 57989dc21c..a344250117 100644 --- a/api/kyverno/v1/common_types.go +++ b/api/kyverno/v1/common_types.go @@ -344,6 +344,7 @@ type PodSecurity struct { type PodSecurityStandard struct { // ControlName specifies the name of the Pod Security Standard control. // See: https://kubernetes.io/docs/concepts/security/pod-security-standards/ + // +kubebuilder:validation:Enum=HostProcess;Host Namespaces;Privileged Containers;Capabilities;HostPath Volumes;Host Ports;AppArmor;SELinux;/proc Mount Type;Seccomp;Sysctls;Volume Types;Privilege Escalation;Running as Non-root;Running as Non-root user ControlName string `json:"controlName" yaml:"controlName"` // Images selects matching containers and applies the container level PSS. diff --git a/api/kyverno/v1/rule_test.go b/api/kyverno/v1/rule_test.go index dd813a6d45..d3c417ef2b 100644 --- a/api/kyverno/v1/rule_test.go +++ b/api/kyverno/v1/rule_test.go @@ -2,6 +2,7 @@ package v1 import ( "encoding/json" + "fmt" "testing" "gotest.tools/assert" @@ -328,6 +329,391 @@ func Test_Validate_NamespacedPolicy_MutateRuleTargetNamespace(t *testing.T) { } } +func Test_ValidatePSaControlNames(t *testing.T) { + path := field.NewPath("dummy") + testcases := []struct { + description string + rule []byte + errors func(r *Rule) field.ErrorList + }{ + { + description: "baseline_with_restricted_control_name", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Running as Non-root", + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Seccomp", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + }`), + errors: func(r *Rule) (errs field.ErrorList) { + return append(errs, + field.Invalid(path.Child("podSecurity").Child("exclude").Index(0).Child("controlName"), "Running as Non-root", "Invalid control name defined at the given level"), + ) + }, + }, + { + description: "baseline_with_baseline_control_name", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "/proc Mount Type", + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Seccomp", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + }`), + }, + { + description: "restricted_with_baseline_control_name", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "/proc Mount Type", + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Seccomp", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + }`), + errors: func(r *Rule) (errs field.ErrorList) { + return append(errs, + field.Invalid(path.Child("podSecurity").Child("exclude").Index(0).Child("controlName"), "/proc Mount Type", "Invalid control name defined at the given level"), + ) + }, + }, + { + description: "restricted_with_restricted_control_name", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Escalation", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + }`), + }, + { + description: "container_level_control_with_images", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Escalation" + } + ] + } + } + }`), + errors: func(r *Rule) (errs field.ErrorList) { + return append(errs, + field.Invalid(path.Child("podSecurity").Child("exclude").Index(0).Child("controlName"), "Privilege Escalation", "exclude.images must be specified for the container level control"), + ) + }, + }, + { + description: "container_level_control_without_images", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Escalation", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + }`), + }, + { + description: "pod_level_control_with_images", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + }`), + errors: func(r *Rule) (errs field.ErrorList) { + return append(errs, + field.Invalid(path.Child("podSecurity").Child("exclude").Index(0).Child("controlName"), "Host Namespaces", "exclude.images must not be specified for the pod level control"), + ) + }, + }, + { + description: "pod_level_control_without_images", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces" + } + ] + } + } + }`), + }, + { + description: "mixed_level_controls_without_images", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux" + } + ] + } + } + }`), + }, + { + description: "mixed_level_controls_with_images", + rule: []byte(` + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + }`), + }, + } + + for _, testcase := range testcases { + var rule Rule + err := json.Unmarshal(testcase.rule, &rule) + assert.NilError(t, err) + errs := rule.ValidatePSaControlNames(path) + var expectedErrs field.ErrorList + if testcase.errors != nil { + expectedErrs = testcase.errors(&rule) + } + fmt.Println("====errs", errs) + assert.Equal(t, len(errs), len(expectedErrs)) + for i := range errs { + assert.Equal(t, errs[i].Error(), expectedErrs[i].Error()) + } + } +} + func Test_Validate_ClusterPolicy_MutateRuleTargetNamespace(t *testing.T) { path := field.NewPath("dummy") testcases := []struct { diff --git a/api/kyverno/v1/rule_types.go b/api/kyverno/v1/rule_types.go index ca3e9d3aa9..177ef3d3d3 100644 --- a/api/kyverno/v1/rule_types.go +++ b/api/kyverno/v1/rule_types.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" + "github.com/kyverno/kyverno/pkg/pss/utils" wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -139,6 +140,10 @@ func (r *Rule) IsMutateExisting() bool { return r.Mutation.Targets != nil } +func (r *Rule) IsPodSecurity() bool { + return r.Validation.PodSecurity != nil +} + // IsCloneSyncGenerate checks if the generate rule has the clone block with sync=true func (r *Rule) GetCloneSyncForGenerate() (clone bool, sync bool) { if !r.HasGenerate() { @@ -359,6 +364,38 @@ func (r *Rule) ValidateMutationRuleTargetNamespace(path *field.Path, namespaced return errs } +func (r *Rule) ValidatePSaControlNames(path *field.Path) (errs field.ErrorList) { + if r.IsPodSecurity() { + podSecurity := r.Validation.PodSecurity + forbiddenControls := utils.PSS_baseline_control_names + if podSecurity.Level == "baseline" { + forbiddenControls = utils.PSS_restricted_control_names + } + + for idx, exclude := range podSecurity.Exclude { + // container level control must specify images + if containsString(utils.PSS_container_level_control, exclude.ControlName) { + if len(exclude.Images) == 0 { + errs = append(errs, field.Invalid(path.Child("podSecurity").Child("exclude").Index(idx).Child("controlName"), exclude.ControlName, "exclude.images must be specified for the container level control")) + } + } else if containsString(utils.PSS_pod_level_control, exclude.ControlName) { + if len(exclude.Images) != 0 { + errs = append(errs, field.Invalid(path.Child("podSecurity").Child("exclude").Index(idx).Child("controlName"), exclude.ControlName, "exclude.images must not be specified for the pod level control")) + } + } + + if containsString([]string{"Seccomp", "Capabilities"}, exclude.ControlName) { + continue + } + + if containsString(forbiddenControls, exclude.ControlName) { + errs = append(errs, field.Invalid(path.Child("podSecurity").Child("exclude").Index(idx).Child("controlName"), exclude.ControlName, "Invalid control name defined at the given level")) + } + } + } + return errs +} + // Validate implements programmatic validation func (r *Rule) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.String) (errs field.ErrorList) { errs = append(errs, r.ValidateRuleType(path)...) @@ -366,5 +403,6 @@ func (r *Rule) Validate(path *field.Path, namespaced bool, policyNamespace strin errs = append(errs, r.MatchResources.Validate(path.Child("match"), namespaced, clusterResources)...) errs = append(errs, r.ExcludeResources.Validate(path.Child("exclude"), namespaced, clusterResources)...) errs = append(errs, r.ValidateMutationRuleTargetNamespace(path, namespaced, policyNamespace)...) + errs = append(errs, r.ValidatePSaControlNames(path)...) return errs } diff --git a/api/kyverno/v1/utils.go b/api/kyverno/v1/utils.go index 5282870c50..463b7c6fed 100755 --- a/api/kyverno/v1/utils.go +++ b/api/kyverno/v1/utils.go @@ -45,3 +45,12 @@ func ValidatePolicyName(path *field.Path, name string) (errs field.ErrorList) { } return errs } + +func containsString(list []string, key string) bool { + for _, val := range list { + if val == key { + return true + } + } + return false +} diff --git a/charts/kyverno/templates/crds.yaml b/charts/kyverno/templates/crds.yaml index 422ad78c75..af8b8bd315 100644 --- a/charts/kyverno/templates/crds.yaml +++ b/charts/kyverno/templates/crds.yaml @@ -2460,6 +2460,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. @@ -4180,6 +4196,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. @@ -5852,6 +5884,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. @@ -7547,6 +7595,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. @@ -9815,6 +9879,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. @@ -11535,6 +11615,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. @@ -13207,6 +13303,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. @@ -14902,6 +15014,22 @@ spec: properties: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. diff --git a/config/crds/kyverno.io_clusterpolicies.yaml b/config/crds/kyverno.io_clusterpolicies.yaml index 65cf36c8d5..d569296548 100644 --- a/config/crds/kyverno.io_clusterpolicies.yaml +++ b/config/crds/kyverno.io_clusterpolicies.yaml @@ -2367,6 +2367,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -5208,6 +5224,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -7844,6 +7876,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -10645,6 +10693,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers diff --git a/config/crds/kyverno.io_policies.yaml b/config/crds/kyverno.io_policies.yaml index 84dcba8fe1..213e3a58d6 100644 --- a/config/crds/kyverno.io_policies.yaml +++ b/config/crds/kyverno.io_policies.yaml @@ -2368,6 +2368,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -5210,6 +5226,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -7847,6 +7879,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -10648,6 +10696,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers diff --git a/config/install.yaml b/config/install.yaml index 995954d9ea..94206ce707 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -3653,6 +3653,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -6494,6 +6510,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -9130,6 +9162,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -11931,6 +11979,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -15380,6 +15444,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -18222,6 +18302,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -20859,6 +20955,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -23660,6 +23772,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers diff --git a/config/install_debug.yaml b/config/install_debug.yaml index 818e79647f..f2688ad348 100644 --- a/config/install_debug.yaml +++ b/config/install_debug.yaml @@ -3647,6 +3647,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -6488,6 +6504,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -9124,6 +9156,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -11925,6 +11973,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -15371,6 +15435,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -18213,6 +18293,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -20850,6 +20946,22 @@ spec: controlName: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers @@ -23651,6 +23763,22 @@ spec: description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user type: string images: description: Images selects matching containers diff --git a/pkg/pss/evaluate.go b/pkg/pss/evaluate.go index 917bd4e5f9..73200cd1f0 100644 --- a/pkg/pss/evaluate.go +++ b/pkg/pss/evaluate.go @@ -1,14 +1,19 @@ package pss import ( + "fmt" + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + pssutils "github.com/kyverno/kyverno/pkg/pss/utils" + "github.com/kyverno/kyverno/pkg/utils" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/pod-security-admission/api" "k8s.io/pod-security-admission/policy" ) // Evaluate Pod's specified containers only and get PSSCheckResults -func evaluatePSS(level *api.LevelVersion, pod corev1.Pod) (results []pssCheckResult) { +func evaluatePSS(level *api.LevelVersion, pod corev1.Pod) (results []pssutils.PSSCheckResult) { checks := policy.DefaultChecks() for _, check := range checks { @@ -20,10 +25,10 @@ func evaluatePSS(level *api.LevelVersion, pod corev1.Pod) (results []pssCheckRes checkResult := versionCheck.CheckPod(&pod.ObjectMeta, &pod.Spec) // Append only if the checkResult is not already in pssCheckResult if !checkResult.Allowed { - results = append(results, pssCheckResult{ - id: check.ID, - checkResult: checkResult, - restrictedFields: getRestrictedFields(check), + results = append(results, pssutils.PSSCheckResult{ + ID: check.ID, + CheckResult: checkResult, + RestrictedFields: GetRestrictedFields(check), }) } } @@ -31,22 +36,22 @@ func evaluatePSS(level *api.LevelVersion, pod corev1.Pod) (results []pssCheckRes return results } -func exemptKyvernoExclusion(defaultCheckResults, excludeCheckResults []pssCheckResult, exclude kyvernov1.PodSecurityStandard) []pssCheckResult { - defaultCheckResultsMap := make(map[string]pssCheckResult, len(defaultCheckResults)) +func exemptKyvernoExclusion(defaultCheckResults, excludeCheckResults []pssutils.PSSCheckResult, exclude kyvernov1.PodSecurityStandard) []pssutils.PSSCheckResult { + defaultCheckResultsMap := make(map[string]pssutils.PSSCheckResult, len(defaultCheckResults)) for _, result := range defaultCheckResults { - defaultCheckResultsMap[result.id] = result + defaultCheckResultsMap[result.ID] = result } for _, excludeResult := range excludeCheckResults { - for _, checkID := range PSS_controls_to_check_id[exclude.ControlName] { - if excludeResult.id == checkID { + for _, checkID := range pssutils.PSS_controls_to_check_id[exclude.ControlName] { + if excludeResult.ID == checkID { delete(defaultCheckResultsMap, checkID) } } } - var newDefaultCheckResults []pssCheckResult + var newDefaultCheckResults []pssutils.PSSCheckResult for _, result := range defaultCheckResultsMap { newDefaultCheckResults = append(newDefaultCheckResults, result) } @@ -75,7 +80,7 @@ func parseVersion(rule *kyvernov1.PodSecurity) (*api.LevelVersion, error) { } // EvaluatePod applies PSS checks to the pod and exempts controls specified in the rule -func EvaluatePod(rule *kyvernov1.PodSecurity, pod *corev1.Pod) (bool, []pssCheckResult, error) { +func EvaluatePod(rule *kyvernov1.PodSecurity, pod *corev1.Pod) (bool, []pssutils.PSSCheckResult, error) { level, err := parseVersion(rule) if err != nil { return false, nil, err @@ -84,7 +89,7 @@ func EvaluatePod(rule *kyvernov1.PodSecurity, pod *corev1.Pod) (bool, []pssCheck defaultCheckResults := evaluatePSS(level, *pod) for _, exclude := range rule.Exclude { - spec, matching := getPodWithMatchingContainers(exclude, pod) + spec, matching := GetPodWithMatchingContainers(exclude, pod) switch { // exclude pod level checks @@ -101,3 +106,61 @@ func EvaluatePod(rule *kyvernov1.PodSecurity, pod *corev1.Pod) (bool, []pssCheck return len(defaultCheckResults) == 0, defaultCheckResults, nil } + +// GetPodWithMatchingContainers extracts matching container/pod info by the given exclude rule +// and returns pod manifests containing spec and container info respectively +func GetPodWithMatchingContainers(exclude kyvernov1.PodSecurityStandard, pod *corev1.Pod) (podSpec, matching *corev1.Pod) { + if len(exclude.Images) == 0 { + podSpec = pod.DeepCopy() + podSpec.Spec.Containers = []corev1.Container{{Name: "fake"}} + podSpec.Spec.InitContainers = nil + podSpec.Spec.EphemeralContainers = nil + return podSpec, nil + } + + matchingImages := exclude.Images + matching = &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.GetName(), + Namespace: pod.GetNamespace(), + }, + } + for _, container := range pod.Spec.Containers { + if utils.ContainsWildcardPatterns(matchingImages, container.Image) { + matching.Spec.Containers = append(matching.Spec.Containers, container) + } + } + for _, container := range pod.Spec.InitContainers { + if utils.ContainsWildcardPatterns(matchingImages, container.Image) { + matching.Spec.InitContainers = append(matching.Spec.InitContainers, container) + } + } + + for _, container := range pod.Spec.EphemeralContainers { + if utils.ContainsWildcardPatterns(matchingImages, container.Image) { + matching.Spec.EphemeralContainers = append(matching.Spec.EphemeralContainers, container) + } + } + + return nil, matching +} + +// Get restrictedFields from Check.ID +func GetRestrictedFields(check policy.Check) []pssutils.RestrictedField { + for _, control := range pssutils.PSS_controls_to_check_id { + for _, checkID := range control { + if check.ID == checkID { + return pssutils.PSS_controls[checkID] + } + } + } + return nil +} + +func FormatChecksPrint(checks []pssutils.PSSCheckResult) string { + var str string + for _, check := range checks { + str += fmt.Sprintf("(%+v)\n", check.CheckResult) + } + return str +} diff --git a/pkg/pss/mapping.go b/pkg/pss/mapping.go deleted file mode 100644 index 11269cfdae..0000000000 --- a/pkg/pss/mapping.go +++ /dev/null @@ -1,599 +0,0 @@ -package pss - -import "k8s.io/pod-security-admission/policy" - -type restrictedField struct { - path string - allowedValues []interface{} -} - -type pssCheckResult struct { - id string - checkResult policy.CheckResult - restrictedFields []restrictedField -} - -// Translate PSS control to CheckResult.ID so that we can use PSS control in Kyverno policy -// For PSS controls see: https://kubernetes.io/docs/concepts/security/pod-security-standards/ -// For CheckResult.ID see: https://github.com/kubernetes/pod-security-admission/tree/master/policy -var PSS_controls_to_check_id = map[string][]string{ - // Controls with 2 different controls for each level - // container-level control - "Capabilities": { - "capabilities_baseline", - "capabilities_restricted", - }, - // Container and Pod-level control - "Seccomp": { - "seccompProfile_baseline", - "seccompProfile_restricted", - }, - - // === Baseline - // Container-level controls - "Privileged Containers": { - "privileged", - }, - "Host Ports": { - "hostPorts", - }, - "/proc Mount Type": { - "procMount", - }, - - // Container and pod-level controls - "HostProcess": { - "windowsHostProcess", - }, - "SELinux": { - "seLinuxOptions", - }, - - // Pod-level controls - "Host Namespaces": { - "hostNamespaces", - }, - "HostPath Volumes": { - "hostPathVolumes", - }, - "Sysctls": { - "sysctls", - }, - - // Metadata-level control - "AppArmor": { - "appArmorProfile", - }, - - // === Restricted - "Privilege Escalation": { - "allowPrivilegeEscalation", - }, - // Container and pod-level controls - "Running as Non-root": { - "runAsNonRoot", - }, - "Running as Non-root user": { - "runAsUser", - }, - - // Pod-level controls - "Volume Types": { - "restrictedVolumes", - }, -} - -var PSS_controls = map[string][]restrictedField{ - // Control name as key, same as ID field in CheckResult - - // === Baseline - // Container-level controls - "privileged": { - { - // type: - // - container-level - // - pod-container-level - // - pod level - path: "spec.containers[*].securityContext.privileged", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.initContainers[*].securityContext.privileged", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.privileged", - allowedValues: []interface{}{ - false, - nil, - }, - }, - }, - "hostPorts": { - { - path: "spec.containers[*].ports[*].hostPort", - allowedValues: []interface{}{ - false, - 0, - }, - }, - { - path: "spec.initContainers[*].ports[*].hostPort", - allowedValues: []interface{}{ - false, - 0, - }, - }, - { - path: "spec.ephemeralContainers[*].ports[*].hostPort", - allowedValues: []interface{}{ - false, - 0, - }, - }, - }, - "procMount": { - { - path: "spec.containers[*].securityContext.procMount", - allowedValues: []interface{}{ - nil, - "Default", - }, - }, - { - path: "spec.initContainers[*].securityContext.procMount", - allowedValues: []interface{}{ - nil, - "Default", - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.procMount", - allowedValues: []interface{}{ - nil, - "Default", - }, - }, - }, - "capabilities_baseline": { - { - path: "spec.containers[*].securityContext.capabilities.add", - allowedValues: []interface{}{ - nil, - "AUDIT_WRITE", - "CHOWN", - "DAC_OVERRIDE", - "FOWNER", - "FSETID", - "KILL", - "MKNOD", - "NET_BIND_SERVICE", - "SETFCAP", - "SETGID", - "SETPCAP", - "SETUID", - "SYS_CHROOT", - }, - }, - { - path: "spec.initContainers[*].securityContext.capabilities.add", - allowedValues: []interface{}{ - nil, - "AUDIT_WRITE", - "CHOWN", - "DAC_OVERRIDE", - "FOWNER", - "FSETID", - "KILL", - "MKNOD", - "NET_BIND_SERVICE", - "SETFCAP", - "SETGID", - "SETPCAP", - "SETUID", - "SYS_CHROOT", - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.capabilities.add", - allowedValues: []interface{}{ - nil, - "AUDIT_WRITE", - "CHOWN", - "DAC_OVERRIDE", - "FOWNER", - "FSETID", - "KILL", - "MKNOD", - "NET_BIND_SERVICE", - "SETFCAP", - "SETGID", - "SETPCAP", - "SETUID", - "SYS_CHROOT", - }, - }, - }, - - // Container and pod-level controls - "windowsHostProcess": { - { - path: "spec.securityContext.windowsOptions.hostProcess", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.containers[*].securityContext.windowsOptions.hostProcess", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.initContainers[*].securityContext.windowsOptions.hostProcess", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess", - allowedValues: []interface{}{ - false, - nil, - }, - }, - }, - "seLinuxOptions": { - // type - { - path: "spec.securityContext.seLinuxOptions.type", - allowedValues: []interface{}{ - "", - "container_t", - "container_init_t", - "container_kvm_t", - }, - }, - { - path: "spec.containers[*].securityContext.seLinuxOptions.type", - allowedValues: []interface{}{ - "", - "container_t", - "container_init_t", - "container_kvm_t", - }, - }, - { - path: "spec.initContainers[*].securityContext.seLinuxOptions.type", - allowedValues: []interface{}{ - "", - "container_t", - "container_init_t", - "container_kvm_t", - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.seLinuxOptions.type", - allowedValues: []interface{}{ - "", - "container_t", - "container_init_t", - "container_kvm_t", - }, - }, - - // user - { - path: "spec.securityContext.seLinuxOptions.user", - allowedValues: []interface{}{ - "", - }, - }, - { - path: "spec.containers[*].securityContext.seLinuxOptions.user", - allowedValues: []interface{}{ - "", - }, - }, - { - path: "spec.initContainers[*].securityContext.seLinuxOptions.user", - allowedValues: []interface{}{ - "", - }, - }, - { - path: "spec.ephemeralContainers[*].seLinuxOptions.user", - allowedValues: []interface{}{ - "", - }, - }, - - // role - { - path: "spec.securityContext.seLinuxOptions.role", - allowedValues: []interface{}{ - "", - }, - }, - { - path: "spec.containers[*].securityContext.seLinuxOptions.role", - allowedValues: []interface{}{ - "", - }, - }, - { - path: "spec.initContainers[*].securityContext.seLinuxOptions.role", - allowedValues: []interface{}{ - "", - }, - }, - { - path: "spec.ephemeralContainers[*].seLinuxOptions.role", - allowedValues: []interface{}{ - "", - }, - }, - }, - "seccompProfile_baseline": { - { - path: "spec.securityContext.seccompProfile.type", - allowedValues: []interface{}{ - nil, - "RuntimeDefault", - "Localhost", - }, - }, - { - path: "spec.containers[*].securityContext.seccompProfile.type", - allowedValues: []interface{}{ - nil, - "RuntimeDefault", - "Localhost", - }, - }, - { - path: "spec.initContainers[*].securityContext.seccompProfile.type", - allowedValues: []interface{}{ - nil, - "RuntimeDefault", - "Localhost", - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.seccompProfile.type", - allowedValues: []interface{}{ - nil, - "RuntimeDefault", - "Localhost", - }, - }, - }, - "seccompProfile_restricted": { - { - path: "spec.securityContext.seccompProfile.type", - allowedValues: []interface{}{ - "RuntimeDefault", - "Localhost", - }, - }, - { - path: "spec.containers[*].securityContext.seccompProfile.type", - allowedValues: []interface{}{ - "RuntimeDefault", - "Localhost", - }, - }, - { - path: "spec.initContainers[*].securityContext.seccompProfile.type", - allowedValues: []interface{}{ - "RuntimeDefault", - "Localhost", - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.seccompProfile.type", - allowedValues: []interface{}{ - "RuntimeDefault", - "Localhost", - }, - }, - }, - - // Pod-level controls - "sysctls": { - { - path: "spec.securityContext.sysctls[*].name", - allowedValues: []interface{}{ - "kernel.shm_rmid_forced", - "net.ipv4.ip_local_port_range", - "net.ipv4.tcp_syncookies", - "net.ipv4.ping_group_range", - "net.ipv4.ip_unprivileged_port_start", - }, - }, - }, - "hostPathVolumes": { - { - path: "spec.volumes[*].hostPath", - allowedValues: []interface{}{ - nil, - }, - }, - }, - "hostNamespaces": { - { - path: "spec.hostNetwork", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.hostPID", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.hostIPC", - allowedValues: []interface{}{ - false, - nil, - }, - }, - }, - - // metadata-level controls - "appArmorProfile": { - { - path: "metadata.annotations", - allowedValues: []interface{}{ - nil, - "", - "runtime/default", - "localhost/*", - }, - }, - }, - - // === Restricted - "restrictedVolumes": { - { - path: "spec.volumes[*]", - allowedValues: []interface{}{ - "spec.volumes[*].configMap", - "spec.volumes[*].downwardAPI", - "spec.volumes[*].emptyDir", - "spec.volumes[*].projected", - "spec.volumes[*].secret", - "spec.volumes[*].csi", - "spec.volumes[*].persistentVolumeClaim", - "spec.volumes[*].ephemeral", - }, - }, - }, - "runAsNonRoot": { - { - path: "spec.containers[*].securityContext.runAsNonRoot", - allowedValues: []interface{}{ - true, - nil, - }, - }, - { - path: "spec.initContainers[*].securityContext.runAsNonRoot", - allowedValues: []interface{}{ - false, - nil, - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.runAsNonRoot", - allowedValues: []interface{}{ - false, - nil, - }, - }, - }, - "runAsUser": { - { - path: "spec.securityContext.runAsUser", - allowedValues: []interface{}{ - "", - nil, - }, - }, - { - path: "spec.containers[*].securityContext.runAsUser", - allowedValues: []interface{}{ - "", - nil, - }, - }, - { - path: "spec.initContainers[*].securityContext.runAsUser", - allowedValues: []interface{}{ - "", - nil, - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.runAsUser", - allowedValues: []interface{}{ - "", - nil, - }, - }, - }, - "allowPrivilegeEscalation": { - { - path: "spec.containers[*].securityContext.allowPrivilegeEscalation", - allowedValues: []interface{}{ - false, - }, - }, - { - path: "spec.initContainers[*].securityContext.allowPrivilegeEscalation", - allowedValues: []interface{}{ - false, - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation", - allowedValues: []interface{}{ - false, - }, - }, - }, - "capabilities_restricted": { - { - path: "spec.containers[*].securityContext.capabilities.drop", - allowedValues: []interface{}{ - "ALL", - }, - }, - { - path: "spec.initContainers[*].securityContext.capabilities.drop", - allowedValues: []interface{}{ - "ALL", - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.capabilities.drop", - allowedValues: []interface{}{ - "ALL", - }, - }, - { - path: "spec.containers[*].securityContext.capabilities.add", - allowedValues: []interface{}{ - nil, - "NET_BIND_SERVICE", - }, - }, - { - path: "spec.initContainers[*].securityContext.capabilities.add", - allowedValues: []interface{}{ - nil, - "NET_BIND_SERVICE", - }, - }, - { - path: "spec.ephemeralContainers[*].securityContext.capabilities.add", - allowedValues: []interface{}{ - nil, - "NET_BIND_SERVICE", - }, - }, - }, -} diff --git a/pkg/pss/utils.go b/pkg/pss/utils.go deleted file mode 100644 index 49cd0f83eb..0000000000 --- a/pkg/pss/utils.go +++ /dev/null @@ -1,69 +0,0 @@ -package pss - -import ( - "fmt" - - kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/utils" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/pod-security-admission/policy" -) - -// getPodWithMatchingContainers extracts matching container/pod info by the given exclude rule -// and returns pod manifests containing spec and container info respectively -func getPodWithMatchingContainers(exclude kyvernov1.PodSecurityStandard, pod *corev1.Pod) (podSpec, matching *corev1.Pod) { - if len(exclude.Images) == 0 { - podSpec = pod.DeepCopy() - podSpec.Spec.Containers = []corev1.Container{{Name: "fake"}} - podSpec.Spec.InitContainers = nil - podSpec.Spec.EphemeralContainers = nil - return podSpec, nil - } - - matchingImages := exclude.Images - matching = &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: pod.GetName(), - Namespace: pod.GetNamespace(), - }, - } - for _, container := range pod.Spec.Containers { - if utils.ContainsWildcardPatterns(matchingImages, container.Image) { - matching.Spec.Containers = append(matching.Spec.Containers, container) - } - } - for _, container := range pod.Spec.InitContainers { - if utils.ContainsWildcardPatterns(matchingImages, container.Image) { - matching.Spec.InitContainers = append(matching.Spec.InitContainers, container) - } - } - - for _, container := range pod.Spec.EphemeralContainers { - if utils.ContainsWildcardPatterns(matchingImages, container.Image) { - matching.Spec.EphemeralContainers = append(matching.Spec.EphemeralContainers, container) - } - } - - return nil, matching -} - -// Get restrictedFields from Check.ID -func getRestrictedFields(check policy.Check) []restrictedField { - for _, control := range PSS_controls_to_check_id { - for _, checkID := range control { - if check.ID == checkID { - return PSS_controls[checkID] - } - } - } - return nil -} - -func FormatChecksPrint(checks []pssCheckResult) string { - var str string - for _, check := range checks { - str += fmt.Sprintf("(%+v)\n", check.checkResult) - } - return str -} diff --git a/pkg/pss/utils/mapping.go b/pkg/pss/utils/mapping.go new file mode 100644 index 0000000000..f1dded830c --- /dev/null +++ b/pkg/pss/utils/mapping.go @@ -0,0 +1,626 @@ +package utils + +var PSS_baseline_control_names = []string{ + "HostProcess", + "Host Namespaces", + "Privileged Containers", + "Capabilities", + "HostPath Volumes", + "Host Ports", + "AppArmor", + "SELinux", + "/proc Mount Type", + "Seccomp", + "Sysctls", +} + +var PSS_restricted_control_names = []string{ + "Volume Types", + "Privilege Escalation", + "Running as Non-root", + "Running as Non-root user", + "Seccomp", + "Capabilities", +} + +var PSS_pod_level_control = []string{ + "Host Namespaces", + "HostPath Volumes", + "Sysctls", + "AppArmor", + "Volume Types", +} + +var PSS_container_level_control = []string{ + "Capabilities", + "Privileged Containers", + "Host Ports", + "/proc Mount Type", + "Privilege Escalation", +} + +// Translate PSS control to CheckResult.ID so that we can use PSS control in Kyverno policy +// For PSS controls see: https://kubernetes.io/docs/concepts/security/pod-security-standards/ +// For CheckResult.ID see: https://github.com/kubernetes/pod-security-admission/tree/master/policy +var PSS_controls_to_check_id = map[string][]string{ + // Controls with 2 different controls for each level + // container-level control + "Capabilities": { + "capabilities_baseline", + "capabilities_restricted", + }, + // Container and Pod-level control + "Seccomp": { + "seccompProfile_baseline", + "seccompProfile_restricted", + }, + + // === Baseline + // Container-level controls + "Privileged Containers": { + "privileged", + }, + "Host Ports": { + "hostPorts", + }, + "/proc Mount Type": { + "procMount", + }, + + // Container and pod-level controls + "HostProcess": { + "windowsHostProcess", + }, + "SELinux": { + "seLinuxOptions", + }, + + // Pod-level controls + "Host Namespaces": { + "hostNamespaces", + }, + "HostPath Volumes": { + "hostPathVolumes", + }, + "Sysctls": { + "sysctls", + }, + + // Metadata-level control + "AppArmor": { + "appArmorProfile", + }, + + // === Restricted + // Container-level control + "Privilege Escalation": { + "allowPrivilegeEscalation", + }, + // Container and pod-level controls + "Running as Non-root": { + "runAsNonRoot", + }, + "Running as Non-root user": { + "runAsUser", + }, + + // Pod-level controls + "Volume Types": { + "restrictedVolumes", + }, +} + +var PSS_controls = map[string][]RestrictedField{ + // Control name as key, same as ID field in CheckResult + + // === Baseline + // Container-level controls + "privileged": { + { + // type: + // - container-level + // - pod-container-level + // - pod level + Path: "spec.containers[*].securityContext.privileged", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.initContainers[*].securityContext.privileged", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.privileged", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + }, + "hostPorts": { + { + Path: "spec.containers[*].ports[*].hostPort", + AllowedValues: []interface{}{ + false, + 0, + }, + }, + { + Path: "spec.initContainers[*].ports[*].hostPort", + AllowedValues: []interface{}{ + false, + 0, + }, + }, + { + Path: "spec.ephemeralContainers[*].ports[*].hostPort", + AllowedValues: []interface{}{ + false, + 0, + }, + }, + }, + "procMount": { + { + Path: "spec.containers[*].securityContext.procMount", + AllowedValues: []interface{}{ + nil, + "Default", + }, + }, + { + Path: "spec.initContainers[*].securityContext.procMount", + AllowedValues: []interface{}{ + nil, + "Default", + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.procMount", + AllowedValues: []interface{}{ + nil, + "Default", + }, + }, + }, + "capabilities_baseline": { + { + Path: "spec.containers[*].securityContext.capabilities.add", + AllowedValues: []interface{}{ + nil, + "AUDIT_WRITE", + "CHOWN", + "DAC_OVERRIDE", + "FOWNER", + "FSETID", + "KILL", + "MKNOD", + "NET_BIND_SERVICE", + "SETFCAP", + "SETGID", + "SETPCAP", + "SETUID", + "SYS_CHROOT", + }, + }, + { + Path: "spec.initContainers[*].securityContext.capabilities.add", + AllowedValues: []interface{}{ + nil, + "AUDIT_WRITE", + "CHOWN", + "DAC_OVERRIDE", + "FOWNER", + "FSETID", + "KILL", + "MKNOD", + "NET_BIND_SERVICE", + "SETFCAP", + "SETGID", + "SETPCAP", + "SETUID", + "SYS_CHROOT", + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.capabilities.add", + AllowedValues: []interface{}{ + nil, + "AUDIT_WRITE", + "CHOWN", + "DAC_OVERRIDE", + "FOWNER", + "FSETID", + "KILL", + "MKNOD", + "NET_BIND_SERVICE", + "SETFCAP", + "SETGID", + "SETPCAP", + "SETUID", + "SYS_CHROOT", + }, + }, + }, + + // Container and pod-level controls + "windowsHostProcess": { + { + Path: "spec.securityContext.windowsOptions.hostProcess", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.containers[*].securityContext.windowsOptions.hostProcess", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.initContainers[*].securityContext.windowsOptions.hostProcess", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + }, + "seLinuxOptions": { + // type + { + Path: "spec.securityContext.seLinuxOptions.type", + AllowedValues: []interface{}{ + "", + "container_t", + "container_init_t", + "container_kvm_t", + }, + }, + { + Path: "spec.containers[*].securityContext.seLinuxOptions.type", + AllowedValues: []interface{}{ + "", + "container_t", + "container_init_t", + "container_kvm_t", + }, + }, + { + Path: "spec.initContainers[*].securityContext.seLinuxOptions.type", + AllowedValues: []interface{}{ + "", + "container_t", + "container_init_t", + "container_kvm_t", + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.seLinuxOptions.type", + AllowedValues: []interface{}{ + "", + "container_t", + "container_init_t", + "container_kvm_t", + }, + }, + + // user + { + Path: "spec.securityContext.seLinuxOptions.user", + AllowedValues: []interface{}{ + "", + }, + }, + { + Path: "spec.containers[*].securityContext.seLinuxOptions.user", + AllowedValues: []interface{}{ + "", + }, + }, + { + Path: "spec.initContainers[*].securityContext.seLinuxOptions.user", + AllowedValues: []interface{}{ + "", + }, + }, + { + Path: "spec.ephemeralContainers[*].seLinuxOptions.user", + AllowedValues: []interface{}{ + "", + }, + }, + + // role + { + Path: "spec.securityContext.seLinuxOptions.role", + AllowedValues: []interface{}{ + "", + }, + }, + { + Path: "spec.containers[*].securityContext.seLinuxOptions.role", + AllowedValues: []interface{}{ + "", + }, + }, + { + Path: "spec.initContainers[*].securityContext.seLinuxOptions.role", + AllowedValues: []interface{}{ + "", + }, + }, + { + Path: "spec.ephemeralContainers[*].seLinuxOptions.role", + AllowedValues: []interface{}{ + "", + }, + }, + }, + "seccompProfile_baseline": { + { + Path: "spec.securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + nil, + "RuntimeDefault", + "Localhost", + }, + }, + { + Path: "spec.containers[*].securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + nil, + "RuntimeDefault", + "Localhost", + }, + }, + { + Path: "spec.initContainers[*].securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + nil, + "RuntimeDefault", + "Localhost", + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + nil, + "RuntimeDefault", + "Localhost", + }, + }, + }, + "seccompProfile_restricted": { + { + Path: "spec.securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + "RuntimeDefault", + "Localhost", + }, + }, + { + Path: "spec.containers[*].securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + "RuntimeDefault", + "Localhost", + }, + }, + { + Path: "spec.initContainers[*].securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + "RuntimeDefault", + "Localhost", + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.seccompProfile.type", + AllowedValues: []interface{}{ + "RuntimeDefault", + "Localhost", + }, + }, + }, + + // Pod-level controls + "sysctls": { + { + Path: "spec.securityContext.sysctls[*].name", + AllowedValues: []interface{}{ + "kernel.shm_rmid_forced", + "net.ipv4.ip_local_port_range", + "net.ipv4.tcp_syncookies", + "net.ipv4.ping_group_range", + "net.ipv4.ip_unprivileged_port_start", + }, + }, + }, + "hostPathVolumes": { + { + Path: "spec.volumes[*].hostPath", + AllowedValues: []interface{}{ + nil, + }, + }, + }, + "hostNamespaces": { + { + Path: "spec.hostNetwork", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.hostPID", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.hostIPC", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + }, + + // metadata-level controls + "appArmorProfile": { + { + Path: "metadata.annotations", + AllowedValues: []interface{}{ + nil, + "", + "runtime/default", + "localhost/*", + }, + }, + }, + + // === Restricted + "restrictedVolumes": { + { + Path: "spec.volumes[*]", + AllowedValues: []interface{}{ + "spec.volumes[*].configMap", + "spec.volumes[*].downwardAPI", + "spec.volumes[*].emptyDir", + "spec.volumes[*].projected", + "spec.volumes[*].secret", + "spec.volumes[*].csi", + "spec.volumes[*].persistentVolumeClaim", + "spec.volumes[*].ephemeral", + }, + }, + }, + "runAsNonRoot": { + { + Path: "spec.containers[*].securityContext.runAsNonRoot", + AllowedValues: []interface{}{ + true, + nil, + }, + }, + { + Path: "spec.initContainers[*].securityContext.runAsNonRoot", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.runAsNonRoot", + AllowedValues: []interface{}{ + false, + nil, + }, + }, + }, + "runAsUser": { + { + Path: "spec.securityContext.runAsUser", + AllowedValues: []interface{}{ + "", + nil, + }, + }, + { + Path: "spec.containers[*].securityContext.runAsUser", + AllowedValues: []interface{}{ + "", + nil, + }, + }, + { + Path: "spec.initContainers[*].securityContext.runAsUser", + AllowedValues: []interface{}{ + "", + nil, + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.runAsUser", + AllowedValues: []interface{}{ + "", + nil, + }, + }, + }, + "allowPrivilegeEscalation": { + { + Path: "spec.containers[*].securityContext.allowPrivilegeEscalation", + AllowedValues: []interface{}{ + false, + }, + }, + { + Path: "spec.initContainers[*].securityContext.allowPrivilegeEscalation", + AllowedValues: []interface{}{ + false, + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation", + AllowedValues: []interface{}{ + false, + }, + }, + }, + "capabilities_restricted": { + { + Path: "spec.containers[*].securityContext.capabilities.drop", + AllowedValues: []interface{}{ + "ALL", + }, + }, + { + Path: "spec.initContainers[*].securityContext.capabilities.drop", + AllowedValues: []interface{}{ + "ALL", + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.capabilities.drop", + AllowedValues: []interface{}{ + "ALL", + }, + }, + { + Path: "spec.containers[*].securityContext.capabilities.add", + AllowedValues: []interface{}{ + nil, + "NET_BIND_SERVICE", + }, + }, + { + Path: "spec.initContainers[*].securityContext.capabilities.add", + AllowedValues: []interface{}{ + nil, + "NET_BIND_SERVICE", + }, + }, + { + Path: "spec.ephemeralContainers[*].securityContext.capabilities.add", + AllowedValues: []interface{}{ + nil, + "NET_BIND_SERVICE", + }, + }, + }, +} diff --git a/pkg/pss/utils/types.go b/pkg/pss/utils/types.go new file mode 100644 index 0000000000..da2b40827a --- /dev/null +++ b/pkg/pss/utils/types.go @@ -0,0 +1,16 @@ +package utils + +import ( + "k8s.io/pod-security-admission/policy" +) + +type RestrictedField struct { + Path string + AllowedValues []interface{} +} + +type PSSCheckResult struct { + ID string + CheckResult policy.CheckResult + RestrictedFields []RestrictedField +}