1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 02:18:15 +00:00

Extend Pod Security Admission (#4364)

* init commit for pss

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* add test for Volume Type control

* add test for App Armor control except ExemptProfile. Fix PSS profile check in EvaluatePSS()

* remove unused code, still a JMESPATH problem with app armor ExemptProfile()

* test for Host Process / Host Namespaces controls

* test for Privileged containers controls

* test for HostPathVolume control

* test for HostPorts control

* test for HostPorts control

* test for SELinux control

* test for Proc mount type control

* Set to baseline

* test for Seccomp control

* test for Sysctl control

* test for Privilege escalation control

* test for Run as non root control

* test for Restricted Seccomp control

* Add problems to address

* add solutions to problems

* Add validate rule for PSA

* api.Version --> string. latest by default

* Exclude all values for a restrictedField

* add tests for kyverno engine

* code to be used to match kyverno rule's namespace

* Refacto pkg/pss

* fix multiple problems: not matching containers, add contains methods, select the right container when we have the same exclude.RestrictedField for multiple containers:

* EvaluatePod

* Use EvaluatePod in kyverno engine

* Set pod instead of container in context to use full Jmespath. e.g.: securityContext.capabilities.add --> spec.containers[*].securityContext.capabilities.add

* Check if PSSCheckResult matched at least one exclude value

* add tests for engine

* fix engine validation test

* config

* update go.mod and go.sum

* crds

* Check validate value: add PodSecurity

* exclude all restrictedFields when we only specify the controlName

* ExemptProfile(): check if exclud.RestrictedField matches at least one restrictedField.path

* handle containers, initContainers, ephemeralContainers when we only specify the controlName (all restrictedFields are excluded)

* refacto pks/pss/evaluate.go and add pkg/engine/validation_test.go

* add all controls with containers in restrictedFields as comments

* add tests for capabilities and privileged containers and fix some errors

* add tests for host ports control

* add tests for proc mount control

* add tests for privilege escalation control

* add tests for capabilities control

* remove comments

* new algo

* refacto algo, working. Add test for hostProcess control

* remove unused code

* fix getPodWithNotMatchingContainers(), add tests for host namespaces control

* refacto ExemptProfile()

* get values for a specific container. add test for SELinuxOptions control

* fix allowedValues for SELinuxOptions

* add tests for seccompProfile_baseline control

* refacto checkContainers(), add test for seccomp control

* add test for running as non root control

* add some tests for runAsUser control, have to update current PSA version

* add sysctls control

* add allowed values for restrictedVolumes control

* add some tests for appArmor, volume types controls

* add tests for volume types control

* add tests for hostPath volume control

* finish merge conflicts and add tests for runAsUser

* update charts and crds

* exclude.images optional

* change volume types control exclude values

* add appAmor control

* fix: did not match any exclude value for pod-level restrictedFields

* create autogen for validate.PodSecurity

* clean code, remove logs

* fix sonatype lift errors

* fix sonatype lift errors: duplication

* fix crash in pkg/policy/validate/ tests and unmarshall errors for pkg/engine tests

* beginning of autogen implement for validate.exclude

* Autogen for validation.PodSecurity

* working autogen with simple tests

* change validate.PodSecurity failure response format

* make codegen

* fix lint errors, remove debug prints

* fix tags

* fix tags

* fix crash when deleting pods matching validate.podSecurity rule. Only check validatePodSecurity() when it's not a delete request

* Changes requested

* Changes requested 2

* Changes requested 3

* Changes requested 4

* Changes requested and make codegen

* fix host namespaces control

* fix lint

* fix codegen error

* update docs/crd/v1/index.html

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix path

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* update crd schema

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* update charts/kyverno/templates/crds.yaml

Signed-off-by: ShutingZhao <shuting@nirmata.com>

Signed-off-by: ShutingZhao <shuting@nirmata.com>
Co-authored-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
ToLToL 2022-08-31 11:16:31 +02:00 committed by GitHub
parent a53ad6a5dd
commit 1b9a2fca21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 14630 additions and 6 deletions

View file

@ -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

View file

@ -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{}

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

254
config/install_debug.yaml Executable file → Normal file
View file

@ -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

View file

@ -2528,6 +2528,132 @@ github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest.ObjectReferenceList
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.PodSecurity">PodSecurity
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Validation">Validation</a>)
</p>
<p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>level</code></br>
<em>
k8s.io/pod-security-admission/api.Level
</em>
</td>
<td>
<p>Level defines the Pod Security Standard level to be applied to workloads.
Allowed values are privileged, baseline, and restricted.</p>
</td>
</tr>
<tr>
<td>
<code>version</code></br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
</td>
</tr>
<tr>
<td>
<code>exclude</code></br>
<em>
<a href="#kyverno.io/v1.PodSecurityStandard">
[]PodSecurityStandard
</a>
</em>
</td>
<td>
<p>Exclude specifies the Pod Security Standard controls to be excluded.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.PodSecurityStandard">PodSecurityStandard
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.PodSecurity">PodSecurity</a>)
</p>
<p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>controlName</code></br>
<em>
string
</em>
</td>
<td>
<p>ControlName specifies the name of the Pod Security Standard control.
See: <a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/">https://kubernetes.io/docs/concepts/security/pod-security-standards/</a></p>
</td>
</tr>
<tr>
<td>
<code>images</code></br>
<em>
[]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Images is a list of matching image patterns.
Each image is the image name consisting of the registry address, repository, image, and tag.</p>
</td>
</tr>
<tr>
<td>
<code>restrictedField</code></br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>RestrictedField selects the field for the given Pod Security Standard control.
When not set, all restricted fields for the control are selected.</p>
</td>
</tr>
<tr>
<td>
<code>values</code></br>
<em>
[]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Values defines the allowed values that can be excluded.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.PolicyInterface">PolicyInterface
</h3>
<p>
@ -3441,6 +3567,21 @@ Deny
<p>Deny defines conditions used to pass or fail a validation rule.</p>
</td>
</tr>
<tr>
<td>
<code>podSecurity</code></br>
<em>
<a href="#kyverno.io/v1.PodSecurity">
PodSecurity
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>PodSecurity applies exemptions for Kubernetes Pod Security admission
by specifying exclusions for Pod Security Standards controls.</p>
</td>
</tr>
</tbody>
</table>
<hr />

3
go.mod
View file

@ -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

3
go.sum
View file

@ -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=

View file

@ -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,

View file

@ -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 {

View file

@ -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
}

View file

@ -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)

File diff suppressed because it is too large Load diff

View file

@ -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++
}

368
pkg/pss/evaluate.go Normal file
View file

@ -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
}

600
pkg/pss/mapping.go Normal file
View file

@ -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",
},
},
},
}

144
pkg/pss/utils.go Normal file
View file

@ -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
}

View file

@ -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