2023-03-27 10:09:46 +02:00
package utils
2019-06-07 14:46:18 +03:00
import (
2020-01-10 17:15:44 -08:00
"encoding/json"
2019-06-07 14:46:18 +03:00
"testing"
2023-02-08 09:08:09 +01:00
authenticationv1 "k8s.io/api/authentication/v1"
rbacv1 "k8s.io/api/rbac/v1"
2021-10-29 18:13:20 +02:00
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
2024-06-20 11:44:43 +02:00
v2 "github.com/kyverno/kyverno/api/kyverno/v2"
2024-10-16 15:24:37 +02:00
"github.com/kyverno/kyverno/pkg/autogen"
2023-01-03 13:02:15 +01:00
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
2019-08-21 12:03:53 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-06-07 14:46:18 +03:00
)
2020-02-07 18:36:17 +05:30
func TestMatchesResourceDescription ( t * testing . T ) {
tcs := [ ] struct {
Description string
2024-06-20 11:44:43 +02:00
AdmissionInfo v2 . RequestInfo
2020-02-07 18:36:17 +05:30
Resource [ ] byte
2020-02-09 12:34:59 +05:30
Policy [ ] byte
2020-02-07 18:36:17 +05:30
areErrorsExpected bool
} {
2021-07-29 01:29:53 +05:30
{
Description : "Match Any matches the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "any-match-rule" ,
"match" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "dev" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Match Any does not match the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "abc" ,
"namespace" : "default"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "dev" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Match All matches the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "abc" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Match All does not match the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "xyz" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Exclude Any excludes the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "dev" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"dev"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"default"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Exclude Any does not exclude the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"dev"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"default"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Exclude All excludes the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "dev" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"dev"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"prod"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Exclude All does not exclude the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-07-29 01:29:53 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"abc"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"default"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
2020-02-07 18:36:17 +05:30
{
2020-02-09 19:56:38 +05:30
Description : "Should match pod and not exclude it" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2020-02-09 12:34:59 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
2020-02-07 18:36:17 +05:30
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
2020-02-09 20:52:45 +05:30
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
2020-02-07 18:36:17 +05:30
areErrorsExpected : false ,
} ,
2020-02-09 19:11:25 +05:30
{
2020-02-09 19:56:38 +05:30
Description : "Should exclude resource since it matches the exclude block" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2020-02-09 19:11:25 +05:30
ClusterRoles : [ ] string { "system:node" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
2020-02-09 20:52:45 +05:30
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
2020-02-09 19:11:25 +05:30
areErrorsExpected : true ,
} ,
{
2020-02-09 20:52:45 +05:30
Description : "Should not fail if in sync mode, if admission info is empty it should still match resources with specific clusterRoles" ,
2020-02-09 19:11:25 +05:30
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
2020-02-09 20:52:45 +05:30
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
2020-02-09 19:11:25 +05:30
areErrorsExpected : false ,
} ,
2021-06-29 11:01:22 +05:30
{
Description : "Should fail since resource does not match because of names field" ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"],"names": ["dev-*"]},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
2022-04-25 20:20:40 +08:00
Description : "Should pass since resource matches a name in the names field" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-04-25 20:20:40 +08:00
ClusterRoles : [ ] string { "system:node" } ,
} ,
2021-06-29 11:01:22 +05:30
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"],"names": ["dev-*","hello-world"]},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Should fail since resource gets excluded because of the names field" ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "names": ["dev-*","hello-*"]}},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Should pass since resource does not get excluded because of the names field" ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"bye-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "names": ["dev-*","hello-*"]}},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : false ,
} ,
2020-02-09 19:56:38 +05:30
{
Description : "Should fail since resource does not match policy" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2020-02-09 19:56:38 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Service","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
2020-02-09 20:52:45 +05:30
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
2020-02-09 19:56:38 +05:30
areErrorsExpected : true ,
} ,
{
Description : "Should not fail since resource does not match exclude block" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2020-02-09 19:56:38 +05:30
ClusterRoles : [ ] string { "system:node" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "name":"hello-world2","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
2020-02-09 20:52:45 +05:30
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
2020-02-09 19:56:38 +05:30
areErrorsExpected : false ,
} ,
2021-04-29 23:28:17 +05:30
{
Description : "Should pass since group, version, kind match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-04-29 23:28:17 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}} ` ) ,
2024-08-06 21:24:28 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } }, { "name": "check-cpu-memory-limits", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "validate": { "message": "Resource limits are required for CPU and memory", "pattern": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "memory": "?*", "cpu": "?*" } } } ] } } } } } } ] } } ` ) ,
2021-04-29 23:28:17 +05:30
areErrorsExpected : false ,
} ,
{
Description : "Should pass since version and kind match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-04-29 23:28:17 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } } ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "v1/Pod" ] } }, "validate": { "failureAction": "enforce", "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } } ` ) ,
2021-04-29 23:28:17 +05:30
areErrorsExpected : false ,
} ,
{
Description : "Should fail since resource does not match " ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-04-29 23:28:17 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Service","metadata": { "name":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Should fail since version not match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-04-29 23:28:17 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "apps/v1beta1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}} ` ) ,
2024-08-06 21:24:28 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } }, { "name": "check-cpu-memory-limits", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "validate": { "message": "Resource limits are required for CPU and memory", "pattern": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "memory": "?*", "cpu": "?*" } } } ] } } } } } } ] } } ` ) ,
2021-04-29 23:28:17 +05:30
areErrorsExpected : true ,
} ,
{
Description : "Should fail since cluster role version not match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-04-29 23:28:17 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "kind": "ClusterRole", "apiVersion": "rbac.authorization.k8s.io/v1", "metadata": { "name": "secret-reader-demo", "namespace": "default" }, "rules": [ { "apiGroups": [ "" ], "resources": [ "secrets" ], "verbs": [ "get", "watch", "list" ] } ] } ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "check-host-path" }, "spec": { "background": true, "rules": [ { "name": "check-host-path", "match": { "resources": { "kinds": [ "rbac.authorization.k8s.io/v1beta1/ClusterRole" ] } }, "validate": { "failureAction": "enforce", "message": "Host path is not allowed", "pattern": { "spec": { "volumes": [ { "name": "*", "hostPath": { "path": "" } } ] } } } } ] } } ` ) ,
2021-04-29 23:28:17 +05:30
areErrorsExpected : true ,
} ,
2021-09-03 10:42:43 +05:30
{
Description : "Test for GVK case sensitive" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-09-03 10:42:43 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } } ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "pod" ] } }, "validate": { "failureAction": "enforce", "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } } ` ) ,
2023-01-16 13:31:05 +05:30
areErrorsExpected : true ,
2021-09-03 10:42:43 +05:30
} ,
{
2023-01-16 13:31:05 +05:30
Description : "Test should fail for GVK case sensitive" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2021-09-03 10:42:43 +05:30
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}} ` ) ,
2024-08-06 21:24:28 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } } ] } } ` ) ,
2023-01-16 13:31:05 +05:30
areErrorsExpected : true ,
2021-09-03 10:42:43 +05:30
} ,
2020-02-07 18:36:17 +05:30
}
2020-02-09 19:11:25 +05:30
for i , tc := range tcs {
2021-10-29 18:13:20 +02:00
var policy v1 . Policy
2020-02-09 19:56:38 +05:30
err := json . Unmarshal ( tc . Policy , & policy )
if err != nil {
t . Errorf ( "Testcase %d invalid policy raw" , i + 1 )
}
2023-01-03 13:02:15 +01:00
resource , _ := kubeutils . BytesToUnstructured ( tc . Resource )
2020-02-07 18:36:17 +05:30
2024-10-16 15:24:37 +02:00
for _ , rule := range autogen . Default . ComputeRules ( & policy , "" ) {
2023-03-29 06:22:21 +02:00
err := MatchesResourceDescription ( * resource , rule , tc . AdmissionInfo , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" )
2020-02-09 19:11:25 +05:30
if err != nil {
if ! tc . areErrorsExpected {
2022-04-25 20:20:40 +08:00
t . Errorf ( "Testcase %d Unexpected error: %v\nmsg: %s" , i + 1 , err , tc . Description )
2020-02-09 19:11:25 +05:30
}
2020-02-09 12:34:59 +05:30
} else {
if tc . areErrorsExpected {
2020-11-17 12:01:01 -08:00
t . Errorf ( "Testcase %d Expected Error but received no error" , i + 1 )
2020-02-09 12:34:59 +05:30
}
}
}
}
2020-02-07 18:36:17 +05:30
}
2022-10-13 18:02:01 +02:00
func TestMatchesResourceDescription_GenerateName ( t * testing . T ) {
tcs := [ ] struct {
Description string
2024-06-20 11:44:43 +02:00
AdmissionInfo v2 . RequestInfo
2022-10-13 18:02:01 +02:00
Resource [ ] byte
Policy [ ] byte
areErrorsExpected bool
} {
{
Description : "Match Any matches the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "any-match-rule" ,
"match" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "dev" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Match Any does not match the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "abc" ,
"namespace" : "default"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "dev" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Match All matches the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "abc" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Match All does not match the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [ "xyz" ]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [ "prod" ]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Exclude Any excludes the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "dev" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"dev"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"default"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Exclude Any does not exclude the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"any" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"dev"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"default"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Exclude All excludes the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "dev" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"dev"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"prod"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Exclude All does not exclude the Pod" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"generateName" : "abc" ,
"namespace" : "prod"
} ,
"spec" : {
"containers" : [
{
"name" : "cont-name" ,
"image" : "cont-img" ,
"ports" : [
{
"containerPort" : 81
}
] ,
"resources" : {
"limits" : {
"memory" : "30Mi" ,
"cpu" : "0.2"
} ,
"requests" : {
"memory" : "20Mi" ,
"cpu" : "0.1"
}
}
}
]
}
} ` ) ,
Policy : [ ] byte ( ` {
"apiVersion" : "kyverno.io/v1" ,
"kind" : "ClusterPolicy" ,
"metadata" : {
"name" : "test-policy"
} ,
"spec" : {
"background" : false ,
"rules" : [
{
"name" : "test-rule" ,
"match" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
]
}
}
]
} ,
"exclude" : {
"all" : [
{
"resources" : {
"kinds" : [
"Pod"
] ,
"names" : [
"abc"
]
}
} ,
{
"resources" : {
"kinds" : [
"Pod"
] ,
"namespaces" : [
"default"
]
}
}
]
} ,
"mutate" : {
"overlay" : {
"spec" : {
"containers" : [
{
"(image)" : "*" ,
"imagePullPolicy" : "IfNotPresent"
}
]
}
}
}
}
]
}
} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Should match pod and not exclude it" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Should exclude resource since it matches the exclude block" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "system:node" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Should not fail if in sync mode, if admission info is empty it should still match resources with specific clusterRoles" ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Should fail since resource does not match because of names field" ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"],"names": ["dev-*"]},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Should pass since resource matches a name in the names field" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "system:node" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"],"names": ["dev-*","hello-world"]},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Should fail since resource gets excluded because of the names field" ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "names": ["dev-*","hello-*"]}},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Should pass since resource does not get excluded because of the names field" ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"bye-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "names": ["dev-*","hello-*"]}},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Should fail since resource does not match policy" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Service","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Should not fail since resource does not match exclude block" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "system:node" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Pod","metadata": { "generateName":"hello-world2","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : false ,
} ,
{
Description : "Should pass since group, version, kind match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "generateName": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}} ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } }, { "name": "check-cpu-memory-limits", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "validate": { "failureAction": "enforce", "message": "Resource limits are required for CPU and memory", "pattern": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "memory": "?*", "cpu": "?*" } } } ] } } } } } } ] } } ` ) ,
2022-10-13 18:02:01 +02:00
areErrorsExpected : false ,
} ,
{
Description : "Should pass since version and kind match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "v1", "kind": "Pod", "metadata": { "generateName": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } } ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "v1/Pod" ] } }, "validate": { "failureAction": "enforce", "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } } ` ) ,
2022-10-13 18:02:01 +02:00
areErrorsExpected : false ,
} ,
{
Description : "Should fail since resource does not match " ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion":"v1","kind":"Service","metadata": { "generateName":"hello-world","labels": { "name":"hello-world"}},"spec": { "containers":[ { "name":"hello-world","image":"hello-world","ports":[ { "containerPort":81}],"resources": { "limits": { "memory":"30Mi","cpu":"0.2"},"requests": { "memory":"20Mi","cpu":"0.1"}}}]}} ` ) ,
Policy : [ ] byte ( ` { "apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata": { "name":"hello-world-policy"},"spec": { "background":false,"rules":[ { "name":"hello-world-policy","match": { "resources": { "kinds":["Pod"]}},"exclude": { "resources": { "name":"hello-world"},"clusterRoles":["system:node"]},"mutate": { "overlay": { "spec": { "containers":[ { "(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}} ` ) ,
areErrorsExpected : true ,
} ,
{
Description : "Should fail since version not match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "apps/v1beta1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "generateName": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}} ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } }, { "name": "check-cpu-memory-limits", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "validate": { "failureAction": "enforce", "message": "Resource limits are required for CPU and memory", "pattern": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "memory": "?*", "cpu": "?*" } } } ] } } } } } } ] } } ` ) ,
2022-10-13 18:02:01 +02:00
areErrorsExpected : true ,
} ,
{
Description : "Should fail since cluster role version not match" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "kind": "ClusterRole", "apiVersion": "rbac.authorization.k8s.io/v1", "metadata": { "generateName": "secret-reader-demo", "namespace": "default" }, "rules": [ { "apiGroups": [ "" ], "resources": [ "secrets" ], "verbs": [ "get", "watch", "list" ] } ] } ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "check-host-path" }, "spec": { "background": true, "rules": [ { "name": "check-host-path", "match": { "resources": { "kinds": [ "rbac.authorization.k8s.io/v1beta1/ClusterRole" ] } }, "validate": { "failureAction": "enforce", "message": "Host path is not allowed", "pattern": { "spec": { "volumes": [ { "name": "*", "hostPath": { "path": "" } } ] } } } } ] } } ` ) ,
2022-10-13 18:02:01 +02:00
areErrorsExpected : true ,
} ,
{
Description : "Test for GVK case sensitive" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "v1", "kind": "Pod", "metadata": { "generateName": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } } ` ) ,
2024-08-27 23:07:57 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "pod" ] } }, "validate": { "failureAction": "enforce", "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } } ` ) ,
2023-01-16 13:31:05 +05:30
areErrorsExpected : true ,
2022-10-13 18:02:01 +02:00
} ,
{
2023-01-16 13:31:05 +05:30
Description : "Test should fail for GVK case sensitive" ,
2024-06-20 11:44:43 +02:00
AdmissionInfo : v2 . RequestInfo {
2022-10-13 18:02:01 +02:00
ClusterRoles : [ ] string { "admin" } ,
} ,
Resource : [ ] byte ( ` { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "generateName": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}} ` ) ,
2024-08-06 21:24:28 +03:00
Policy : [ ] byte ( ` { "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } } ] } } ` ) ,
2023-01-16 13:31:05 +05:30
areErrorsExpected : true ,
2022-10-13 18:02:01 +02:00
} ,
}
for i , tc := range tcs {
var policy v1 . Policy
err := json . Unmarshal ( tc . Policy , & policy )
if err != nil {
t . Errorf ( "Testcase %d invalid policy raw" , i + 1 )
}
2023-01-03 13:02:15 +01:00
resource , _ := kubeutils . BytesToUnstructured ( tc . Resource )
2022-10-13 18:02:01 +02:00
2024-10-16 15:24:37 +02:00
for _ , rule := range autogen . Default . ComputeRules ( & policy , "" ) {
2023-03-29 06:22:21 +02:00
err := MatchesResourceDescription ( * resource , rule , tc . AdmissionInfo , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" )
2022-10-13 18:02:01 +02:00
if err != nil {
if ! tc . areErrorsExpected {
t . Errorf ( "Testcase %d Unexpected error: %v\nmsg: %s" , i + 1 , err , tc . Description )
}
} else {
if tc . areErrorsExpected {
t . Errorf ( "Testcase %d Expected Error but received no error" , i + 1 )
}
}
}
}
}
2019-08-21 12:03:53 -07:00
// Match multiple kinds
func TestResourceDescriptionMatch_MultipleKind ( t * testing . T ) {
rawResource := [ ] byte ( ` {
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"name" : "nginx-deployment" ,
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
}
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2019-08-21 12:03:53 -07:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
2021-10-29 18:13:20 +02:00
resourceDescription := v1 . ResourceDescription {
2019-08-21 12:03:53 -07:00
Kinds : [ ] string { "Deployment" , "Pods" } ,
2019-06-07 14:46:18 +03:00
Selector : & metav1 . LabelSelector {
MatchLabels : nil ,
MatchExpressions : nil ,
} ,
}
2021-10-29 18:13:20 +02:00
rule := v1 . Rule { MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } }
2019-08-21 12:03:53 -07:00
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2020-02-07 14:45:43 +05:30
t . Errorf ( "Testcase has failed due to the following:%v" , err )
}
2019-08-21 12:03:53 -07:00
}
2019-06-07 14:46:18 +03:00
2023-02-08 09:08:09 +01:00
func TestResourceDescriptionMatch_ExcludeDefaultGroups ( t * testing . T ) {
// slightly simplified ingress controller pod that lives in the ingress-nginx namespace
rawResource := [ ] byte ( ` {
"apiVersion" : "v1" ,
"kind" : "Pod" ,
"metadata" : {
"name" : "ingress-nginx-controller-57bc6474bb-mcpt2" ,
"namespace" : "ingress-nginx"
} ,
"spec" : {
"containers" : [
{
"args" : [ "/nginx-ingress-controller" ] ,
"env" : [ ] ,
"image" : "registry.k8s.io/ingress-nginx/controller:v1.5.1" ,
"imagePullPolicy" : "IfNotPresent" ,
"name" : "controller" ,
"securityContext" : {
"allowPrivilegeEscalation" : true ,
"capabilities" : {
"add" : [
"NET_BIND_SERVICE"
] ,
"drop" : [
"ALL"
]
} ,
"runAsUser" : 101
} ,
"volumeMounts" : [
{
"mountPath" : "/usr/local/certificates/" ,
"name" : "webhook-cert" ,
"readOnly" : true
} ,
{
"mountPath" : "/var/run/secrets/kubernetes.io/serviceaccount" ,
"name" : "kube-api-access-vc2qz" ,
"readOnly" : true
}
]
}
] ,
"securityContext" : { } ,
"serviceAccount" : "ingress-nginx" ,
"serviceAccountName" : "ingress-nginx"
}
} ` )
resource , err := kubeutils . BytesToUnstructured ( rawResource )
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
// this rule should match only pods in the user1-restricted namespace, and also pods of User:user1.
rule := v1 . Rule {
MatchResources : v1 . MatchResources { Any : v1 . ResourceFilters {
// pods in user1-restricted namespace
v1 . ResourceFilter {
ResourceDescription : v1 . ResourceDescription {
Kinds : [ ] string { "Pod" } ,
Namespaces : [ ] string {
"user1-restricted" ,
} ,
} ,
} ,
// pods for User user1 account
v1 . ResourceFilter {
ResourceDescription : v1 . ResourceDescription {
Kinds : [ ] string { "Pod" } ,
} ,
UserInfo : v1 . UserInfo {
Subjects : [ ] rbacv1 . Subject {
{
Kind : "User" ,
Name : "user1" ,
} ,
} ,
} ,
} ,
} } ,
2024-09-10 13:14:49 +02:00
ExcludeResources : & v1 . MatchResources { } ,
2023-02-08 09:08:09 +01:00
}
// this is the request info that was also passed with the mocked pod
2024-06-20 11:44:43 +02:00
requestInfo := v2 . RequestInfo {
2023-02-08 09:08:09 +01:00
AdmissionUserInfo : authenticationv1 . UserInfo {
Username : "system:serviceaccount:kube-system:replicaset-controller" ,
UID : "8f36cad4-eb68-4931-bea8-8a42dd1fee4c" ,
Groups : [ ] string {
"system:serviceaccounts" ,
"system:serviceaccounts:kube-system" ,
"system:authenticated" ,
} ,
} ,
}
// First test: confirm that this above rule produces errors (and raise an error if err == nil)
2023-03-29 06:22:21 +02:00
if err := MatchesResourceDescription ( * resource , rule , requestInfo , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err == nil {
2023-02-08 09:08:09 +01:00
t . Error ( "Testcase was expected to fail, but err was nil" )
}
// This next rule *should* match, because we explicitly match the ingress-nginx namespace this time.
rule2 := v1 . Rule {
MatchResources : v1 . MatchResources { Any : v1 . ResourceFilters {
v1 . ResourceFilter {
ResourceDescription : v1 . ResourceDescription {
Kinds : [ ] string { "Pod" } ,
Namespaces : [ ] string {
"ingress-nginx" ,
} ,
} ,
} ,
} } ,
2024-09-10 13:14:49 +02:00
ExcludeResources : & v1 . MatchResources { Any : v1 . ResourceFilters { } } ,
2023-02-08 09:08:09 +01:00
}
// Second test: confirm that matching this rule does not create any errors (and raise if err != nil)
2023-03-29 06:22:21 +02:00
if err := MatchesResourceDescription ( * resource , rule2 , requestInfo , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2023-02-08 09:08:09 +01:00
t . Errorf ( "Testcase was expected to not fail, but err was %s" , err )
}
// Now we extend the previous rule to have an Exclude part. Making it 'not-empty' should make the exclude-code run.
2024-09-10 13:14:49 +02:00
rule2 . ExcludeResources = & v1 . MatchResources { Any : v1 . ResourceFilters {
2023-02-08 09:08:09 +01:00
v1 . ResourceFilter {
ResourceDescription : v1 . ResourceDescription {
Kinds : [ ] string { "Pod" } ,
} ,
} ,
} }
// Third test: confirm that now the custom exclude-snippet should run in CheckSubjects() and that should result in this rule failing (raise if err == nil for that reason)
2023-03-29 06:22:21 +02:00
if err := MatchesResourceDescription ( * resource , rule2 , requestInfo , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err == nil {
2023-02-08 09:08:09 +01:00
t . Error ( "Testcase was expected to fail, but err was nil #1!" )
}
}
2019-08-21 12:03:53 -07:00
// Match resource name
func TestResourceDescriptionMatch_Name ( t * testing . T ) {
2019-06-07 14:46:18 +03:00
rawResource := [ ] byte ( ` {
2019-08-21 12:03:53 -07:00
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"name" : "nginx-deployment" ,
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
2019-06-07 14:46:18 +03:00
}
2019-08-21 12:03:53 -07:00
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2019-08-21 12:03:53 -07:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
2021-10-29 18:13:20 +02:00
resourceDescription := v1 . ResourceDescription {
2019-08-21 12:03:53 -07:00
Kinds : [ ] string { "Deployment" } ,
Name : "nginx-deployment" ,
2019-06-07 14:46:18 +03:00
Selector : & metav1 . LabelSelector {
MatchLabels : nil ,
MatchExpressions : nil ,
} ,
}
2021-10-29 18:13:20 +02:00
rule := v1 . Rule { MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } }
2019-07-26 07:28:34 -04:00
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2020-02-07 14:45:43 +05:30
t . Errorf ( "Testcase has failed due to the following:%v" , err )
}
2019-08-21 12:03:53 -07:00
}
2019-06-07 14:46:18 +03:00
2022-10-13 18:02:01 +02:00
func TestResourceDescriptionMatch_GenerateName ( t * testing . T ) {
rawResource := [ ] byte ( ` {
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"generateName" : "nginx-deployment" ,
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
}
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2022-10-13 18:02:01 +02:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
resourceDescription := v1 . ResourceDescription {
Kinds : [ ] string { "Deployment" } ,
Name : "nginx-deployment" ,
Selector : & metav1 . LabelSelector {
MatchLabels : nil ,
MatchExpressions : nil ,
} ,
}
rule := v1 . Rule { MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } }
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2022-10-13 18:02:01 +02:00
t . Errorf ( "Testcase has failed due to the following:%v" , err )
}
}
2019-08-21 12:03:53 -07:00
// Match resource regex
func TestResourceDescriptionMatch_Name_Regex ( t * testing . T ) {
2019-06-07 14:46:18 +03:00
rawResource := [ ] byte ( ` {
2019-08-21 12:03:53 -07:00
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"name" : "nginx-deployment" ,
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
2019-06-07 14:46:18 +03:00
}
2019-08-21 12:03:53 -07:00
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2019-08-21 12:03:53 -07:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
2021-10-29 18:13:20 +02:00
resourceDescription := v1 . ResourceDescription {
2019-08-21 12:03:53 -07:00
Kinds : [ ] string { "Deployment" } ,
Name : "nginx-*" ,
Selector : & metav1 . LabelSelector {
MatchLabels : nil ,
MatchExpressions : nil ,
2022-10-13 18:02:01 +02:00
} ,
}
rule := v1 . Rule { MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } }
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2022-10-13 18:02:01 +02:00
t . Errorf ( "Testcase has failed due to the following:%v" , err )
}
}
func TestResourceDescriptionMatch_GenerateName_Regex ( t * testing . T ) {
rawResource := [ ] byte ( ` {
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"generateName" : "nginx-deployment" ,
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
}
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2022-10-13 18:02:01 +02:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
resourceDescription := v1 . ResourceDescription {
Kinds : [ ] string { "Deployment" } ,
Name : "nginx-*" ,
Selector : & metav1 . LabelSelector {
MatchLabels : nil ,
MatchExpressions : nil ,
2019-08-21 12:03:53 -07:00
} ,
}
2021-10-29 18:13:20 +02:00
rule := v1 . Rule { MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } }
2019-06-07 14:46:18 +03:00
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2020-02-07 14:45:43 +05:30
t . Errorf ( "Testcase has failed due to the following:%v" , err )
}
2019-08-21 12:03:53 -07:00
}
2019-06-07 14:46:18 +03:00
2019-08-21 12:03:53 -07:00
// Match expressions for labels to not match
func TestResourceDescriptionMatch_Label_Expression_NotMatch ( t * testing . T ) {
rawResource := [ ] byte ( ` {
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"name" : "nginx-deployment" ,
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
2019-06-07 14:46:18 +03:00
}
2019-08-21 12:03:53 -07:00
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2019-08-21 12:03:53 -07:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
2021-10-29 18:13:20 +02:00
resourceDescription := v1 . ResourceDescription {
2019-08-21 12:03:53 -07:00
Kinds : [ ] string { "Deployment" } ,
Name : "nginx-*" ,
2019-06-07 14:46:18 +03:00
Selector : & metav1 . LabelSelector {
MatchLabels : nil ,
MatchExpressions : [ ] metav1 . LabelSelectorRequirement {
2020-02-03 13:38:24 -08:00
{
2019-06-07 14:46:18 +03:00
Key : "label2" ,
Operator : "NotIn" ,
Values : [ ] string {
"sometest1" ,
} ,
} ,
} ,
} ,
}
2021-10-29 18:13:20 +02:00
rule := v1 . Rule { MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } }
2019-06-07 14:46:18 +03:00
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2020-02-07 14:45:43 +05:30
t . Errorf ( "Testcase has failed due to the following:%v" , err )
}
2019-06-07 14:46:18 +03:00
}
2019-08-21 12:03:53 -07:00
// Match label expression in matching set
func TestResourceDescriptionMatch_Label_Expression_Match ( t * testing . T ) {
2019-06-07 14:46:18 +03:00
rawResource := [ ] byte ( ` {
2019-08-21 12:03:53 -07:00
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"name" : "nginx-deployment" ,
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
2019-06-07 14:46:18 +03:00
}
2019-08-21 12:03:53 -07:00
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2019-08-21 12:03:53 -07:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
2019-06-07 14:46:18 +03:00
}
2021-10-29 18:13:20 +02:00
resourceDescription := v1 . ResourceDescription {
2019-08-21 12:03:53 -07:00
Kinds : [ ] string { "Deployment" } ,
Name : "nginx-*" ,
2019-06-07 14:46:18 +03:00
Selector : & metav1 . LabelSelector {
2019-08-21 12:03:53 -07:00
MatchLabels : nil ,
2019-06-07 14:46:18 +03:00
MatchExpressions : [ ] metav1 . LabelSelectorRequirement {
2020-02-03 13:38:24 -08:00
{
2019-08-21 12:03:53 -07:00
Key : "app" ,
Operator : "NotIn" ,
2019-06-07 14:46:18 +03:00
Values : [ ] string {
2019-08-21 12:03:53 -07:00
"nginx1" ,
"nginx2" ,
2019-06-07 14:46:18 +03:00
} ,
} ,
} ,
} ,
}
2021-10-29 18:13:20 +02:00
rule := v1 . Rule { MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } }
2019-08-21 12:03:53 -07:00
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err != nil {
2020-02-07 14:45:43 +05:30
t . Errorf ( "Testcase has failed due to the following:%v" , err )
}
2019-08-21 12:03:53 -07:00
}
2019-06-07 14:46:18 +03:00
2019-08-21 12:03:53 -07:00
// check for exclude conditions
func TestResourceDescriptionExclude_Label_Expression_Match ( t * testing . T ) {
2019-06-07 14:46:18 +03:00
rawResource := [ ] byte ( ` {
2019-08-21 12:03:53 -07:00
"apiVersion" : "apps/v1" ,
"kind" : "Deployment" ,
"metadata" : {
"name" : "nginx-deployment" ,
"labels" : {
"app" : "nginx" ,
"block" : "true"
}
} ,
"spec" : {
"replicas" : 3 ,
"selector" : {
"matchLabels" : {
"app" : "nginx"
}
} ,
"template" : {
"metadata" : {
"labels" : {
"app" : "nginx"
}
} ,
"spec" : {
"containers" : [
{
"name" : "nginx" ,
"image" : "nginx:1.7.9" ,
"ports" : [
{
"containerPort" : 80
}
]
}
]
}
}
2019-06-07 14:46:18 +03:00
}
2019-08-21 12:03:53 -07:00
} ` )
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( rawResource )
2019-08-21 12:03:53 -07:00
if err != nil {
t . Errorf ( "unable to convert raw resource to unstructured: %v" , err )
}
2021-10-29 18:13:20 +02:00
resourceDescription := v1 . ResourceDescription {
2019-08-21 12:03:53 -07:00
Kinds : [ ] string { "Deployment" } ,
Name : "nginx-*" ,
2019-06-07 14:46:18 +03:00
Selector : & metav1 . LabelSelector {
2019-08-21 12:03:53 -07:00
MatchLabels : nil ,
2019-06-07 14:46:18 +03:00
MatchExpressions : [ ] metav1 . LabelSelectorRequirement {
2020-02-03 13:38:24 -08:00
{
2019-08-21 12:03:53 -07:00
Key : "app" ,
2019-06-07 14:46:18 +03:00
Operator : "NotIn" ,
Values : [ ] string {
2019-08-21 12:03:53 -07:00
"nginx1" ,
"nginx2" ,
2019-06-07 14:46:18 +03:00
} ,
} ,
} ,
} ,
}
2021-10-29 18:13:20 +02:00
resourceDescriptionExclude := v1 . ResourceDescription {
2019-06-07 14:46:18 +03:00
Selector : & metav1 . LabelSelector {
MatchLabels : map [ string ] string {
2019-08-21 12:03:53 -07:00
"block" : "true" ,
2019-06-07 14:46:18 +03:00
} ,
} ,
}
2022-12-12 07:20:20 -08:00
rule := v1 . Rule {
MatchResources : v1 . MatchResources { ResourceDescription : resourceDescription } ,
2024-09-10 13:14:49 +02:00
ExcludeResources : & v1 . MatchResources { ResourceDescription : resourceDescriptionExclude } ,
2022-12-12 07:20:20 -08:00
}
2019-06-07 14:46:18 +03:00
2024-06-20 11:44:43 +02:00
if err := MatchesResourceDescription ( * resource , rule , v2 . RequestInfo { } , nil , "" , resource . GroupVersionKind ( ) , "" , "CREATE" ) ; err == nil {
2020-11-17 12:01:01 -08:00
t . Errorf ( "Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail" )
2020-02-07 14:45:43 +05:30
}
2019-06-07 14:46:18 +03:00
}