1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-15 17:51:20 +00:00
kyverno/pkg/utils/fuzz/policies.go
Charles-Edouard Brétéché f87fa52cb7
feat: bump to k8s 1.31 (#10938)
* feat: bump to k8s 1.31

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* tidy

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* mod

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix otel

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix otel schema

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* update linter

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* feat: fix image verification tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* linter issues

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* cel change

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
Co-authored-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
2024-08-28 17:09:58 +00:00

882 lines
18 KiB
Go

package fuzz
import (
"encoding/json"
"fmt"
"reflect"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
type BypassChecker struct {
ResourceType string
ShouldBlock func(*corev1.Pod) (bool, error)
ClusterPolicy *kyvernov1.ClusterPolicy
}
var (
cp1 *kyvernov1.ClusterPolicy
cp2 *kyvernov1.ClusterPolicy
cp3 *kyvernov1.ClusterPolicy
cp4 *kyvernov1.ClusterPolicy
cp5 *kyvernov1.ClusterPolicy
cp6 *kyvernov1.ClusterPolicy
cp7 *kyvernov1.ClusterPolicy
cp8 *kyvernov1.ClusterPolicy
cp9 *kyvernov1.ClusterPolicy
cp10 *kyvernov1.ClusterPolicy
cp11 *kyvernov1.ClusterPolicy
mi2048 resource.Quantity
Policies map[int]*BypassChecker
k8sKinds = map[int]string{
0: "Config",
1: "ConfigMap",
2: "CronJob",
3: "DaemonSet",
4: "Deployment",
5: "EndpointSlice",
6: "Ingress",
7: "Job",
8: "LimitRange",
9: "List",
10: "NetworkPolicy",
11: "PersistentVolume",
12: "PersistentVolumeClaim",
13: "Pod",
14: "ReplicaSet",
15: "ReplicationController",
16: "RuntimeClass",
17: "Secret",
18: "Service",
19: "StorageClass",
20: "VolumeSnapshot",
21: "VolumeSnapshotClass",
22: "VolumeSnapshotContent",
}
kindToVersion = map[string]string{
"Config": "v1",
"ConfigMap": "v1",
"CronJob": "batch/v1",
"DaemonSet": "apps/v1",
"Deployment": "apps/v1",
"EndpointSlice": "discovery.k8s.io/v1",
"Ingress": "networking.k8s.io/v1",
"Job": "batch/v1",
"LimitRange": "v1",
"List": "v1",
"NetworkPolicy": "networking.k8s.io/v1",
"PersistentVolume": "v1",
"PersistentVolumeClaim": "v1",
"Pod": "v1",
"ReplicaSet": "apps/v1",
"ReplicationController": "v1",
"RuntimeClass": "node.k8s.io/v1",
"Secret": "v1",
"Service": "v1",
"StorageClass": "storage.k8s.io/v1",
"VolumeSnapshot": "snapshot.storage.k8s.io/v1",
"VolumeSnapshotClass": "snapshot.storage.k8s.io/v1",
"VolumeSnapshotContent": "snapshot.storage.k8s.io/v1",
}
LatestImageTagPolicy = []byte(`{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "latest-image-tag-policy"
},
"spec": {
"rules": [
{
"name": "validate-tag",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "An image tag is required",
"pattern": {
"spec": {
"containers": [
{
"image": "*:*"
}
]
}
}
}
},
{
"name": "validate-latest",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "If the image has 'latest' tag then imagePullPolicy must be 'Always'",
"pattern": {
"spec": {
"containers": [
{
"(image)": "*latest",
"imagePullPolicy": "Always"
}
]
}
}
}
}
]
}
}
`)
EqualityHostpathPolicy = []byte(`
{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "equality-hostpath-policy"
},
"spec": {
"rules": [
{
"name": "validate-host-path",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "Host path '/var/lib/' is not allowed",
"pattern": {
"spec": {
"volumes": [
{
"=(hostPath)": {
"path": "!/var/lib"
}
}
]
}
}
}
}
]
}
}
`)
SecurityContextPolicy = []byte(`{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "security-context-policy"
},
"spec": {
"rules": [
{
"name": "pod rule 2",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "pod: validate run as non root user",
"pattern": {
"spec": {
"=(securityContext)": {
"runAsNonRoot": true
}
}
}
}
}
]
}
}`)
ContainerNamePolicy = []byte(`
{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "container-name-policy"
},
"spec": {
"rules": [
{
"name": "pod image rule",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"pattern": {
"spec": {
"=(containers)": [
{
"name": "nginx"
}
]
}
}
}
}
]
}
}`)
PodExistencePolicy = []byte(`
{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "pod-existence-policy"
},
"spec": {
"rules": [
{
"name": "pod image rule",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"pattern": {
"spec": {
"^(containers)": [
{
"name": "nginx"
}
]
}
}
}
}
]
}
}
`)
HostPathCannotExistPolicy = []byte(`
{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "host-path-cannot-exist-policy"
},
"spec": {
"rules": [
{
"name": "validate-host-path",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "Host path is not allowed",
"pattern": {
"spec": {
"volumes": [
{
"name": "*",
"X(hostPath)": null
}
]
}
}
}
}
]
}
}
`)
NamespaceCannotBeEmptyOrDefaultPolicy = []byte(`
{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "namespace-cannot-be-empty-or-default-policy"
},
"spec": {
"rules": [
{
"name": "check-default-namespace",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "A namespace is required",
"anyPattern": [
{
"metadata": {
"namespace": "?*"
}
},
{
"metadata": {
"namespace": "!default"
}
}
]
}
}
]
}
}
`)
HostnetworkAndPortNotAllowedPolicy = []byte(`
{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "hostnetwork-and-port-not-allowed-policy"
},
"spec": {
"rules": [
{
"name": "validate-host-network-port",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "Host network and port are not allowed",
"pattern": {
"spec": {
"hostNetwork": false,
"containers": [
{
"name": "*",
"ports": [
{
"hostPort": null
}
]
}
]
}
}
}
}
]
}
}
`)
SupplementalGroupsShouldBeHigherThanZeroPolicy = []byte(`{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "supplemental-groups-should-be-higher-than-zero-policy"
},
"spec": {
"rules": [
{
"name": "pod rule 2",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "pod: validate run as non root user",
"pattern": {
"spec": {
"=(supplementalGroups)": ">0"
}
}
}
}
]
}
} `)
SupplementalGroupsShouldBeBetween = []byte(`{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "supplemental-groups-should-be-between"
},
"spec": {
"rules": [
{
"name": "pod rule 2",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "pod: validate run as non root user",
"pattern": {
"spec": {
"=(supplementalGroups)": [
">0 & <100001"
]
}
}
}
}
]
}
} `)
ShouldHaveMoreMemoryThanFirstContainer = []byte(`{
"apiVersion": "kyvernov1.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "should-have-more-memory-than-first-container"
},
"spec": {
"rules": [
{
"name": "validate-host-network-port",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "Host network and port are not allowed",
"pattern": {
"spec":{
"containers":[
{
"name":"*",
"resources":{
"requests":{
"memory":"$(<=/spec/containers/0/resources/limits/memory)"
},
"limits":{
"memory":"2048Mi"
}
}
}
]
}
}
}
}
]
}
} `)
)
func InitFuzz() {
mi2048, _ = resource.ParseQuantity("2048Mi")
cp1 = &kyvernov1.ClusterPolicy{}
err := json.Unmarshal(ContainerNamePolicy, cp1)
if err != nil {
panic(err)
}
bpc1 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockContainerName,
ClusterPolicy: cp1,
}
cp2 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(LatestImageTagPolicy, cp2)
if err != nil {
panic(err)
}
bpc2 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockImageTag,
ClusterPolicy: cp2,
}
cp3 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(SecurityContextPolicy, cp3)
if err != nil {
panic(err)
}
bpc3 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockSecurityPolicy,
ClusterPolicy: cp3,
}
cp4 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(EqualityHostpathPolicy, cp4)
if err != nil {
panic(err)
}
bpc4 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockEquality,
ClusterPolicy: cp4,
}
cp5 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(PodExistencePolicy, cp5)
if err != nil {
panic(err)
}
bpc5 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockContainerNameExistenceAnchor,
ClusterPolicy: cp5,
}
cp6 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(HostPathCannotExistPolicy, cp6)
if err != nil {
panic(err)
}
bpc6 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockIfHostPathExists,
ClusterPolicy: cp6,
}
cp7 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(NamespaceCannotBeEmptyOrDefaultPolicy, cp7)
if err != nil {
panic(err)
}
bpc7 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockIfNamespaceIsEmptyOrDefault,
ClusterPolicy: cp7,
}
cp8 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(HostnetworkAndPortNotAllowedPolicy, cp8)
if err != nil {
panic(err)
}
bpc8 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockIfHostnetworkOrPortAreSpecified,
ClusterPolicy: cp8,
}
cp9 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(SupplementalGroupsShouldBeHigherThanZeroPolicy, cp9)
if err != nil {
panic(err)
}
bpc9 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockIfSupplementalGroupsExistAndAreLessThanZero,
ClusterPolicy: cp9,
}
cp10 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(SupplementalGroupsShouldBeBetween, cp10)
if err != nil {
panic(err)
}
bpc10 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockIfSupplementalGroupsExistAndIsNotBetween,
ClusterPolicy: cp10,
}
cp11 = &kyvernov1.ClusterPolicy{}
err = json.Unmarshal(ShouldHaveMoreMemoryThanFirstContainer, cp11)
if err != nil {
panic(err)
}
bpc11 := &BypassChecker{
ResourceType: "Pod",
ShouldBlock: ShouldBlockIfLessMemoryThanFirstContainer,
ClusterPolicy: cp11,
}
Policies = make(map[int]*BypassChecker)
Policies[0] = bpc1
Policies[1] = bpc2
Policies[2] = bpc3
Policies[3] = bpc4
Policies[4] = bpc5
Policies[5] = bpc6
Policies[6] = bpc7
Policies[7] = bpc8
Policies[8] = bpc9
Policies[9] = bpc10
Policies[10] = bpc11
}
func ShouldBlockIfLessMemoryThanFirstContainer(pod *corev1.Pod) (bool, error) {
if len(pod.Spec.Containers) == 0 {
return false, fmt.Errorf("No containers found")
}
containers := pod.Spec.Containers
if len(containers) < 2 {
return false, nil
}
var container0MemLimit resource.Quantity
container0 := containers[0]
fieldName := "Resources"
value := reflect.ValueOf(container0)
field := value.FieldByName(fieldName)
if !field.IsValid() {
// field is not specied, so fail
return true, nil
}
fieldName = "Limits"
value = reflect.ValueOf(container0.Resources)
field = value.FieldByName(fieldName)
if !field.IsValid() {
// field is not specied, so fail
return true, nil
}
if limit, ok := container0.Resources.Limits[corev1.ResourceMemory]; ok {
container0MemLimit = limit
}
for i, container := range containers {
if i > 0 {
fieldName := "Resources"
value := reflect.ValueOf(container)
field := value.FieldByName(fieldName)
if !field.IsValid() {
// field is not specied, so fail
return true, nil
}
fieldName = "Limits"
value = reflect.ValueOf(container.Resources)
field = value.FieldByName(fieldName)
if !field.IsValid() {
// field is not specied, so fail
return true, nil
}
if limit, ok := container.Resources.Limits[corev1.ResourceMemory]; ok {
if !limit.Equal(mi2048) {
return true, nil
}
} else {
return true, nil
}
fieldName = "Requests"
value = reflect.ValueOf(container.Resources)
field = value.FieldByName(fieldName)
if !field.IsValid() {
// field is not specied, so fail
return true, nil
}
if limit, ok := container.Resources.Requests[corev1.ResourceMemory]; ok {
smallerThanOrEqual := limit.Cmp(container0MemLimit)
if smallerThanOrEqual == -1 || smallerThanOrEqual == 0 {
return true, nil
}
} else {
return false, nil
}
}
}
return false, nil
}
func ShouldBlockIfSupplementalGroupsExistAndIsNotBetween(pod *corev1.Pod) (bool, error) {
if pod.Spec.SecurityContext != nil {
if len(pod.Spec.SecurityContext.SupplementalGroups) != 0 {
for _, sg := range pod.Spec.SecurityContext.SupplementalGroups {
if sg <= 0 || sg >= 100001 {
return true, nil
}
}
}
}
return false, nil
}
func ShouldBlockIfSupplementalGroupsExistAndAreLessThanZero(pod *corev1.Pod) (bool, error) {
if pod.Spec.SecurityContext != nil {
if len(pod.Spec.SecurityContext.SupplementalGroups) != 0 {
for _, sg := range pod.Spec.SecurityContext.SupplementalGroups {
if sg <= 0 {
return true, nil
}
}
}
}
return false, nil
}
func ShouldBlockIfHostnetworkOrPortAreSpecified(pod *corev1.Pod) (bool, error) {
if pod.Spec.SecurityContext != nil {
fieldName := "HostNetwork"
value := reflect.ValueOf(pod.Spec.SecurityContext)
field := value.Elem().FieldByName(fieldName)
if field.IsValid() {
// field is specified but cannot be according to the policy
return true, nil
}
}
if len(pod.Spec.Containers) == 0 {
return false, fmt.Errorf("No containers found")
}
containers := pod.Spec.Containers
for _, container := range containers {
for _, port := range container.Ports {
fieldName := "HostPort"
value := reflect.ValueOf(port)
field := value.FieldByName(fieldName)
if field.IsValid() {
// field is specified but cannot be according to the policy
return true, nil
}
}
}
return false, nil
}
func ShouldBlockIfNamespaceIsEmptyOrDefault(pod *corev1.Pod) (bool, error) {
if len(pod.ObjectMeta.Namespace) == 0 {
return true, nil
}
if pod.ObjectMeta.Namespace == "default" {
return true, nil
}
return false, nil
}
func ShouldBlockContainerName(pod *corev1.Pod) (bool, error) {
if len(pod.Spec.Containers) == 0 {
return false, fmt.Errorf("No containers found")
}
containers := pod.Spec.Containers
for _, container := range containers {
if container.Name != "nginx" {
return true, nil
}
}
return false, nil
}
func ShouldBlockContainerNameExistenceAnchor(pod *corev1.Pod) (bool, error) {
if len(pod.Spec.Containers) == 0 {
return false, fmt.Errorf("No containers found")
}
containers := pod.Spec.Containers
for _, container := range containers {
if container.Name == "nginx" {
return false, nil
}
}
return true, nil
}
func ShouldBlockImageTag(pod *corev1.Pod) (bool, error) {
if len(pod.Spec.Containers) == 0 {
return false, fmt.Errorf("No containers found")
}
containers := pod.Spec.Containers
for _, container := range containers {
split := strings.Split(container.Image, ":")
if len(split) != 2 {
return true, nil
}
if _, ok := strings.CutSuffix(container.Image, "latest"); ok {
if container.ImagePullPolicy != "Always" {
return true, nil
}
}
}
return false, nil
}
func ShouldBlockEquality(pod *corev1.Pod) (bool, error) {
if len(pod.Spec.Volumes) == 0 {
return false, fmt.Errorf("No volumes found")
}
volumes := pod.Spec.Volumes
for _, volume := range volumes {
if volume.VolumeSource.HostPath != nil {
if volume.VolumeSource.HostPath.Path == "/var/lib" {
return true, nil
}
}
}
return false, nil
}
func ShouldBlockIfHostPathExists(pod *corev1.Pod) (bool, error) {
if len(pod.Spec.Volumes) == 0 {
return false, fmt.Errorf("No volumes found")
}
volumes := pod.Spec.Volumes
for _, volume := range volumes {
if volume.VolumeSource.HostPath != nil {
return true, nil
}
}
return false, nil
}
// if there is a security policy, then RunAsNonRoot must be true
func ShouldBlockSecurityPolicy(pod *corev1.Pod) (bool, error) {
if pod.Spec.SecurityContext == nil {
return false, nil
}
securityContext := pod.Spec.SecurityContext
if securityContext.RunAsNonRoot == nil {
return true, nil
}
if !*securityContext.RunAsNonRoot {
return true, nil
}
return false, nil
}