diff --git a/Makefile b/Makefile index c1caa3fa77..42c568fc67 100644 --- a/Makefile +++ b/Makefile @@ -480,9 +480,13 @@ verify-codegen: verify-api verify-config verify-api-docs verify-helm ## Verify a # HELM ################################## +# .PHONY: gen-helm-docs .PHONY: gen-helm-docs gen-helm-docs: ## Generate Helm docs @docker run -v ${PWD}:/work -w /work jnorwood/helm-docs:v1.6.0 -s file +# gen-helm-docs: $(HELM_DOCS) ## Generate Helm docs +# # @$(HELM_DOCS) -s file +# @docker run -v ${PWD}:/work -w /work jnorwood/helm-docs:v1.6.0 -s file .PHONY: gen-helm gen-helm: gen-helm-docs kustomize-crd ## Generate Helm charts stuff diff --git a/api/kyverno/v1/common_types.go b/api/kyverno/v1/common_types.go index 6973298afe..cdacd8b5cd 100644 --- a/api/kyverno/v1/common_types.go +++ b/api/kyverno/v1/common_types.go @@ -6,6 +6,7 @@ import ( "github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/pod-security-admission/api" ) // FailurePolicyType specifies a failure policy that defines how unrecognized errors from the admission endpoint are handled. @@ -313,6 +314,46 @@ type Validation struct { // Deny defines conditions used to pass or fail a validation rule. // +optional Deny *Deny `json:"deny,omitempty" yaml:"deny,omitempty"` + + // PodSecurity applies exemptions for Kubernetes Pod Security admission + // by specifying exclusions for Pod Security Standards controls. + // +optional + PodSecurity *PodSecurity `json:"podSecurity,omitempty" yaml:"podSecurity,omitempty"` +} + +type PodSecurity struct { + // Level defines the Pod Security Standard level to be applied to workloads. + // Allowed values are privileged, baseline, and restricted. + // +kubebuilder:validation:Enum=privileged;baseline;restricted + Level api.Level `json:"level,omitempty" yaml:"level,omitempty"` + + // Version defines the Pod Security Standard versions that Kubernetes supports. + // Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + // +kubebuilder:validation:Enum=v1.19;v1.20;v1.21;v1.22;v1.23;v1.24;v1.25;latest + // +optional + Version string `json:"version,omitempty" yaml:"version,omitempty"` + + // Exclude specifies the Pod Security Standard controls to be excluded. + Exclude []PodSecurityStandard `json:"exclude,omitempty" yaml:"exclude,omitempty"` +} +type PodSecurityStandard struct { + // ControlName specifies the name of the Pod Security Standard control. + // See: https://kubernetes.io/docs/concepts/security/pod-security-standards/ + ControlName string `json:"controlName" yaml:"controlName"` + + // Images is a list of matching image patterns. + // Each image is the image name consisting of the registry address, repository, image, and tag. + // +optional + Images []string `json:"images,omitempty" yaml:"images,omitempty"` + + // RestrictedField selects the field for the given Pod Security Standard control. + // When not set, all restricted fields for the control are selected. + // +optional + RestrictedField string `json:"restrictedField,omitempty" yaml:"restrictedField,omitempty"` + + // Values defines the allowed values that can be excluded. + // +optional + Values []string `json:"values,omitempty" yaml:"values,omitempty"` } // DeserializeAnyPattern deserialize apiextensions.JSON to []interface{} diff --git a/api/kyverno/v1/zz_generated.deepcopy.go b/api/kyverno/v1/zz_generated.deepcopy.go index 1ccfcb8361..7a7f2e6581 100755 --- a/api/kyverno/v1/zz_generated.deepcopy.go +++ b/api/kyverno/v1/zz_generated.deepcopy.go @@ -905,6 +905,53 @@ func (in *ObjectFieldBinding) DeepCopy() *ObjectFieldBinding { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSecurity) DeepCopyInto(out *PodSecurity) { + *out = *in + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make([]PodSecurityStandard, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurity. +func (in *PodSecurity) DeepCopy() *PodSecurity { + if in == nil { + return nil + } + out := new(PodSecurity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSecurityStandard) DeepCopyInto(out *PodSecurityStandard) { + *out = *in + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityStandard. +func (in *PodSecurityStandard) DeepCopy() *PodSecurityStandard { + if in == nil { + return nil + } + out := new(PodSecurityStandard) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Policy) DeepCopyInto(out *Policy) { *out = *in @@ -1301,6 +1348,11 @@ func (in *Validation) DeepCopyInto(out *Validation) { *out = new(Deny) (*in).DeepCopyInto(*out) } + if in.PodSecurity != nil { + in, out := &in.PodSecurity, &out.PodSecurity + *out = new(PodSecurity) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Validation. diff --git a/charts/kyverno/templates/crds.yaml b/charts/kyverno/templates/crds.yaml index 372c72cb28..e04d9a8443 100644 --- a/charts/kyverno/templates/crds.yaml +++ b/charts/kyverno/templates/crds.yaml @@ -1450,6 +1450,53 @@ spec: pattern: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image patterns. Each image is the image name consisting of the registry address, repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field for the given Pod Security Standard control. When not set, all restricted fields for the control are selected. + type: string + values: + description: Values defines the allowed values that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures and mutate them to add a digest @@ -3087,6 +3134,53 @@ spec: pattern: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image patterns. Each image is the image name consisting of the registry address, repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field for the given Pod Security Standard control. When not set, all restricted fields for the control are selected. + type: string + values: + description: Values defines the allowed values that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures and mutate them to add a digest @@ -5543,6 +5637,53 @@ spec: pattern: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image patterns. Each image is the image name consisting of the registry address, repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field for the given Pod Security Standard control. When not set, all restricted fields for the control are selected. + type: string + values: + description: Values defines the allowed values that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures and mutate them to add a digest @@ -7180,6 +7321,53 @@ spec: pattern: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image patterns. Each image is the image name consisting of the registry address, repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field for the given Pod Security Standard control. When not set, all restricted fields for the control are selected. + type: string + values: + description: Values defines the allowed values that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures and mutate them to add a digest diff --git a/config/crds/kyverno.io_clusterpolicies.yaml b/config/crds/kyverno.io_clusterpolicies.yaml index 7fbbb42066..a1b0c315cb 100644 --- a/config/crds/kyverno.io_clusterpolicies.yaml +++ b/config/crds/kyverno.io_clusterpolicies.yaml @@ -2289,6 +2289,69 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for Pod + Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard + controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of + the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image + patterns. Each image is the image name consisting + of the registry address, repository, image, + and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for the + control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values are + privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, + latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -5007,6 +5070,70 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for + Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security + Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name + of the Pod Security Standard control. See: + https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching + image patterns. Each image is the image + name consisting of the registry address, + repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for + the control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values + are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, + v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures diff --git a/config/crds/kyverno.io_policies.yaml b/config/crds/kyverno.io_policies.yaml index 85d72e7c0a..6236b57825 100644 --- a/config/crds/kyverno.io_policies.yaml +++ b/config/crds/kyverno.io_policies.yaml @@ -2290,6 +2290,69 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for Pod + Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard + controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of + the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image + patterns. Each image is the image name consisting + of the registry address, repository, image, + and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for the + control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values are + privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, + latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -5009,6 +5072,70 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for + Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security + Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name + of the Pod Security Standard control. See: + https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching + image patterns. Each image is the image + name consisting of the registry address, + repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for + the control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values + are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, + v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures diff --git a/config/install.yaml b/config/install.yaml index 964d2502f0..1e8e44330e 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2306,6 +2306,69 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for Pod + Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard + controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of + the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image + patterns. Each image is the image name consisting + of the registry address, repository, image, + and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for the + control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values are + privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, + latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -5024,6 +5087,70 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for + Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security + Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name + of the Pod Security Standard control. See: + https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching + image patterns. Each image is the image + name consisting of the registry address, + repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for + the control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values + are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, + v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -8724,6 +8851,69 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for Pod + Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard + controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of + the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image + patterns. Each image is the image name consisting + of the registry address, repository, image, + and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for the + control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values are + privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, + latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -11443,6 +11633,70 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for + Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security + Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name + of the Pod Security Standard control. See: + https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching + image patterns. Each image is the image + name consisting of the registry address, + repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for + the control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values + are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, + v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures diff --git a/config/install_debug.yaml b/config/install_debug.yaml old mode 100755 new mode 100644 index ab0dab26d0..e324248744 --- a/config/install_debug.yaml +++ b/config/install_debug.yaml @@ -2304,6 +2304,69 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for Pod + Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard + controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of + the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image + patterns. Each image is the image name consisting + of the registry address, repository, image, + and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for the + control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values are + privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, + latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -5022,6 +5085,70 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for + Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security + Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name + of the Pod Security Standard control. See: + https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching + image patterns. Each image is the image + name consisting of the registry address, + repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for + the control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values + are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, + v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -8718,6 +8845,69 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for Pod + Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard + controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name of + the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching image + patterns. Each image is the image name consisting + of the registry address, repository, image, + and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for the + control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values are + privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, + latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures @@ -11437,6 +11627,70 @@ spec: description: Pattern specifies an overlay-style pattern used to check resources. x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes + Pod Security admission by specifying exclusions for + Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security + Standard controls to be excluded. + items: + properties: + controlName: + description: 'ControlName specifies the name + of the Pod Security Standard control. See: + https://kubernetes.io/docs/concepts/security/pod-security-standards/' + type: string + images: + description: Images is a list of matching + image patterns. Each image is the image + name consisting of the registry address, + repository, image, and tag. + items: + type: string + type: array + restrictedField: + description: RestrictedField selects the field + for the given Pod Security Standard control. + When not set, all restricted fields for + the control are selected. + type: string + values: + description: Values defines the allowed values + that can be excluded. + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard + level to be applied to workloads. Allowed values + are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard + versions that Kubernetes supports. Allowed values + are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, + v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object type: object verifyImages: description: VerifyImages is used to verify image signatures diff --git a/docs/crd/v1/index.html b/docs/crd/v1/index.html index 2dd7dc2070..681ee3342a 100644 --- a/docs/crd/v1/index.html +++ b/docs/crd/v1/index.html @@ -2528,6 +2528,132 @@ github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest.ObjectReferenceList
+

PodSecurity +

+

+(Appears on: +Validation) +

+

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+level
+ +k8s.io/pod-security-admission/api.Level + +
+

Level defines the Pod Security Standard level to be applied to workloads. +Allowed values are privileged, baseline, and restricted.

+
+version
+ +string + +
+(Optional) +

Version defines the Pod Security Standard versions that Kubernetes supports. +Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest.

+
+exclude
+ + +[]PodSecurityStandard + + +
+

Exclude specifies the Pod Security Standard controls to be excluded.

+
+
+

PodSecurityStandard +

+

+(Appears on: +PodSecurity) +

+

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+controlName
+ +string + +
+

ControlName specifies the name of the Pod Security Standard control. +See: https://kubernetes.io/docs/concepts/security/pod-security-standards/

+
+images
+ +[]string + +
+(Optional) +

Images is a list of matching image patterns. +Each image is the image name consisting of the registry address, repository, image, and tag.

+
+restrictedField
+ +string + +
+(Optional) +

RestrictedField selects the field for the given Pod Security Standard control. +When not set, all restricted fields for the control are selected.

+
+values
+ +[]string + +
+(Optional) +

Values defines the allowed values that can be excluded.

+
+

PolicyInterface

@@ -3441,6 +3567,21 @@ Deny

Deny defines conditions used to pass or fail a validation rule.

+ + +podSecurity
+ + +PodSecurity + + + + +(Optional) +

PodSecurity applies exemptions for Kubernetes Pod Security admission +by specifying exclusions for Pod Security Standards controls.

+ +
diff --git a/go.mod b/go.mod index 38858593d6..409c62f9bb 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( k8s.io/client-go v0.23.5 k8s.io/klog/v2 v2.60.1-0.20220317184644-43cc75f9ae89 k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf + k8s.io/pod-security-admission v0.23.0 sigs.k8s.io/controller-runtime v0.11.0 sigs.k8s.io/kustomize/api v0.11.2 sigs.k8s.io/kustomize/kyaml v0.13.3 @@ -337,7 +338,7 @@ require ( golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 // indirect golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect golang.org/x/tools v0.1.11 // indirect diff --git a/go.sum b/go.sum index b8f9a8f679..8238c6e2db 100644 --- a/go.sum +++ b/go.sum @@ -1040,7 +1040,6 @@ github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= github.com/go-openapi/validate v0.20.3/go.mod h1:goDdqVGiigM3jChcrYJxD2joalke3ZXeftD16byIjA4= github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= -github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y= github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-piv/piv-go v1.8.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk= @@ -3760,6 +3759,8 @@ k8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE= k8s.io/legacy-cloud-providers v0.19.7/go.mod h1:dsZk4gH9QIwAtHQ8CK0Ps257xlfgoXE3tMkMNhW2xDU= k8s.io/metrics v0.16.4/go.mod h1:dckkfqvaASo+NrzEmp8ST8yCc9hGt7lx9ABAILyDHx8= k8s.io/metrics v0.23.5/go.mod h1:WNAtV2a5BYbmDS8+7jSqYYV6E3efuGTpIwJ8PTD1wgs= +k8s.io/pod-security-admission v0.23.0 h1:xxCwpQ0sXoGnzplssHFseP7mD8P7TnK7qDuvlWEmPLw= +k8s.io/pod-security-admission v0.23.0/go.mod h1:vGExA081PHZFK9Yma4kuPtfWwy5zxbEUhniiUDKFicM= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= diff --git a/pkg/autogen/autogen.go b/pkg/autogen/autogen.go index 51c8106a65..63453a8548 100644 --- a/pkg/autogen/autogen.go +++ b/pkg/autogen/autogen.go @@ -236,11 +236,19 @@ func convertRule(rule kyvernoRule, kind string) (*kyvernov1.Rule, error) { if bytes, err := json.Marshal(rule); err != nil { return nil, err } else { - bytes = updateGenRuleByte(bytes, kind) - if err := json.Unmarshal(bytes, &rule); err != nil { - return nil, err + if rule.Validation != nil && rule.Validation.PodSecurity != nil { + bytes = updateRestrictedFields(bytes, kind) + if err := json.Unmarshal(bytes, &rule); err != nil { + return nil, err + } + } else { + bytes = updateGenRuleByte(bytes, kind) + if err := json.Unmarshal(bytes, &rule); err != nil { + return nil, err + } } } + out := kyvernov1.Rule{ Name: rule.Name, VerifyImages: rule.VerifyImages, diff --git a/pkg/autogen/autogen_test.go b/pkg/autogen/autogen_test.go index f924766ede..82f4490246 100644 --- a/pkg/autogen/autogen_test.go +++ b/pkg/autogen/autogen_test.go @@ -127,6 +127,11 @@ func Test_CanAutoGen(t *testing.T) { policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Namespace"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), expectedControllers: "none", }, + { + name: "rule-with-match-kinds-pod-only-validate-exclude", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","podSecurity": {"level": "baseline","version":"v1.24","exclude":[{"controlName":"SELinux","restrictedField":"spec.containers[*].securityContext.seLinuxOptions.role","images":["nginx"],"values":["baz"]}, {"controlName":"SELinux","restrictedField":"spec.initContainers[*].securityContext.seLinuxOptions.role","images":["nodejs"],"values":["init-baz"]}]}}}]}}`), + expectedControllers: PodControllers, + }, } for _, test := range testCases { @@ -173,7 +178,6 @@ func Test_GetSupportedControllers(t *testing.T) { policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","deny":{"conditions":[{"key":"{{request.object.metadata.labels.foo}}","operator":"Equals","value":"bar"}]}}}]}}`), expectedControllers: PodControllers, }, - { name: "rule-with-match-mixed-kinds-pod-podcontrollers", policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"preconditions":{"any":[{"key":"{{request.operation}}","operator":"Equals","value":"CREATE"}]},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), @@ -219,6 +223,11 @@ func Test_GetSupportedControllers(t *testing.T) { policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Namespace"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), expectedControllers: "none", }, + { + name: "rule-with-match-kinds-pod-only-validate-exclude", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","podSecurity": {"level": "baseline","version":"v1.24","exclude":[{"controlName":"SELinux","restrictedField":"spec.containers[*].securityContext.seLinuxOptions.role","images":["nginx"],"values":["baz"]}, {"controlName":"SELinux","restrictedField":"spec.initContainers[*].securityContext.seLinuxOptions.role","images":["nodejs"],"values":["init-baz"]}]}}}]}}`), + expectedControllers: PodControllers, + }, } for _, test := range testCases { @@ -593,6 +602,37 @@ func Test_Deny(t *testing.T) { } } +func Test_ValidatePodSecurity(t *testing.T) { + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/policy/validate/enforce-baseline-exclude-selinuxoptions.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + spec := policy.GetSpec() + + rulePatches, errs := GenerateRulePatches(spec, PodControllers) + if len(errs) != 0 { + t.Log(errs) + } + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-enforce-baseline-exclude-se-linux-options","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"],"namespaces":["privileged-pss-with-kyverno"]}}],"resources":{}},"validate":{"podSecurity":{"level":"baseline","version":"v1.24","exclude":[{"controlName":"SELinux","images":["nginx"],"restrictedField":"spec.template.spec.containers[*].securityContext.seLinuxOptions.role","values":["baz"]},{"controlName":"SELinux","images":["nodejs"],"restrictedField":"spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role","values":["init-bazo"]}]}}}}`), + []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-enforce-baseline-exclude-se-linux-options","match":{"any":[{"resources":{"kinds":["CronJob"],"namespaces":["privileged-pss-with-kyverno"]}}],"resources":{}},"validate":{"podSecurity":{"level":"baseline","version":"v1.24","exclude":[{"controlName":"SELinux","images":["nginx"],"restrictedField":"spec.jobTemplate.spec.template.spec.containers[*].securityContext.seLinuxOptions.role","values":["baz"]},{"controlName":"SELinux","images":["nodejs"],"restrictedField":"spec.jobTemplate.spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role","values":["init-bazo"]}]}}}}`), + } + + for i, ep := range expectedPatches { + assert.Equal(t, string(rulePatches[i]), string(ep), + fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep)) + } +} + func Test_ComputeRules(t *testing.T) { intPtr := func(i int) *int { return &i } testCases := []struct { diff --git a/pkg/autogen/rule.go b/pkg/autogen/rule.go index 4c4d1e322a..50314071ce 100644 --- a/pkg/autogen/rule.go +++ b/pkg/autogen/rule.go @@ -149,6 +149,20 @@ func generateRule(name string, rule *kyvernov1.Rule, tplKey, shift string, kinds rule.Validation = deny return rule } + if rule.Validation.PodSecurity != nil && len(rule.Validation.PodSecurity.Exclude) > 0 { + newExclude := make([]kyvernov1.PodSecurityStandard, len(rule.Validation.PodSecurity.Exclude)) + copy(newExclude, rule.Validation.PodSecurity.Exclude) + podSecurity := kyvernov1.Validation{ + Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "podSecurity"), + PodSecurity: &kyvernov1.PodSecurity{ + Level: rule.Validation.PodSecurity.Level, + Version: rule.Validation.PodSecurity.Version, + Exclude: newExclude, + }, + } + rule.Validation = podSecurity + return rule + } if rule.Validation.GetAnyPattern() != nil { anyPatterns, err := rule.Validation.DeserializeAnyPattern() if err != nil { @@ -285,3 +299,14 @@ func updateGenRuleByte(pbyte []byte, kind string) (obj []byte) { obj = []byte(strings.ReplaceAll(string(obj), "request.object.metadata", "request.object.spec.template.metadata")) return obj } + +func updateRestrictedFields(pbyte []byte, kind string) (obj []byte) { + if kind == "Pod" { + obj = []byte(strings.ReplaceAll(string(pbyte), `"restrictedField":"spec`, `"restrictedField":"spec.template.spec`)) + } + if kind == "Cronjob" { + obj = []byte(strings.ReplaceAll(string(pbyte), `"restrictedField":"spec`, `"restrictedField":"spec.jobTemplate.spec.template.spec`)) + } + obj = []byte(strings.ReplaceAll(string(obj), "metadata", "spec.template.metadata")) + return obj +} diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index e4d150f183..acbdc1705f 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -17,10 +17,16 @@ import ( "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/validate" "github.com/kyverno/kyverno/pkg/engine/variables" + "github.com/kyverno/kyverno/pkg/pss" "github.com/kyverno/kyverno/pkg/utils" "github.com/pkg/errors" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/pod-security-admission/api" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -179,6 +185,7 @@ type validator struct { pattern apiextensions.JSON anyPattern apiextensions.JSON deny *kyvernov1.Deny + podSecurity *kyvernov1.PodSecurity } func newValidator(log logr.Logger, ctx *PolicyContext, rule *kyvernov1.Rule) *validator { @@ -192,6 +199,7 @@ func newValidator(log logr.Logger, ctx *PolicyContext, rule *kyvernov1.Rule) *va pattern: ruleCopy.Validation.GetPattern(), anyPattern: ruleCopy.Validation.GetAnyPattern(), deny: ruleCopy.Validation.Deny, + podSecurity: ruleCopy.Validation.PodSecurity, } } @@ -252,6 +260,12 @@ func (v *validator) validate() *response.RuleResponse { return ruleResponse } + if v.podSecurity.Exclude != nil { + if !isDeleteRequest(v.ctx) { + ruleResponse := v.validatePodSecurity() + return ruleResponse + } + } v.log.V(2).Info("invalid validation rule: either patterns or deny conditions are expected") return nil @@ -427,6 +441,100 @@ func (v *validator) getDenyMessage(deny bool) string { return raw.(string) } +func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) { + kind := v.ctx.NewResource.GetKind() + + if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" { + var deployment appsv1.Deployment + + resourceBytes, err := v.ctx.NewResource.MarshalJSON() + if err != nil { + return nil, nil, err + } + err = json.Unmarshal(resourceBytes, &deployment) + if err != nil { + return nil, nil, err + } + podSpec = &deployment.Spec.Template.Spec + metadata = &deployment.Spec.Template.ObjectMeta + return podSpec, metadata, nil + } else if kind == "CronJob" { + var cronJob batchv1.CronJob + + resourceBytes, err := v.ctx.NewResource.MarshalJSON() + if err != nil { + return nil, nil, err + } + err = json.Unmarshal(resourceBytes, &cronJob) + if err != nil { + return nil, nil, err + } + podSpec = &cronJob.Spec.JobTemplate.Spec.Template.Spec + metadata = &cronJob.Spec.JobTemplate.ObjectMeta + } else if kind == "Pod" { + var pod corev1.Pod + + resourceBytes, err := v.ctx.NewResource.MarshalJSON() + if err != nil { + return nil, nil, err + } + err = json.Unmarshal(resourceBytes, &pod) + if err != nil { + return nil, nil, err + } + podSpec = &pod.Spec + metadata = &pod.ObjectMeta + return podSpec, metadata, nil + } + + if err != nil { + return nil, nil, err + } + return podSpec, metadata, err +} + +// Unstructured +func (v *validator) validatePodSecurity() *response.RuleResponse { + // Marshal pod metadata and spec + podSpec, metadata, err := getSpec(v) + if err != nil { + return ruleError(v.rule, response.Validation, "Error while getting new resource", err) + } + // Get pod security admission version + var apiVersion api.Version + + // Version set to "latest" by default + if v.podSecurity.Version == "" || v.podSecurity.Version == "latest" { + apiVersion = api.LatestVersion() + } else { + parsedApiVersion, err := api.ParseVersion(v.podSecurity.Version) + if err != nil { + return ruleError(v.rule, response.Validation, "failed to parse pod security api version", err) + } + apiVersion = api.MajorMinorVersion(parsedApiVersion.Major(), parsedApiVersion.Minor()) + } + level := &api.LevelVersion{ + Level: v.podSecurity.Level, + Version: apiVersion, + } + pod := &corev1.Pod{ + Spec: *podSpec, + ObjectMeta: *metadata, + } + allowed, pssChecks, err := pss.EvaluatePod(v.podSecurity, pod, level) + if err != nil { + msg := fmt.Sprintf("Failed to evaluate validation rule `%s`: %v", v.rule.Name, err) + return ruleResponse(*v.rule, response.Validation, msg, response.RuleStatusError, nil) + } + if allowed { + msg := fmt.Sprintf("Validation rule '%s' passed.", v.rule.Name) + return ruleResponse(*v.rule, response.Validation, msg, response.RuleStatusPass, nil) + } else { + msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, v.rule.Name, level.Level, level.Version, pss.FormatChecksPrint(pssChecks)) + return ruleResponse(*v.rule, response.Validation, msg, response.RuleStatusFail, nil) + } +} + func (v *validator) validateResourceWithRule() *response.RuleResponse { if !isEmptyUnstructured(&v.ctx.Element) { return v.validatePatterns(v.ctx.Element) diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index d39e70325c..9460034605 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -2,6 +2,7 @@ package engine import ( "encoding/json" + "fmt" "strings" "testing" @@ -3080,6 +3081,12110 @@ func Test_delete_ignore_pattern(t *testing.T) { assert.Equal(t, len(engineResponseDelete.PolicyResponse.Rules), 0) } +// Pod security admission + +// ====== Baseline ====== + +// === Control: "HostPath Volumes", check.ID: "hostPathVolumes" + +// pod-level: +// - spec.volumes[*].hostPath + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_host_path_volumes(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "HostPath Volumes" + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "hostpath-directory", + "hostPath": { + "path": "/var/local/aaa", + "type": "DirectoryOrCreate" + } + }, + { + "name": "hostpath-file", + "hostPath": { + "path": "/var/local/aaa/1.txt", + "type": "FileOrCreate" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_host_path_volumes_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "HostPath Volumes", + "restrictedField": "spec.volumes[*].hostPath", + "values": [ + "/var/local/aaa", + "/var/local/aaa/1.txt" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "hostpath-directory", + "hostPath": { + "path": "/var/local/aaa", + "type": "DirectoryOrCreate" + } + }, + { + "name": "hostpath-file", + "hostPath": { + "path": "/var/local/aaa/1.txt", + "type": "FileOrCreate" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_host_path_volume_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "HostPath Volumes", + "restrictedField": "spec.volumes[*].hostPath", + "values": [ + "/var/local/aaa" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "hostpath-directory", + "hostPath": { + "path": "/var/local/aaa", + "type": "DirectoryOrCreate" + } + }, + { + "name": "hostpath-file", + "hostPath": { + "path": "/var/local/aaa/1.txt", + "type": "FileOrCreate" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_host_path_volume_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Sysctls" + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "hostpath-directory", + "hostPath": { + "path": "/var/local/aaa", + "type": "DirectoryOrCreate" + } + }, + { + "name": "hostpath-file", + "hostPath": { + "path": "/var/local/aaa/1.txt", + "type": "FileOrCreate" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "AppArmor", check.ID: "appArmorProfile" + +// metadata-level: +// - metadata.annotations['container.apparmor.security.beta.kubernetes.io/*'] + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_app_armor(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "AppArmor" + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging", + "annotations": { + "container.apparmor.security.beta.kubernetes.io/": "bogus", + "container.apparmor.security.beta.kubernetes.io/a": "", + "container.apparmor.security.beta.kubernetes.io/b": "runtime/default", + "container.apparmor.security.beta.kubernetes.io/c": "localhost/", + "container.apparmor.security.beta.kubernetes.io/d": "localhost/foo", + "container.apparmor.security.beta.kubernetes.io/e": "unconfined", + "container.apparmor.security.beta.kubernetes.io/f": "unknown" + } + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx" + }, + { + "name": "nodejs", + "image": "nodejs" + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx" + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx" + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_app_armor_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "AppArmor", + "restrictedField": "metadata.annotations", + "values": [ + "bogus", + "unconfined", + "unknown" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging", + "annotations": { + "container.apparmor.security.beta.kubernetes.io/": "bogus", + "container.apparmor.security.beta.kubernetes.io/a": "", + "container.apparmor.security.beta.kubernetes.io/b": "runtime/default", + "container.apparmor.security.beta.kubernetes.io/c": "localhost/", + "container.apparmor.security.beta.kubernetes.io/d": "localhost/foo", + "container.apparmor.security.beta.kubernetes.io/e": "unconfined", + "container.apparmor.security.beta.kubernetes.io/f": "unknown" + } + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx" + }, + { + "name": "nodejs", + "image": "nodejs" + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx" + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx" + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_app_armor_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "AppArmor", + "restrictedField": "metadata.annotations", + "values": [ + "bogus", + "unconfined" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging", + "annotations": { + "container.apparmor.security.beta.kubernetes.io/": "bogus", + "container.apparmor.security.beta.kubernetes.io/a": "", + "container.apparmor.security.beta.kubernetes.io/b": "runtime/default", + "container.apparmor.security.beta.kubernetes.io/c": "localhost/", + "container.apparmor.security.beta.kubernetes.io/d": "localhost/foo", + "container.apparmor.security.beta.kubernetes.io/e": "unconfined", + "container.apparmor.security.beta.kubernetes.io/f": "unknown" + } + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx" + }, + { + "name": "nodejs", + "image": "nodejs" + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx" + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx" + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Sysctls", check.ID: "sysctls" + +// pod-level: +// - spec.securityContext.sysctls[*].name + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_sysctls(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Sysctls" + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "sysctls": [ + { + "name": "a" + }, + { + "name": "b" + } + ] + }, + "containers": [ + { + "name": "nginx", + "image": "nginx" + }, + { + "name": "nodejs", + "image": "nodejs" + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx" + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx" + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_sysctls_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Sysctls", + "restrictedField": "spec.securityContext.sysctls[*].name", + "values": [ + "a", + "b" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "sysctls": [ + { + "name": "a" + }, + { + "name": "b" + } + ] + }, + "containers": [ + { + "name": "nginx", + "image": "nginx" + }, + { + "name": "nodejs", + "image": "nodejs" + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx" + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx" + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_sysctls_with_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Sysctls", + "restrictedField": "spec.securityContext.sysctls[*].name", + "values": [ + "fdsfds", + "fdfdsdddd" + ] + } + ] + } + } + } + ] + } + } + `) + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "sysctls": [ + { + "name": "kernel.shm_rmid_forced" + }, + { + "name": "b" + } + ] + }, + "containers": [ + { + "name": "nginx", + "image": "nginx" + }, + { + "name": "nodejs", + "image": "nodejs" + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx" + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx" + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Seccomp", check.ID: "seccompProfile_baseline" + +// pod-level: +// - spec.securityContext.seccompProfile.type + +// container-level: +// - spec.containers[*].securityContext.seccompProfile.type +// - spec.initContainers[*].securityContext.seccompProfile.type +// - spec.ephemeralContainers[*].securityContext.seccompProfile.type + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_seccomp(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_seccomp_with_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "restrictedField": "spec.securityContext.seccompProfile.type", + "values": [ + "randomValue1" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.containers[*].securityContext.seccompProfile.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "randomValue2" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.initContainers[*].securityContext.seccompProfile.type", + "images": [ + "nginx" + ], + "values": [ + "randomValue3" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seccompProfile.type", + "images": [ + "nginx" + ], + "values": [ + "randomValue4" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "randomValue1" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Localhost" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "randomValue3" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "randomValue4" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_seccomp_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "restrictedField": "spec.securityContext.seccompProfile.type", + "values": [ + "randomValue1" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.containers[*].securityContext.seccompProfile.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "NotMatchingValue" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.initContainers[*].securityContext.seccompProfile.type", + "images": [ + "nginx" + ], + "values": [ + "randomValue3" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seccompProfile.type", + "images": [ + "nginx" + ], + "values": [ + "randomValue4" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "randomValue1" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "randomValue2" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Localhost" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "randomValue3" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "randomValue4" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_seccomp_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "restrictedField": "spec.securityContext.seccompProfile.type", + "values": [ + "randomValue1" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.containers[*].securityContext.seccompProfile.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "NotMatchingValue" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.initContainers[*].securityContext.seccompProfile.type", + "images": [ + "nginx" + ], + "values": [ + "randomValue3" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "randomValue1" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Localhost" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "randomValue3" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "randomValue4" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// pod-level: +// - spec.securityContext.seLinuxOptions.type + +// container-level: +// Type +// - spec.containers[*].securityContext.seLinuxOptions.type +// - spec.initContainers[*].securityContext.seLinuxOptions.type +// - spec.ephemeralContainers[*].securityContext.seLinuxOptions.type + +// User +// - spec.securityContext.seLinuxOptions.user +// - spec.containers[*].securityContext.seLinuxOptions.user +// - spec.initContainers[*].securityContext.seLinuxOptions.user +// - spec.ephemeralContainers[*].securityContext.seLinuxOptions.user + +// Role +// - spec.securityContext.seLinuxOptions.role +// - spec.containers[*].securityContext.seLinuxOptions.role +// - spec.initContainers[*].securityContext.seLinuxOptions.role +// - spec.ephemeralContainers[*].securityContext.seLinuxOptions.role + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_SELinuxOptions(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seLinuxOptions": { + "type": "foo", + "user": "bar", + "role": "baz" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "type": "foo" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seLinuxOptions": { + "user": "bar" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +// TO DO +// func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_pod_level_restrictedFields(t *testing.T) { +// rawPolicy := []byte(` +// { +// "apiVersion": "kyverno.io/v1", +// "kind": "ClusterPolicy", +// "metadata": { +// "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" +// }, +// "spec": { +// "validationFailureAction": "enforce", +// "rules": [ +// { +// "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", +// "match": { +// "resources": { +// "kinds": [ +// "Pod" +// ], +// "namespaces": [ +// "staging" +// ] +// } +// }, +// "validate": { +// "podSecurity": { +// "level": "baseline", +// "version": "v1.24", +// "exclude": [ +// { +// "controlName": "SELinux", +// "restrictedField": "spec.securityContext.seLinuxOptions.type", +// "values": [ +// "foo" +// ] +// }, +// { +// "controlName": "SELinux", +// "restrictedField": "spec.securityContext.seLinuxOptions.user", +// "values": [ +// "bar" +// ] +// }, +// { +// "controlName": "SELinux", +// "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", +// "images": [ +// "nginx", +// "nodejs" +// ], +// "values": [ +// "foo" +// ] +// }, +// { +// "controlName": "SELinux", +// "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", +// "images": [ +// "nginx" +// ], +// "values": [ +// "baz" +// ] +// }, +// { +// "controlName": "SELinux", +// "restrictedField": "spec.ephemeralContainers[*].securityContext.seLinuxOptions.role", +// "images": [ +// "nginx" +// ], +// "values": [ +// "baz" +// ] +// } +// ] +// } +// } +// } +// ] +// } +// } +// `) + +// rawResource := []byte(` +// { +// "apiVersion": "v1", +// "kind": "Pod", +// "metadata": { +// "name": "nginx-baseline-privileged-container", +// "namespace": "staging" +// }, +// "spec": { +// "hostNetwork": false, +// "securityContext": { +// "seLinuxOptions": { +// "type": "foo", +// "user": "bar" +// } +// }, +// "containers": [ +// { +// "name": "nginx", +// "image": "nginx", +// "securityContext": { +// "seLinuxOptions": { +// "type": "foo" +// } +// } +// }, +// { +// "name": "nodejs", +// "image": "nodejs", +// "securityContext": { +// "seLinuxOptions": { +// "user": "foo" +// } +// } +// } +// ], +// "initContainers": [ +// { +// "name": "init-nginx", +// "image": "nginx", +// "securityContext": { +// "seLinuxOptions": { +// "role": "baz" +// } +// } +// } +// ], +// "ephemeralContainers": [ +// { +// "name": "ephemeral-nginx", +// "image": "nginx", +// "securityContext": { +// "seLinuxOptions": { +// "role": "baz" +// } +// } +// } +// ] +// } +// } +// `) + +// var policy kyverno.ClusterPolicy +// err := json.Unmarshal(rawPolicy, &policy) +// assert.NilError(t, err) + +// resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) +// assert.NilError(t, err) +// er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + +// fmt.Println(er) +// // msgs := []string{""} + +// for _, r := range er.PolicyResponse.Rules { +// fmt.Printf("== Response: %+v\n", r.Message) +// // assert.Equal(t, r.Message, msgs[index]) +// } +// assert.Assert(t, er.IsSuccessful()) +// } + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.type", + "values": [ + "foo" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.user", + "values": [ + "bar" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.role", + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "foo" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seLinuxOptions": { + "type": "foo", + "user": "bar", + "role": "baz" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "type": "foo" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seLinuxOptions": { + "user": "foo" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_with_restrictedFields_only_containers(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "foo" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "type": "foo" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seLinuxOptions": { + "user": "foo" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_with_missing_exclude(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.type", + "values": [ + "randomValue" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.user", + "values": [ + "bar" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.role", + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "foo" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seLinuxOptions": { + "type": "foo", + "user": "bar", + "role": "baz" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "type": "container_t" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seLinuxOptions": { + "user": "bar" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_with_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.type", + "values": [ + "foo" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.user", + "values": [ + "bar" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.securityContext.seLinuxOptions.role", + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "foo" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nginx" + ], + "values": [ + "baz" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seLinuxOptions": { + "type": "foo", + "user": "bar", + "role": "baz" + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "type": "container_t" + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seLinuxOptions": { + "user": "bar" + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_missing_exclude_value_deployment_autogen(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "DaemonSet", + "Deployment", + "Job", + "StatefulSet" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-cronjob-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "CronJob" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx-deployment", + "namespace": "staging", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "image": "nginx", + "name": "nginx", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "initContainers": [ + { + "image": "nodejs", + "name": "init-nodejs", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "init-baz" + } + } + } + ] + } + } + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + if err != nil { + fmt.Printf("=== Error: %+v\n", er) + } + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_missing_exclude_value_daemonset_autogen(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "DaemonSet", + "Deployment", + "Job", + "StatefulSet" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-cronjob-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "CronJob" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "apps/v1", + "kind": "DaemonSet", + "metadata": { + "name": "nginx-daemonset", + "namespace": "staging", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "image": "nginx", + "name": "nginx", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "initContainers": [ + { + "image": "nodejs", + "name": "init-nodejs", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "init-baz" + } + } + } + ] + } + } + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + if err != nil { + fmt.Printf("=== Error: %+v\n", er) + } + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_missing_exclude_value_job_autogen(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "DaemonSet", + "Deployment", + "Job", + "StatefulSet" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-cronjob-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "CronJob" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "nginx-daemonset", + "namespace": "staging", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "image": "nginx", + "name": "nginx", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "initContainers": [ + { + "image": "nodejs", + "name": "init-nodejs", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "init-baz" + } + } + } + ] + } + } + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + if err != nil { + fmt.Printf("=== Error: %+v\n", er) + } + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_missing_exclude_value_statefulset_autogen(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "init-randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "DaemonSet", + "Deployment", + "Job", + "StatefulSet" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-cronjob-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "CronJob" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "apps/v1", + "kind": "StatefulSet", + "metadata": { + "name": "nginx-daemonset", + "namespace": "staging", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "image": "nginx", + "name": "nginx", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "initContainers": [ + { + "image": "nodejs", + "name": "init-nodejs", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "init-baz" + } + } + } + ] + } + } + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + if err != nil { + fmt.Printf("=== Error: %+v\n", er) + } + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_SELinuxOptions_missing_exclude_value_cronjob_autogen(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "init-randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "DaemonSet", + "Deployment", + "Job", + "StatefulSet" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + }, + { + "name": "autogen-cronjob-enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "CronJob" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.containers[*].securityContext.seLinuxOptions.type", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "baz" + ] + }, + { + "controlName": "SELinux", + "restrictedField": "spec.jobTemplate.spec.template.spec.initContainers[*].securityContext.seLinuxOptions.role", + "images": [ + "nodejs" + ], + "values": [ + "randomValue" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "batch/v1", + "kind": "CronJob", + "metadata": { + "name": "cronjob-nginx", + "namespace": "staging" + }, + "spec": { + "schedule": "* * * * *", + "jobTemplate": { + "spec": { + "template": { + "spec": { + "containers": [ + { + "image": "nginx", + "name": "nginx", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "baz" + } + } + } + ], + "initContainers": [ + { + "image": "nodejs", + "name": "init-nodejs", + "resources": {}, + "securityContext": { + "seLinuxOptions": { + "role": "init-baz" + } + } + } + ], + "restartPolicy": "OnFailure" + } + } + } + } + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + if err != nil { + fmt.Printf("=== Error: %+v\n", er) + } + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "HostProcess", check.ID: "windowsHostProcess" +// pod-level: +// - spec.securityContext.windowsOptions.hostProcess + +// container-level: +// - spec.containers[*].securityContext.windowsOptions.hostProcess +// - spec.initContainers[*].securityContext.windowsOptions.hostProcess +// - spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_hostProcesses(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "HostProcess", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostProcesses_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "HostProcess", + "restrictedField": "spec.securityContext.windowsOptions.hostProcess", + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.containers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3", + "nodejs:1.2.3" + ], + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.initContainers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3" + ], + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3" + ], + "values": [ + "true" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostProcesses_missing_exlude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "HostProcess", + "restrictedField": "spec.securityContext.windowsOptions.hostProcess", + "values": [ + "RandomValue" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.containers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3", + "nodejs:1.2.3" + ], + "values": [ + "RandomValue" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.initContainers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3" + ], + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3" + ], + "values": [ + "true" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostProcesses_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "HostProcess", + "restrictedField": "spec.containers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3" + ], + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.initContainers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3" + ], + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "restrictedField": "spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess", + "images": [ + "nginx:1.2.3" + ], + "values": [ + "true" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + }, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Host Namespaces", check.ID: "hostNamespaces" + +// pod-level: +// - spec.securityContext.seLinuxOptions.type + +// container-level: +// - spec.hostNetwork +// - spec.hostPID +// - spec.hostIPC +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_hostNamespaces(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostNamespaces" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostNamespaces", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces" + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": true, + "hostIPC": true, + "hostPID": true + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostNamespaces_with_restrictedFields_and_containers(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostNamespaces" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostNamespaces", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostNetwork", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostIPC", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostPID", + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "images": [ + "nginx:1.2.3", + "nodejs:1.2.3" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": true, + "hostIPC": true, + "hostPID": true, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostNamespaces_with_restrictedFields_and_forbidden_containers(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostNamespaces" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostNamespaces", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostNetwork", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostIPC", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostPID", + "values": [ + "true" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": true, + "hostIPC": true, + "hostPID": true, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostNamespaces_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostNamespaces" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostNamespaces", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostNetwork", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostIPC", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostPID", + "values": [ + "randomValue" + ] + }, + { + "controlName": "HostProcess", + "images": [ + "nginx:1.2.3", + "nodejs:1.2.3" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": true, + "hostIPC": true, + "hostPID": true, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostNamespaces_some_pod_level(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostNamespaces" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostNamespaces", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostNetwork", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostIPC", + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "images": [ + "nginx:1.2.3", + "nodejs:1.2.3" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": true, + "hostIPC": true, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostNamespaces_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostNamespaces" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostNamespaces", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostNetwork", + "values": [ + "true" + ] + }, + { + "controlName": "Host Namespaces", + "restrictedField": "spec.hostIPC", + "values": [ + "true" + ] + }, + { + "controlName": "HostProcess", + "images": [ + "nginx:1.2.3", + "nodejs:1.2.3" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": true, + "hostIPC": true, + "hostPID": true, + "containers": [ + { + "name": "nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + }, + { + "name": "nodejs", + "image": "nodejs:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx:1.2.3", + "securityContext": { + "windowsOptions": { + "hostProcess": true + } + } + } + ] + + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Capabilities", check.ID: "capabilities_baseline" +// pod-level restrictedFields: +// - spec.containers[*].securityContext.capabilities.add +// - spec.initContainers[*].securityContext.capabilities.add +// - spec.ephemeralContainers[*].securityContext.capabilities.add + +// Only ControlName: exclude all restrictedFields for `Capabilities` control for all containers (containers, initContainers, ephemeralContainers) running with images `nginx` +// 1 * Container: nginx +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Pod creation allowed +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_capabilities(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +// Exclude `SYS_ADMIN` and `SYS_TIME` values for `Capabilites` control for all containers (containers, initContainers, ephemeralContainers) running with images `nginx` +// 1 * Container: nginx +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Pod creation allowed +func TestValidate_pod_security_admission_enforce_baseline_exclude_capabilities_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-some-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-some-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "restrictedField": "spec.containers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN", + "SYS_TIME" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.initContainers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN", + "SYS_TIME" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.ephemeralContainers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +// Exclude `SYS_ADMIN` value for `Capabilites` control for all containers (containers, initContainers, ephemeralContainers) running with images `nginx` +// 1 * Container: nginx +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Pod creation forbidden: missing `SYS_TIME` value in exclude +func TestValidate_pod_security_admission_enforce_restricted_exclude_capabilities_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-some-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-some-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "restrictedField": "spec.containers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.initContainers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.ephemeralContainers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// Exclude `SYS_ADMIN`, `SYS_TIME` values for `Capabilites` control for all containers (containers, initContainers, ephemeralContainers) running with images `nginx` +// 1 * Container: nginx +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Pod creation forbidden: missing exclude block for ephemeralContainers: +// +// { +// "controlName": "Capabilities", +// "restrictedField": "spec.ephemeralContainers[*].securityContext.capabilities.add", +// "images": [ +// "nginx" +// ], +// "values": [ +// "SYS_ADMIN", +// "SYS_TIME" +// ] +// } +func TestValidate_pod_security_admission_enforce_restricted_exclude_capabilities_missing_exclude_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-some-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-some-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "restrictedField": "spec.containers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN", + "SYS_TIME" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.initContainers[*].securityContext.capabilities.add", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "add": [ + "SYS_ADMIN", + "SYS_TIME" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Privileged Containers", check.ID: "privileged" + +// container-level: +// - spec.containers[*].securityContext.securityContext.privileged +// - spec.initContainers[*].securityContext.securityContext.privileged +// - spec.ephemeralContainers[*].securityContext.securityContext.privileged + +// Only ControlName: exclude all restrictedFields for `Privileged Containers` control running with images `nginx` and `nodejs` +// 2 * Container: nginx / nodejs +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Pod creation allowed +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_privileged_containers(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-privileged-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-privileged-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privileged Containers", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + // Restricted + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_privileged_containers_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-some-privileged-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-some-privileged-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privileged Containers", + "restrictedField": "spec.containers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Privileged Containers", + "restrictedField": "spec.initContainers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Privileged Containers", + "restrictedField": "spec.ephemeralContainers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + // Restricted + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsSuccessful()) +} + +// Only ControlName: exclude all restrictedFields for `privileged containers` control running with images `nginx` +// 2 * Container: nginx / nodejs +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Pod creation forbidden: missing exclude for container running with `nodejs` image +func TestValidate_pod_security_admission_enforce_baseline_exclude_privileged_containers_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-some-privileged-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-some-privileged-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privileged Containers", + "restrictedField": "spec.containers[*].securityContext.privileged", + "values": [ + "ForbiddenValue" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Privileged Containers", + "restrictedField": "spec.initContainers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Privileged Containers", + "restrictedField": "spec.ephemeralContainers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + // Restricted + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_privileged_containers_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-some-privileged-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-some-privileged-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privileged Containers", + "restrictedField": "spec.containers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Privileged Containers", + "restrictedField": "spec.initContainers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + // Restricted + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +// Exclude spec.containers[*].securityContext.privileged for containers running `nginx` image +// 2 * Container: nginx / nodejs +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Fail -> We have to exclude restrictedFields for initContainers and ephemeralContainers: +// - spec.initContainers[*].securityContext.privileged +// - spec.ephemeralContainers[*].securityContext.privileged + +func TestValidate_pod_security_admission_enforce_restricted_exclude_privileged_containers_missing_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-restricted-exclude-privileged-container-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-restricted-exclude-privileged-container-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "privileged", + "restrictedField": "spec.containers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + // Restricted + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +// Exclude spec.containers[*].securityContext.privileged for containers running `nginx` image +// Exclude spec.initContainers[*].securityContext.privileged for initContainers running `nginx` image +// Exclude spec.ephemeralContainers[*].securityContext.privileged for ephemeralContainers running `nginx` image +// 2 * Container: nginx / nodejs +// 1 * InitContainer: nginx +// 1 * EphemeralContainer: nginx +// Fail -> We have to exclude restrictedFields for containers running with `nodejs` image: +// - spec.containers[*].securityContext.privileged +func TestValidate_pod_security_admission_enforce_restricted_exclude_privileged_containers(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-restricted-exclude-privileged-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-restricted-exclude-privileged-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "privileged", + "restrictedField": "spec.containers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "privileged", + "restrictedField": "spec.initContainers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "privileged", + "restrictedField": "spec.ephemeralContainers[*].securityContext.privileged", + "values": [ + "true" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + // Restricted + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "privileged": true, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + } + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Host Ports", check.ID: "hostPorts" + +// container-level: +// - spec.containers[*].securityContext.ports[*].hostPort +// - spec.initContainers[*].securityContext.ports[*].hostPort +// - spec.ephemeralContainers[*].securityContext.ports[*].hostPort + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_hostPorts(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Ports", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + }, + { + "name": "nodejs", + "image": "nodejs", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostPorts_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Ports", + "restrictedField": "spec.containers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080", + "9000" + ] + }, + { + "controlName": "Host Ports", + "restrictedField": "spec.initContainers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080", + "9000" + ] + }, + { + "controlName": "Host Ports", + "restrictedField": "spec.ephemeralContainers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080", + "9000" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + }, + { + "name": "nodejs", + "image": "nodejs", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_hostPorts_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Ports", + "restrictedField": "spec.containers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080" + ] + }, + { + "controlName": "Host Ports", + "restrictedField": "spec.initContainers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080", + "9000" + ] + }, + { + "controlName": "Host Ports", + "restrictedField": "spec.ephemeralContainers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080", + "9000" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + }, + { + "name": "nodejs", + "image": "nodejs", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_hostPorts_missing_exclude_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostPorts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "Host Ports", + "restrictedField": "spec.containers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080", + "9000" + ] + }, + { + "controlName": "Host Ports", + "restrictedField": "spec.initContainers[*].ports[*].hostPort", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "8080", + "9000" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + }, + { + "name": "nodejs", + "image": "nodejs", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "ports": [ + { + "hostPort": 8080 + }, + { + "hostPort": 9000 + } + ] + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "/proc Mount Type", check.ID: "procMount" + +// container-level: +// - spec.containers[*].securityContext.procMount +// - spec.initContainers[*].securityContext.procMount +// - spec.ephemeralContainers[*].securityContext.procMount + +func TestValidate_pod_security_admission_enforce_baseline_exclude_all_procMounts(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "/proc Mount Type", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "ProcMount": "Other" + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_procMounts_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.containers[*].securityContext.procMount", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "Unmasked", + "Other" + ] + }, + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.initContainers[*].securityContext.procMount", + "images": [ + "nginx" + ], + "values": [ + "Unmasked" + ] + }, + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.ephemeralContainers[*].securityContext.procMount", + "images": [ + "nginx" + ], + "values": [ + "Unmasked" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "ProcMount": "Other" + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_procMounts_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.containers[*].securityContext.procMount", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "Unmasked" + ] + }, + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.initContainers[*].securityContext.procMount", + "images": [ + "nginx" + ], + "values": [ + "Unmasked" + ] + }, + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.ephemeralContainers[*].securityContext.procMount", + "images": [ + "nginx" + ], + "values": [ + "Unmasked" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "ProcMount": "Other" + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_baseline_exclude_procMounts_missing_exclude_RestrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-procMounts-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "baseline", + "version": "v1.24", + "exclude": [ + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.containers[*].securityContext.procMount", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "Unmasked", + "Other" + ] + }, + { + "controlName": "/proc Mount Type", + "restrictedField": "spec.initContainers[*].securityContext.procMount", + "images": [ + "nginx" + ], + "values": [ + "Unmasked" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "ProcMount": "Other" + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "ProcMount": "Unmasked" + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// ====== Restricted ====== + +// === Control: "Volumes Types", check.ID: "restrictedVolumes" + +// pod-level: +// - spec.volumes[*] + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_volume_types(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Volume Types" + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "aws-volume", + "AWSElasticBlockStore": { + "volumeID": "id", + "fsType": "ext4" + } + }, + { + "name": "gcp-volume", + "GCEPersistentDisk": { + "pdName": "my-data-disk", + "fsType": "ext4" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_volume_types_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Volume Types", + "restrictedField": "spec.volumes[*]", + "values": [ + "spec.volumes[*].awsElasticBlockStore", + "spec.volumes[*].gcePersistentDisk" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "aws-volume", + "AWSElasticBlockStore": { + "volumeID": "id", + "fsType": "ext4" + } + }, + { + "name": "gcp-volume", + "GCEPersistentDisk": { + "pdName": "my-data-disk", + "fsType": "ext4" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_volume_types_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Volume Types", + "restrictedField": "spec.volumes[*]", + "values": [ + "spec.volumes[*].awsElasticBlockStore", + "spec.volumes[*].cephfs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "aws-volume", + "AWSElasticBlockStore": { + "volumeID": "id", + "fsType": "ext4" + } + }, + { + "name": "gcp-volume", + "GCEPersistentDisk": { + "pdName": "my-data-disk", + "fsType": "ext4" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_volume_types_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Containers", + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "volumes": [ + { + "name": "aws-volume", + "AWSElasticBlockStore": { + "volumeID": "id", + "fsType": "ext4" + } + }, + { + "name": "gcp-volume", + "GCEPersistentDisk": { + "pdName": "my-data-disk", + "fsType": "ext4" + } + } + ], + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Running as Non-root user", check.ID: "runAsUser" +// pod-level: +// - spec.securityContext.runAsUser + +// container-level: +// - spec.containers[*].securityContext.runAsUser +// - spec.initContainers[*].securityContext.runAsUser +// - spec.ephemeralContainers[*].securityContext.runAsUser + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root_user(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Running as Non-root user", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "runAsUser": 1, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root_user_with_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.23", + "exclude": [ + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.securityContext.runAsUser", + "values": [ + "0" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.containers[*].securityContext.runAsUser", + "values": [ + "0" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.initContainers[*].securityContext.runAsUser", + "values": [ + "0" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.ephemeralContainers[*].securityContext.runAsUser", + "values": [ + "0" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root_user_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.23", + "exclude": [ + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.securityContext.runAsUser", + "values": [ + "0" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.containers[*].securityContext.runAsUser", + "values": [ + "1" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.initContainers[*].securityContext.runAsUser", + "values": [ + "0" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.ephemeralContainers[*].securityContext.runAsUser", + "values": [ + "0" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + }, + { + "name": "init-nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 1, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root_user_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.23", + "exclude": [ + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.securityContext.runAsUser", + "values": [ + "0" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.initContainers[*].securityContext.runAsUser", + "values": [ + "0" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Running as Non-root user", + "restrictedField": "spec.ephemeralContainers[*].securityContext.runAsUser", + "values": [ + "0" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "runAsUser": 0, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Running as Non-root", check.ID: "runAsNonRoot" + +// pod-level: +// - spec.securityContext.runAsNonRoot + +// container-level: +// - spec.containers[*].securityContext.runAsNonRoot +// - spec.initContainers[*].securityContext.runAsNonRoot +// - spec.ephemeralContainers[*].securityContext.runAsNonRoot + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Running as Non-root", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root_with_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Running as Non-root", + "restrictedField": "spec.securityContext.runAsNonRoot", + "values": [ + "false" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.containers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.initContainers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.ephemeralContainers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Running as Non-root", + "restrictedField": "spec.securityContext.runAsNonRoot", + "values": [ + "false" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.containers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.initContainers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.ephemeralContainers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_running_as_non_root_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Running as Non-root", + "restrictedField": "spec.securityContext.runAsNonRoot", + "values": [ + "false" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.containers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nodejs" + ] + }, + { + "controlName": "Running as Non-root", + "restrictedField": "spec.ephemeralContainers[*].securityContext.runAsNonRoot", + "values": [ + "false" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": false, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Seccomp", check.ID: "seccompProfile_restricted" +// pod-level: +// - spec.securityContext.seccompProfile.type + +// container-level: +// - spec.containers[*].securityContext.seccompProfile.type +// - spec.initContainers[*].securityContext.seccompProfile.type +// - spec.ephemeralContainers[*].securityContext.seccompProfile.type + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_seccomp(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_seccomp_with_exclude(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "restrictedField": "spec.securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.containers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.initContainers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginx" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_seccomp_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "restrictedField": "spec.securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.containers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.initContainers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginxImageNotMatching" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.ephemeralContainers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_seccomp_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-hostProcesses-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Seccomp", + "restrictedField": "spec.securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.containers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginx", + "nodejs" + ] + }, + { + "controlName": "Seccomp", + "restrictedField": "spec.initContainers[*].securityContext.seccompProfile.type", + "values": [ + "Unconfined" + ], + "images": [ + "nginx" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "Unconfined" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Privilege Escalation", check.ID: "allowPrivilegeEscalation" + +// container-level: +// - spec.containers[*].securityContext.allowPrivilegeEscalation +// - spec.initContainers[*].securityContext.allowPrivilegeEscalation +// - spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_privilege_escalations(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Escalation", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_privilege_escalations_with_restrictedFields(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.containers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "true" + ] + }, + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.initContainers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx" + ], + "values": [ + "true" + ] + }, + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx" + ], + "values": [ + "true" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_privilege_escalations_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.containers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx" + ], + "values": [ + "true" + ] + }, + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.initContainers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx" + ], + "values": [ + "true" + ] + }, + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx" + ], + "values": [ + "true" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_privilege_escalations_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-baseline-exclude-all-privilege_escalations-all-containers-nginx-nodejs", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.initContainers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx" + ], + "values": [ + "true" + ] + }, + { + "controlName": "Privilege Escalation", + "restrictedField": "spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation", + "images": [ + "nginx" + ], + "values": [ + "true" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +// === Control: "Capabilities", check.ID: "capabilities_restricted" +// container-level: +// - spec.containers[*].securityContext.capabilities.drop +// - spec.initContainers[*].securityContext.capabilities.drop +// - spec.ephemeralContainers[*].securityContext.capabilities.drop +// - spec.containers[*].securityContext.capabilities.add +// - spec.initContainers[*].securityContext.capabilities.add +// - spec.ephemeralContainers[*].securityContext.capabilities.add + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_capabilities(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "images": [ + "nginx", + "nodejs" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "add": [ + "SYS_TIME" + ], + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "add": [ + "SYS_TIME" + ], + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "add": [ + "SYS_TIME" + ], + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "add": [ + "SYS_TIME" + ], + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_capabilities_with_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "restrictedField": "spec.containers[*].securityContext.capabilities.drop", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "SYS_ADMIN" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.initContainers[*].securityContext.capabilities.drop", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.ephemeralContainers[*].securityContext.capabilities.drop", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsSuccessful()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_capabilities_missing_exclude_value(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "restrictedField": "spec.containers[*].securityContext.capabilities.drop", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "SYS_TIME" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.initContainers[*].securityContext.capabilities.drop", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.ephemeralContainers[*].securityContext.capabilities.drop", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + +func TestValidate_pod_security_admission_enforce_restricted_exclude_all_capabilities_missing_restrictedField(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx" + }, + "spec": { + "validationFailureAction": "enforce", + "rules": [ + { + "name": "enforce-restricted-exclude-all-capabilities-all-containers-nginx", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "staging" + ] + } + }, + "validate": { + "podSecurity": { + "level": "restricted", + "version": "v1.24", + "exclude": [ + { + "controlName": "Capabilities", + "restrictedField": "spec.containers[*].securityContext.capabilities.drop", + "images": [ + "nginx", + "nodejs" + ], + "values": [ + "SYS_ADMIN" + ] + }, + { + "controlName": "Capabilities", + "restrictedField": "spec.initContainers[*].securityContext.capabilities.drop", + "images": [ + "nginx" + ], + "values": [ + "SYS_ADMIN" + ] + } + ] + } + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "nginx-baseline-privileged-container", + "namespace": "staging" + }, + "spec": { + "hostNetwork": false, + "containers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + }, + { + "name": "nodejs", + "image": "nodejs", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "initContainers": [ + { + "name": "init-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ], + "ephemeralContainers": [ + { + "name": "ephemeral-nginx", + "image": "nginx", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + }, + "capabilities": { + "drop": [ + "SYS_ADMIN" + ] + }, + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + } + } + ] + } + } + `) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + + fmt.Println(er) + // msgs := []string{""} + + for _, r := range er.PolicyResponse.Rules { + fmt.Printf("== Response: %+v\n", r.Message) + // assert.Equal(t, r.Message, msgs[index]) + } + assert.Assert(t, er.IsFailed()) +} + func Test_block_bypass(t *testing.T) { testcases := []testCase{ { diff --git a/pkg/policy/validate/validate.go b/pkg/policy/validate/validate.go index b567989757..aca82b0532 100644 --- a/pkg/policy/validate/validate.go +++ b/pkg/policy/validate/validate.go @@ -94,6 +94,10 @@ func validationElemCount(v *kyvernov1.Validation) int { count++ } + if v.PodSecurity != nil { + count++ + } + if v.Manifests != nil && len(v.Manifests.Attestors) != 0 { count++ } diff --git a/pkg/pss/evaluate.go b/pkg/pss/evaluate.go new file mode 100644 index 0000000000..bcbbb9ade1 --- /dev/null +++ b/pkg/pss/evaluate.go @@ -0,0 +1,368 @@ +package pss + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + enginectx "github.com/kyverno/kyverno/pkg/engine/context" + "github.com/kyverno/kyverno/pkg/utils" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/pod-security-admission/api" + "k8s.io/pod-security-admission/policy" +) + +func FormatChecksPrint(checks []PSSCheckResult) string { + var str string + for _, check := range checks { + str += fmt.Sprintf("(%+v)\n", check.CheckResult) + } + return str +} + +// Evaluate Pod's specified containers only and get PSSCheckResults +func evaluatePSS(level *api.LevelVersion, pod *corev1.Pod) (results []PSSCheckResult) { + checks := policy.DefaultChecks() + + for _, check := range checks { + if level.Level == api.LevelBaseline && check.Level != level.Level { + continue + } + // check version + for _, versionCheck := range check.Versions { + checkResult := versionCheck.CheckPod(&pod.ObjectMeta, &pod.Spec) + // Append only if the checkResult is not already in PSSCheckResults + if !checkResult.Allowed { + results = append(results, PSSCheckResult{ + ID: check.ID, + CheckResult: checkResult, + RestrictedFields: getRestrictedFields(check), + }) + } + } + } + return results +} + +// When we specify the controlName only we want to exclude all restrictedFields for this control. +// Remove all PSSChecks related to this control +func trimExemptedChecks(pssChecks []PSSCheckResult, rule *kyvernov1.PodSecurity) []PSSCheckResult { + // Keep in memory the number of checks that have been removed + // to avoid panics when removing a new check. + removedChecks := 0 + for checkIndex, check := range pssChecks { + for _, exclude := range rule.Exclude { + // Translate PSS control to check_id and remove it from PSSChecks if it's specified in exclude block + for _, CheckID := range PSS_controls_to_check_id[exclude.ControlName] { + if check.ID == CheckID && exclude.RestrictedField == "" && checkIndex <= len(pssChecks) { + index := checkIndex - removedChecks + pssChecks = append(pssChecks[:index], pssChecks[index+1:]...) + removedChecks++ + } + } + } + } + return pssChecks +} + +func forbiddenValuesExempted(ctx enginectx.Interface, pod *corev1.Pod, check PSSCheckResult, exclude kyvernov1.PodSecurityStandard, restrictedField string) (bool, error) { + if err := enginectx.AddJSONObject(ctx, pod); err != nil { + return false, errors.Wrap(err, "failed to add podSpec to engine context") + } + value, err := ctx.Query(restrictedField) + if err != nil { + return false, errors.Wrap(err, fmt.Sprintf("failed to query value with the given path %s", exclude.RestrictedField)) + } + if !allowedValues(value, exclude, PSS_controls[check.ID]) { + return false, nil + } + return true, nil +} + +func checkContainer(ctx enginectx.Interface, pod *corev1.Pod, check PSSCheckResult, exclude []kyvernov1.PodSecurityStandard, restrictedField restrictedField, containerName string, containerTypePrefix string) (bool, error) { + matchedOnce := false + // Container.Name with double quotes + formatedContainerName := fmt.Sprintf(`"%s"`, containerName) + if !strings.Contains(check.CheckResult.ForbiddenDetail, formatedContainerName) { + return true, nil + } + for _, exclude := range exclude { + if !strings.Contains(exclude.RestrictedField, containerTypePrefix) { + continue + } + + // Get values of this container only. + // spec.containers[*].securityContext.privileged -> spec.containers[?name=="nginx"].securityContext.privileged + newRestrictedField := strings.Replace(restrictedField.path, "*", fmt.Sprintf(`?name=='%s'`, containerName), 1) + + // No need to check if exclude.Images contains container.Image + // Since we only have containers matching the exclude.images with getPodWithMatchingContainers() + exempted, err := forbiddenValuesExempted(ctx, pod, check, exclude, newRestrictedField) + if err != nil || !exempted { + return false, nil + } + matchedOnce = true + } + // If container name is in check.Forbidden but isn't exempted by an exclude then pod creation is forbidden + if strings.Contains(check.CheckResult.ForbiddenDetail, formatedContainerName) && !matchedOnce { + return false, nil + } + return true, nil +} + +func checkContainerLevelFields(ctx enginectx.Interface, pod *corev1.Pod, check PSSCheckResult, exclude []kyvernov1.PodSecurityStandard, restrictedField restrictedField) (bool, error) { + if strings.Contains(restrictedField.path, "spec.containers[*]") { + for _, container := range pod.Spec.Containers { + allowed, err := checkContainer(ctx, pod, check, exclude, restrictedField, container.Name, "spec.containers[*]") + if err != nil || !allowed { + return false, nil + } + } + } + if strings.Contains(restrictedField.path, "spec.initContainers[*]") { + for _, container := range pod.Spec.InitContainers { + allowed, err := checkContainer(ctx, pod, check, exclude, restrictedField, container.Name, "spec.initContainers[*]") + if err != nil || !allowed { + return false, nil + } + } + } + if strings.Contains(restrictedField.path, "spec.ephemeralContainers[*]") { + for _, container := range pod.Spec.EphemeralContainers { + allowed, err := checkContainer(ctx, pod, check, exclude, restrictedField, container.Name, "spec.ephemeralContainers[*]") + if err != nil || !allowed { + return false, nil + } + } + } + return true, nil +} + +func checkHostNamespacesControl(check PSSCheckResult, restrictedField string) bool { + hostNamespace := strings.Trim(restrictedField, "spec.") + return strings.Contains(check.CheckResult.ForbiddenDetail, hostNamespace) +} + +func checkPodLevelFields(ctx enginectx.Interface, pod *corev1.Pod, check PSSCheckResult, rule *kyvernov1.PodSecurity, restrictedField restrictedField) (bool, error) { + // Specific checks for controls with multiple pod-level restrictedFields + // TO DO: SELinux control + if check.ID == "hostNamespaces" { + if !checkHostNamespacesControl(check, restrictedField.path) { + return true, nil + } + } + matchedOnce := false + for _, exclude := range rule.Exclude { + if !strings.Contains(exclude.RestrictedField, restrictedField.path) { + continue + } + + exempted, err := forbiddenValuesExempted(ctx, pod, check, exclude, exclude.RestrictedField) + if err != nil || !exempted { + return false, nil + } + matchedOnce = true + } + if !matchedOnce { + return false, nil + } + return true, nil +} + +func exemptProfile(checks []PSSCheckResult, rule *kyvernov1.PodSecurity, pod *corev1.Pod) (bool, error) { + ctx := enginectx.NewContext() + + // 1. Iterate over check.RestrictedFields + // 2. Check if it's a `container-level` or `pod-level` restrictedField + // - `container-level`: container has a disallowed check (container name in check.ForbiddenDetail) && exempted by an exclude rule ? continue : pod creation is forbbiden + // - `pod-level`: Exempted by an exclude rule ? good : pod creation is forbbiden + for _, check := range checks { + for _, restrictedField := range check.RestrictedFields { + // Is a container-level restrictedField + + // RestrictedField.path can contain: + // - containers[*] + // - initContainers[*] + // - ephemeralContainers[*] + // So we check if it contains `ontainers[*]` to know if there is a CheckResult related to containers. + if strings.Contains(restrictedField.path, "ontainers[*]") { + allowed, err := checkContainerLevelFields(ctx, pod, check, rule.Exclude, restrictedField) + if err != nil { + return false, errors.Wrap(err, err.Error()) + } + if !allowed { + return false, nil + } + } else { + // Is a pod-level restrictedField + if !strings.HasPrefix(check.CheckResult.ForbiddenDetail, "pod") && containsContainerLevelControl(check.RestrictedFields) { + continue + } + allowed, err := checkPodLevelFields(ctx, pod, check, rule, restrictedField) + if err != nil { + return false, errors.Wrap(err, err.Error()) + } + if !allowed { + return false, nil + } + } + } + } + return true, nil +} + +// Check if the pod creation is allowed after exempting some PSS controls +func EvaluatePod(rule *kyvernov1.PodSecurity, pod *corev1.Pod, level *api.LevelVersion) (bool, []PSSCheckResult, error) { + // 1. Evaluate containers that match images specified in exclude + podWithMatchingContainers := getPodWithMatchingContainers(rule.Exclude, pod) + pssChecks := evaluatePSS(level, &podWithMatchingContainers) + pssChecks = trimExemptedChecks(pssChecks, rule) + + // 2. Check if all PSSCheckResults are exempted by exclude values + allowed, err := exemptProfile(pssChecks, rule, &podWithMatchingContainers) + if err != nil { + return false, pssChecks, err + } + // Good to have: remove checks that are exempted and return only forbidden ones + if !allowed { + return false, pssChecks, nil + } + + // 3. Check the remaining containers + podWithNotMatchingContainers := getPodWithNotMatchingContainers(rule.Exclude, pod, &podWithMatchingContainers) + pssChecks = evaluatePSS(level, &podWithNotMatchingContainers) + if len(pssChecks) > 0 { + return false, pssChecks, nil + } + return true, pssChecks, nil +} + +func appendAllowedValues(controls []restrictedField, exclude *kyvernov1.PodSecurityStandard) { + for _, control := range controls { + if control.path == exclude.RestrictedField { + for _, allowedValue := range control.allowedValues { + switch v := allowedValue.(type) { + case string: + if !utils.ContainsString(exclude.Values, v) { + exclude.Values = append(exclude.Values, v) + } + } + } + } + } +} + +func allowedValuesSlice(excludeValues []interface{}, exclude kyvernov1.PodSecurityStandard) bool { + for _, values := range excludeValues { + v := reflect.TypeOf(values) + switch v.Kind() { + case reflect.Slice: + for _, value := range values.([]interface{}) { + if reflect.TypeOf(value).Kind() == reflect.Float64 { + if !utils.ContainsString(exclude.Values, fmt.Sprintf("%.f", value)) { + return false + } + } else if reflect.TypeOf(value).Kind() == reflect.String { + if !utils.ContainsString(exclude.Values, value.(string)) { + return false + } + } + } + case reflect.Map: + for key, value := range values.(map[string]interface{}) { + if exclude.RestrictedField == "spec.volumes[*]" { + if key == "name" { + continue + } + matchedOnce := false + for _, excludeValue := range exclude.Values { + // Remove `spec.volumes[*].` prefix + if strings.TrimPrefix(excludeValue, "spec.volumes[*].") == key { + matchedOnce = true + } + } + if !matchedOnce { + return false + } + } + // "HostPath volume" control: check the path of the hostPath volume since the type is optional + // volumes: + // - name: test-volume + // hostPath: + // # directory location on host + // path: /data <--- Check the path + // # this field is optional + // type: Directory + if exclude.RestrictedField == "spec.volumes[*].hostPath" { + if key != "path" { + continue + } + if !utils.ContainsString(exclude.Values, value.(string)) { + return false + } + } + } + case reflect.String: + if !utils.ContainsString(exclude.Values, values.(string)) { + return false + } + + case reflect.Bool: + if !utils.ContainsString(exclude.Values, strconv.FormatBool(values.(bool))) { + return false + } + case reflect.Float64: + if !utils.ContainsString(exclude.Values, fmt.Sprintf("%.f", values)) { + return false + } + } + } + return true +} + +func allowedValues(resourceValue interface{}, exclude kyvernov1.PodSecurityStandard, controls []restrictedField) bool { + appendAllowedValues(controls, &exclude) + + v := reflect.TypeOf(resourceValue) + switch v.Kind() { + case reflect.Bool: + if !utils.ContainsString(exclude.Values, strconv.FormatBool(resourceValue.(bool))) { + return false + } + return true + case reflect.String: + if !utils.ContainsString(exclude.Values, resourceValue.(string)) { + return false + } + return true + case reflect.Float64: + if !utils.ContainsString(exclude.Values, fmt.Sprintf("%.f", resourceValue)) { + return false + } + return true + case reflect.Map: + // `AppArmor` control + for key, value := range resourceValue.(map[string]interface{}) { + if !strings.Contains(key, "container.apparmor.security.beta.kubernetes.io/") { + continue + } + // For allowed value: "localhost/*" + if strings.Contains(value.(string), "localhost/") { + continue + } + if !utils.ContainsString(exclude.Values, value.(string)) { + return false + } + } + return true + case reflect.Slice: + exempted := allowedValuesSlice(resourceValue.([]interface{}), exclude) + if !exempted { + return false + } + } + return true +} diff --git a/pkg/pss/mapping.go b/pkg/pss/mapping.go new file mode 100644 index 0000000000..9827ea17c0 --- /dev/null +++ b/pkg/pss/mapping.go @@ -0,0 +1,600 @@ +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 + "Capabilities": { + "capabilities_baseline", + "capabilities_restricted", + }, + "Seccomp": { + "seccompProfile_baseline", + "seccompProfile_restricted", + }, + + // === Baseline + // Container-level controls + "Privileged Containers": { + "privileged", + }, + "Host Ports": { + "hostPorts", + }, + "/proc Mount Type": { + "procMount", + }, + "procMount": { + "hostPorts", + }, + + // 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 and pod-level controls + "Privilege Escalation": { + "allowPrivilegeEscalation", + }, + "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 new file mode 100644 index 0000000000..2af4a77fa7 --- /dev/null +++ b/pkg/pss/utils.go @@ -0,0 +1,144 @@ +package pss + +import ( + "strings" + + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/utils" + corev1 "k8s.io/api/core/v1" + "k8s.io/pod-security-admission/policy" +) + +func containsContainer(containers interface{}, containerName string) bool { + switch v := containers.(type) { + case []interface{}: + for _, container := range v { + switch v := container.(type) { + case corev1.Container: + if v.Name == containerName { + return true + } + case corev1.EphemeralContainer: + if v.Name == containerName { + return true + } + } + } + case []corev1.Container: + for _, container := range v { + if container.Name == containerName { + return true + } + } + case []corev1.EphemeralContainer: + for _, container := range v { + if container.Name == containerName { + return true + } + } + } + return false +} + +// Get copy of pod with containers (containers, initContainers, ephemeralContainers) matching the exclude.image +func getPodWithMatchingContainers(exclude []kyvernov1.PodSecurityStandard, pod *corev1.Pod) (podCopy corev1.Pod) { + podCopy = *pod + podCopy.Spec.Containers = []corev1.Container{} + podCopy.Spec.InitContainers = []corev1.Container{} + podCopy.Spec.EphemeralContainers = []corev1.EphemeralContainer{} + + for _, container := range pod.Spec.Containers { + for _, excludeRule := range exclude { + // Ignore all restrictedFields when we only specify the `controlName` with no `restrictedField` + controlNameOnly := excludeRule.RestrictedField == "" + if !utils.ContainsString(excludeRule.Images, container.Image) { + continue + } + if strings.Contains(excludeRule.RestrictedField, "spec.containers[*]") || controlNameOnly { + // Add to matchingContainers if either it's empty or is unique + if len(podCopy.Spec.Containers) == 0 || !containsContainer(podCopy.Spec.Containers, container.Name) { + podCopy.Spec.Containers = append(podCopy.Spec.Containers, container) + } + } + } + } + for _, container := range pod.Spec.InitContainers { + for _, excludeRule := range exclude { + // Ignore all restrictedFields when we only specify the `controlName` with no `restrictedField` + controlNameOnly := excludeRule.RestrictedField == "" + if !utils.ContainsString(excludeRule.Images, container.Image) { + continue + } + if strings.Contains(excludeRule.RestrictedField, "spec.initContainers[*]") || controlNameOnly { + // Add to matchingContainers if either it's empty or is unique + if len(podCopy.Spec.InitContainers) == 0 || !containsContainer(podCopy.Spec.InitContainers, container.Name) { + podCopy.Spec.InitContainers = append(podCopy.Spec.InitContainers, container) + } + } + } + } + for _, container := range pod.Spec.EphemeralContainers { + for _, excludeRule := range exclude { + // Ignore all restrictedFields when we only specify the `controlName` with no `restrictedField` + controlNameOnly := excludeRule.RestrictedField == "" + if !utils.ContainsString(excludeRule.Images, container.Image) { + continue + } + if strings.Contains(excludeRule.RestrictedField, "spec.ephemeralContainers[*]") || controlNameOnly { + // Add to matchingContainers if either it's empty or is unique + if len(podCopy.Spec.EphemeralContainers) == 0 || !containsContainer(podCopy.Spec.EphemeralContainers, container.Name) { + podCopy.Spec.EphemeralContainers = append(podCopy.Spec.EphemeralContainers, container) + } + } + } + } + return podCopy +} + +// Get containers NOT matching images specified in Exclude values +func getPodWithNotMatchingContainers(exclude []kyvernov1.PodSecurityStandard, pod *corev1.Pod, podWithMatchingContainers *corev1.Pod) (podCopy corev1.Pod) { + // Only copy containers because we have already evaluated the pod-level controls + // e.g.: spec.securityContext.hostProcess + podCopy.Spec.Containers = []corev1.Container{} + podCopy.Spec.InitContainers = []corev1.Container{} + podCopy.Spec.EphemeralContainers = []corev1.EphemeralContainer{} + + // Append containers that are not in podWithMatchingContainers already evaluated in EvaluatePod() + for _, container := range pod.Spec.Containers { + if !containsContainer(podWithMatchingContainers.Spec.Containers, container.Name) { + podCopy.Spec.Containers = append(podCopy.Spec.Containers, container) + } + } + for _, container := range pod.Spec.InitContainers { + if !containsContainer(podWithMatchingContainers.Spec.InitContainers, container.Name) { + podCopy.Spec.InitContainers = append(podCopy.Spec.InitContainers, container) + } + } + for _, container := range pod.Spec.EphemeralContainers { + if !containsContainer(podWithMatchingContainers.Spec.EphemeralContainers, container.Name) { + podCopy.Spec.EphemeralContainers = append(podCopy.Spec.EphemeralContainers, container) + } + } + return podCopy +} + +// 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 containsContainerLevelControl(restrictedFields []restrictedField) bool { + for _, restrictedField := range restrictedFields { + if strings.Contains(restrictedField.path, "ontainers[*]") { + return true + } + } + return false +} diff --git a/test/policy/validate/enforce-baseline-exclude-selinuxoptions.yaml b/test/policy/validate/enforce-baseline-exclude-selinuxoptions.yaml new file mode 100644 index 0000000000..f7df5e35a7 --- /dev/null +++ b/test/policy/validate/enforce-baseline-exclude-selinuxoptions.yaml @@ -0,0 +1,32 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: enforce-baseline-exclude-se-linux-options +spec: + validationFailureAction: enforce + rules: + - name: enforce-baseline-exclude-se-linux-options + match: + any: + - resources: + kinds: + - Pod + namespaces: + - privileged-pss-with-kyverno + validate: + podSecurity: + level: baseline + version: v1.24 + exclude: + - controlName: "SELinux" + restrictedField: spec.containers[*].securityContext.seLinuxOptions.role + images: + - nginx + values: + - baz + - controlName: "SELinux" + restrictedField: spec.initContainers[*].securityContext.seLinuxOptions.role + images: + - nodejs + values: + - init-bazo \ No newline at end of file