1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 02:45:06 +00:00

refactor & validate operations for generate rules in PolicyValidation

This commit is contained in:
shivkumar dudhani 2020-03-11 18:14:23 -07:00
parent 8480276f23
commit b1063a95e1
17 changed files with 1332 additions and 793 deletions

108
pkg/auth/auth.go Normal file
View file

@ -0,0 +1,108 @@
package auth
import (
"fmt"
"reflect"
"github.com/golang/glog"
client "github.com/nirmata/kyverno/pkg/dclient"
authorizationv1 "k8s.io/api/authorization/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
//CanIOptions provides utility ti check if user has authorization for the given operation
type CanIOptions struct {
namespace string
verb string
kind string
client *client.Client
}
//NewCanI returns a new instance of operation access controler evaluator
func NewCanI(client *client.Client, kind, namespace, verb string) *CanIOptions {
o := CanIOptions{
client: client,
}
o.namespace = namespace
o.kind = kind
o.verb = verb
return &o
}
//RunAccessCheck checks if the caller can perform the operation
// - operation is a combination of namespace, kind, verb
// - can only evaluate a single verb
// - group version resource is determined from the kind using the discovery client REST mapper
// - If disallowed, the reason and evaluationError is avialable in the logs
// - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions
func (o *CanIOptions) RunAccessCheck() (bool, error) {
// get GroupVersionResource from RESTMapper
// get GVR from kind
gvr := o.client.DiscoveryClient.GetGVRFromKind(o.kind)
if reflect.DeepEqual(gvr, schema.GroupVersionResource{}) {
// cannot find GVR
return false, fmt.Errorf("failed to get the Group Version Resource for kind %s", o.kind)
}
var sar *authorizationv1.SelfSubjectAccessReview
sar = &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: o.namespace,
Verb: o.verb,
Group: gvr.Group,
Resource: gvr.Resource,
},
},
}
// Set self subject access review
// - namespace
// - verb
// - resource
// - subresource
// Create the Resource
resp, err := o.client.CreateResource("SelfSubjectAccessReview", "", sar, false)
if err != nil {
glog.Errorf("failed to create resource %s/%s/%s", sar.Kind, sar.Namespace, sar.Name)
return false, err
}
// status.allowed
allowed, ok, err := unstructured.NestedBool(resp.Object, "status", "allowed")
if !ok {
if err != nil {
glog.Errorf("unexpected error when getting status.allowed for %s/%s/%s", sar.Kind, sar.Namespace, sar.Name)
}
glog.Errorf("status.allowed not found for %s/%s/%s", sar.Kind, sar.Namespace, sar.Name)
}
if !allowed {
// status.reason
reason, ok, err := unstructured.NestedString(resp.Object, "status", "reason")
if !ok {
if err != nil {
glog.Errorf("unexpected error when getting status.reason for %s/%s/%s", sar.Kind, sar.Namespace, sar.Name)
}
glog.Errorf("status.reason not found for %s/%s/%s", sar.Kind, sar.Namespace, sar.Name)
}
// status.evaluationError
evaluationError, ok, err := unstructured.NestedString(resp.Object, "status", "evaludationError")
if !ok {
if err != nil {
glog.Errorf("unexpected error when getting status.evaluationError for %s/%s/%s", sar.Kind, sar.Namespace, sar.Name)
}
glog.Errorf("status.evaluationError not found for %s/%s/%s", sar.Kind, sar.Namespace, sar.Name)
}
// Reporting ? (just logs)
glog.Errorf("reason to disallow operation: %s", reason)
glog.Errorf("evaluationError to disallow operation: %s", evaluationError)
}
return allowed, nil
}

48
pkg/auth/auth_test.go Normal file
View file

@ -0,0 +1,48 @@
package auth
// import (
// "testing"
// "time"
// "github.com/golang/glog"
// "github.com/nirmata/kyverno/pkg/config"
// dclient "github.com/nirmata/kyverno/pkg/dclient"
// "github.com/nirmata/kyverno/pkg/signal"
// )
// func Test_Auth_pass(t *testing.T) {
// // needs running cluster
// var kubeconfig string
// stopCh := signal.SetupSignalHandler()
// kubeconfig = "/Users/shivd/.kube/config"
// clientConfig, err := config.CreateClientConfig(kubeconfig)
// if err != nil {
// glog.Fatalf("Error building kubeconfig: %v\n", err)
// }
// // DYNAMIC CLIENT
// // - client for all registered resources
// // - invalidate local cache of registered resource every 10 seconds
// client, err := dclient.NewClient(clientConfig, 10*time.Second, stopCh)
// if err != nil {
// glog.Fatalf("Error creating client: %v\n", err)
// }
// // Can i authenticate
// kind := "Deployment"
// namespace := "default"
// verb := "test"
// canI := NewCanI(client, kind, namespace, verb)
// ok, err := canI.RunAccessCheck()
// if err != nil {
// t.Error(err)
// }
// if ok {
// t.Log("allowed")
// } else {
// t.Log("notallowed")
// }
// t.FailNow()
// }

View file

@ -0,0 +1,14 @@
package variables
import "regexp"
//IsVariable returns true if the element contains a 'valid' variable {{}}
func IsVariable(element string) bool {
validRegex := regexp.MustCompile(variableRegex)
groups := validRegex.FindAllStringSubmatch(element, -1)
if len(groups) == 0 {
// there was no match
return false
}
return true
}

50
pkg/policy/actions.go Normal file
View file

@ -0,0 +1,50 @@
package policy
import (
"fmt"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/policy/generate"
"github.com/nirmata/kyverno/pkg/policy/mutate"
"github.com/nirmata/kyverno/pkg/policy/validate"
)
//Validation provides methods to validate a rule
type Validation interface {
Validate() (string, error)
}
//validateAction performs validation on the rule actions
// - Mutate
// - Validation
// - Generate
func validateActions(idx int, rule kyverno.Rule, client *dclient.Client) error {
var checker Validation
// Mutate
if rule.HasMutate() {
checker = mutate.NewMutateFactory(rule.Mutation)
if path, err := checker.Validate(); err != nil {
return fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", idx, path, err)
}
}
// Validate
if rule.HasValidate() {
checker = validate.NewValidateFactory(rule.Validation)
if path, err := checker.Validate(); err != nil {
return fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", idx, path, err)
}
}
// Generate
if rule.HasGenerate() {
checker = generate.NewGenerateFactory(client, rule.Generation)
if path, err := checker.Validate(); err != nil {
return fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
}
}
return nil
}

View file

@ -0,0 +1,85 @@
package common
import (
"fmt"
"regexp"
"strconv"
"github.com/nirmata/kyverno/pkg/engine/anchor"
)
//ValidatePattern validates the pattern
func ValidatePattern(patternElement interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
switch typedPatternElement := patternElement.(type) {
case map[string]interface{}:
return validateMap(typedPatternElement, path, supportedAnchors)
case []interface{}:
return validateArray(typedPatternElement, path, supportedAnchors)
case string, float64, int, int64, bool, nil:
//TODO? check operator
return "", nil
default:
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
}
}
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
// check if anchors are defined
for key, value := range patternMap {
// if key is anchor
// check regex () -> this is anchor
// ()
// single char ()
re, err := regexp.Compile(`^.?\(.+\)$`)
if err != nil {
return path + "/" + key, fmt.Errorf("Unable to parse the field %s: %v", key, err)
}
matched := re.MatchString(key)
// check the type of anchor
if matched {
// some type of anchor
// check if valid anchor
if !checkAnchors(key, supportedAnchors) {
return path + "/" + key, fmt.Errorf("Unsupported anchor %s", key)
}
// addition check for existence anchor
// value must be of type list
if anchor.IsExistenceAnchor(key) {
typedValue, ok := value.([]interface{})
if !ok {
return path + "/" + key, fmt.Errorf("Existence anchor should have value of type list")
}
// validate there is only one entry in the list
if len(typedValue) == 0 || len(typedValue) > 1 {
return path + "/" + key, fmt.Errorf("Existence anchor: single value expected, multiple specified")
}
}
}
// lets validate the values now :)
if errPath, err := ValidatePattern(value, path+"/"+key, supportedAnchors); err != nil {
return errPath, err
}
}
return "", nil
}
func validateArray(patternArray []interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
// lets validate the values now :)
if errPath, err := ValidatePattern(patternElement, currentPath, supportedAnchors); err != nil {
return errPath, err
}
}
return "", nil
}
func checkAnchors(key string, supportedAnchors []anchor.IsAnchor) bool {
for _, f := range supportedAnchors {
if f(key) {
return true
}
}
return false
}

View file

@ -0,0 +1,71 @@
package generate
import (
"github.com/nirmata/kyverno/pkg/auth"
dclient "github.com/nirmata/kyverno/pkg/dclient"
)
//Operations provides methods to performing operations on resource
type Operations interface {
// CanICreate returns 'true' if self can 'create' resource
CanICreate(kind, namespace string) (bool, error)
// CanIUpdate returns 'true' if self can 'update' resource
CanIUpdate(kind, namespace string) (bool, error)
// CanIDelete returns 'true' if self can 'delete' resource
CanIDelete(kind, namespace string) (bool, error)
// CanIGet returns 'true' if self can 'get' resource
CanIGet(kind, namespace string) (bool, error)
}
//Auth provides implementation to check if caller/self/kyverno has access to perofrm operations
type Auth struct {
client *dclient.Client
}
//NewAuth returns a new instance of Auth for operations
func NewAuth(client *dclient.Client) *Auth {
a := Auth{
client: client,
}
return &a
}
// CanICreate returns 'true' if self can 'create' resource
func (a *Auth) CanICreate(kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "create")
ok, err := canI.RunAccessCheck()
if err != nil {
return false, err
}
return ok, nil
}
// CanIUpdate returns 'true' if self can 'update' resource
func (a *Auth) CanIUpdate(kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "update")
ok, err := canI.RunAccessCheck()
if err != nil {
return false, err
}
return ok, nil
}
// CanIDelete returns 'true' if self can 'delete' resource
func (a *Auth) CanIDelete(kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "delete")
ok, err := canI.RunAccessCheck()
if err != nil {
return false, err
}
return ok, nil
}
// CanIGet returns 'true' if self can 'get' resource
func (a *Auth) CanIGet(kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "get")
ok, err := canI.RunAccessCheck()
if err != nil {
return false, err
}
return ok, nil
}

View file

@ -0,0 +1,21 @@
package generate
import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/policy/generate/fake"
)
//FakeGenerate provides implementation for generate rule processing
// with mocks/fakes for cluster interactions
type FakeGenerate struct {
Generate
}
//NewFakeGenerate returns a new instance of generatecheck that uses
// fake/mock implementation for operation access(always returns true)
func NewFakeGenerate(rule kyverno.Generation) *FakeGenerate {
g := FakeGenerate{}
g.rule = rule
g.authCheck = fake.NewFakeAuth()
return &g
}

View file

@ -0,0 +1,31 @@
package fake
//FakeAuth providers implementation for testing, retuning true for all operations
type FakeAuth struct {
}
//NewFakeAuth returns a new instance of Fake Auth that returns true for each operation
func NewFakeAuth() *FakeAuth {
a := FakeAuth{}
return &a
}
// CanICreate returns 'true'
func (a *FakeAuth) CanICreate(kind, namespace string) (bool, error) {
return true, nil
}
// CanIUpdate returns 'true'
func (a *FakeAuth) CanIUpdate(kind, namespace string) (bool, error) {
return true, nil
}
// CanIDelete returns 'true'
func (a *FakeAuth) CanIDelete(kind, namespace string) (bool, error) {
return true, nil
}
// CanIGet returns 'true'
func (a *FakeAuth) CanIGet(kind, namespace string) (bool, error) {
return true, nil
}

View file

@ -0,0 +1,148 @@
package generate
import (
"fmt"
"reflect"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/engine/variables"
"github.com/nirmata/kyverno/pkg/policy/common"
)
// Generate provides implementation to validate 'generate' rule
type Generate struct {
// rule to hold 'generate' rule specifications
rule kyverno.Generation
// authCheck to check access for operations
authCheck Operations
}
//NewGenerateFactory returns a new instance of Generate validation checker
func NewGenerateFactory(client *dclient.Client, rule kyverno.Generation) *Generate {
g := Generate{
rule: rule,
authCheck: NewAuth(client),
}
return &g
}
//Validate validates the generate rule in validation
func (g *Generate) Validate() (string, error) {
rule := g.rule
if rule.Data == nil && rule.Clone == (kyverno.CloneFrom{}) {
return "", fmt.Errorf("clone or data are required")
}
if rule.Data != nil && rule.Clone != (kyverno.CloneFrom{}) {
return "", fmt.Errorf("only one operation allowed per generate rule(data or clone)")
}
kind, name, namespace := rule.Kind, rule.Name, rule.Namespace
if name == "" {
return "name", fmt.Errorf("name cannot be empty")
}
if kind == "" {
return "kind", fmt.Errorf("kind cannot be empty")
}
// Can I generate resource
if !reflect.DeepEqual(rule.Clone, kyverno.CloneFrom{}) {
if path, err := g.validateClone(rule.Clone, kind); err != nil {
return fmt.Sprintf("clone.%s", path), err
}
}
if rule.Data != nil {
//TODO: is this required ?? as anchors can only be on pattern and not resource
// we can add this check by not sure if its needed here
if path, err := common.ValidatePattern(rule.Data, "/", []anchor.IsAnchor{}); err != nil {
return fmt.Sprintf("data.%s", path), fmt.Errorf("anchors not supported on generate resources: %v", err)
}
}
// Kyverno generate-controller create/update/deletes the resources specified in generate rule of policy
// kyverno uses SA 'kyverno-service-account' and has default ClusterRoles and ClusterRoleBindings
// instuctions to modify the RBAC for kyverno are mentioned at https://github.com/nirmata/kyverno/blob/master/documentation/installation.md
// - operations required: create/update/delete/get
// If kind and namespace contain variables, then we cannot resolve then so we skip the processing
if err := g.canIGenerate(kind, namespace); err != nil {
return "", err
}
return "", nil
}
func (g *Generate) validateClone(c kyverno.CloneFrom, kind string) (string, error) {
if c.Name == "" {
return "name", fmt.Errorf("name cannot be empty")
}
if c.Namespace == "" {
return "namespace", fmt.Errorf("namespace cannot be empty")
}
namespace := c.Namespace
// Skip if there is variable defined
if !variables.IsVariable(kind) && !variables.IsVariable(namespace) {
// GET
ok, err := g.authCheck.CanIGet(kind, namespace)
if err != nil {
return "", err
}
if !ok {
return "", fmt.Errorf("kyverno does not have permissions to 'get' resource %s/%s. Update permissions in ClusterRole 'kyverno:generatecontroller'", kind, namespace)
}
} else {
glog.V(4).Info("name & namespace uses variables, so cannot be resolved. Skipping Auth Checks.")
}
return "", nil
}
//canIGenerate returns a error if kyverno cannot perform oprations
func (g *Generate) canIGenerate(kind, namespace string) error {
// Skip if there is variable defined
authCheck := g.authCheck
if !variables.IsVariable(kind) && !variables.IsVariable(namespace) {
// CREATE
ok, err := authCheck.CanICreate(kind, namespace)
if err != nil {
// machinery error
return err
}
if !ok {
return fmt.Errorf("kyverno does not have permissions to 'create' resource %s/%s. Update permissions in ClusterRole 'kyverno:generatecontroller'", kind, namespace)
}
// UPDATE
ok, err = authCheck.CanIUpdate(kind, namespace)
if err != nil {
// machinery error
return err
}
if !ok {
return fmt.Errorf("kyverno does not have permissions to 'update' resource %s/%s. Update permissions in ClusterRole 'kyverno:generatecontroller'", kind, namespace)
}
// GET
ok, err = authCheck.CanIGet(kind, namespace)
if err != nil {
// machinery error
return err
}
if !ok {
return fmt.Errorf("kyverno does not have permissions to 'get' resource %s/%s. Update permissions in ClusterRole 'kyverno:generatecontroller'", kind, namespace)
}
// DELETE
ok, err = authCheck.CanIDelete(kind, namespace)
if err != nil {
// machinery error
return err
}
if !ok {
return fmt.Errorf("kyverno does not have permissions to 'delete' resource %s/%s. Update permissions in ClusterRole 'kyverno:generatecontroller'", kind, namespace)
}
} else {
glog.V(4).Info("name & namespace uses variables, so cannot be resolved. Skipping Auth Checks.")
}
return nil
}

View file

@ -0,0 +1,89 @@
package generate
import (
"encoding/json"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"gotest.tools/assert"
)
func Test_Validate_Generate(t *testing.T) {
rawGenerate := []byte(`
{
"kind": "NetworkPolicy",
"name": "defaultnetworkpolicy",
"data": {
"spec": {
"podSelector": {},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [
{}
],
"egress": [
{}
]
}
}
}`)
var genRule kyverno.Generation
err := json.Unmarshal(rawGenerate, &genRule)
assert.NilError(t, err)
checker := NewFakeGenerate(genRule)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Generate_HasAnchors(t *testing.T) {
var err error
rawGenerate := []byte(`
{
"kind": "NetworkPolicy",
"name": "defaultnetworkpolicy",
"data": {
"spec": {
"(podSelector)": {},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [
{}
],
"egress": [
{}
]
}
}
}`)
var genRule kyverno.Generation
err = json.Unmarshal(rawGenerate, &genRule)
assert.NilError(t, err)
checker := NewFakeGenerate(genRule)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
rawGenerate = []byte(`
{
"kind": "ConfigMap",
"name": "copied-cm",
"clone": {
"^(namespace)": "default",
"name": "game"
}
}`)
err = json.Unmarshal(rawGenerate, &genRule)
assert.NilError(t, err)
checker = NewFakeGenerate(genRule)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}

View file

@ -0,0 +1,63 @@
package mutate
import (
"errors"
"fmt"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/policy/common"
)
// Mutate provides implementation to validate 'mutate' rule
type Mutate struct {
// rule to hold 'mutate' rule specifications
rule kyverno.Mutation
}
//NewMutateFactory returns a new instance of Mutate validation checker
func NewMutateFactory(rule kyverno.Mutation) *Mutate {
m := Mutate{
rule: rule,
}
return &m
}
//Validate validates the 'mutate' rul
func (m *Mutate) Validate() (string, error) {
rule := m.rule
// JSON Patches
if len(rule.Patches) != 0 {
for i, patch := range rule.Patches {
if err := validatePatch(patch); err != nil {
return fmt.Sprintf("patch[%d]", i), err
}
}
}
// Overlay
if rule.Overlay != nil {
path, err := common.ValidatePattern(rule.Overlay, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsAddingAnchor})
if err != nil {
return path, err
}
}
return "", nil
}
// Validate if all mandatory PolicyPatch fields are set
func validatePatch(pp kyverno.Patch) error {
if pp.Path == "" {
return errors.New("JSONPatch field 'path' is mandatory")
}
if pp.Operation == "add" || pp.Operation == "replace" {
if pp.Value == nil {
return fmt.Errorf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation)
}
return nil
} else if pp.Operation == "remove" {
return nil
}
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
}

View file

@ -0,0 +1,151 @@
package mutate
import (
"encoding/json"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"gotest.tools/assert"
)
func Test_Validate_Mutate_ConditionAnchor(t *testing.T) {
rawMutate := []byte(`
{
"overlay": {
"spec": {
"(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
var mutate kyverno.Mutation
err := json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
checker := NewMutateFactory(mutate)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_Mutate_PlusAnchor(t *testing.T) {
rawMutate := []byte(`
{
"overlay": {
"spec": {
"+(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
var mutate kyverno.Mutation
err := json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
checker := NewMutateFactory(mutate)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_Mutate_Mismatched(t *testing.T) {
rawMutate := []byte(`
{
"overlay": {
"spec": {
"^(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
var mutateExistence kyverno.Mutation
err := json.Unmarshal(rawMutate, &mutateExistence)
assert.NilError(t, err)
checker := NewMutateFactory(mutateExistence)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
var mutateEqual kyverno.Mutation
rawMutate = []byte(`
{
"overlay": {
"spec": {
"=(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutateEqual)
assert.NilError(t, err)
checker = NewMutateFactory(mutateEqual)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
var mutateNegation kyverno.Mutation
rawMutate = []byte(`
{
"overlay": {
"spec": {
"X(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutateNegation)
assert.NilError(t, err)
checker = NewMutateFactory(mutateEqual)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Mutate_Unsupported(t *testing.T) {
var err error
var mutate kyverno.Mutation
// case 1
rawMutate := []byte(`
{
"overlay": {
"spec": {
"!(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
checker := NewMutateFactory(mutate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
// case 2
rawMutate = []byte(`
{
"overlay": {
"spec": {
"~(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
checker = NewMutateFactory(mutate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}

View file

@ -4,12 +4,10 @@ import (
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
dclient "github.com/nirmata/kyverno/pkg/dclient"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -17,7 +15,7 @@ import (
// Validate does some initial check to verify some conditions
// - One operation per rule
// - ResourceDescription mandatory checks
func Validate(p kyverno.ClusterPolicy) error {
func Validate(p kyverno.ClusterPolicy, client *dclient.Client) error {
if path, err := validateUniqueRuleName(p); err != nil {
return fmt.Errorf("path: spec.%s: %v", path, err)
}
@ -50,24 +48,12 @@ func Validate(p kyverno.ClusterPolicy) error {
// as there are more than 1 operation in rule, not need to evaluate it further
return fmt.Errorf("path: spec.rules[%d]: %v", i, err)
}
// Operation Validation
// Mutation
if rule.HasMutate() {
if path, err := validateMutation(rule.Mutation); err != nil {
return fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", i, path, err)
}
}
// Validation
if rule.HasValidate() {
if path, err := validateValidation(rule.Validation); err != nil {
return fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", i, path, err)
}
}
// Generation
if rule.HasGenerate() {
if path, err := validateGeneration(rule.Generation); err != nil {
return fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", i, path, err)
}
// validate rule actions
// - Mutate
// - Validate
// - Generate
if err := validateActions(i, rule, client); err != nil {
return err
}
// If a rules match block does not match any kind,
@ -262,193 +248,3 @@ func validateResourceDescription(rd kyverno.ResourceDescription) error {
}
return nil
}
func validateMutation(m kyverno.Mutation) (string, error) {
// JSON Patches
if len(m.Patches) != 0 {
for i, patch := range m.Patches {
if err := validatePatch(patch); err != nil {
return fmt.Sprintf("patch[%d]", i), err
}
}
}
// Overlay
if m.Overlay != nil {
path, err := validatePattern(m.Overlay, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsAddingAnchor})
if err != nil {
return path, err
}
}
return "", nil
}
// Validate if all mandatory PolicyPatch fields are set
func validatePatch(pp kyverno.Patch) error {
if pp.Path == "" {
return errors.New("JSONPatch field 'path' is mandatory")
}
if pp.Operation == "add" || pp.Operation == "replace" {
if pp.Value == nil {
return fmt.Errorf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation)
}
return nil
} else if pp.Operation == "remove" {
return nil
}
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
}
func validateValidation(v kyverno.Validation) (string, error) {
if err := validateOverlayPattern(v); err != nil {
// no need to proceed ahead
return "", err
}
if v.Pattern != nil {
if path, err := validatePattern(v.Pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistenceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
return fmt.Sprintf("pattern.%s", path), err
}
}
if len(v.AnyPattern) != 0 {
for i, pattern := range v.AnyPattern {
if path, err := validatePattern(pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistenceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
return fmt.Sprintf("anyPattern[%d].%s", i, path), err
}
}
}
return "", nil
}
// validateOverlayPattern checks one of pattern/anyPattern must exist
func validateOverlayPattern(v kyverno.Validation) error {
if v.Pattern == nil && len(v.AnyPattern) == 0 {
return fmt.Errorf("a pattern or anyPattern must be specified")
}
if v.Pattern != nil && len(v.AnyPattern) != 0 {
return fmt.Errorf("only one operation allowed per validation rule(pattern or anyPattern)")
}
return nil
}
// Validate returns error if generator is configured incompletely
func validateGeneration(gen kyverno.Generation) (string, error) {
if gen.Data == nil && gen.Clone == (kyverno.CloneFrom{}) {
return "", fmt.Errorf("clone or data are required")
}
if gen.Data != nil && gen.Clone != (kyverno.CloneFrom{}) {
return "", fmt.Errorf("only one operation allowed per generate rule(data or clone)")
}
// check kind is non empty
// check name is non empty
if gen.Name == "" {
return "name", fmt.Errorf("name cannot be empty")
}
if gen.Kind == "" {
return "kind", fmt.Errorf("kind cannot be empty")
}
if !reflect.DeepEqual(gen.Clone, kyverno.CloneFrom{}) {
if path, err := validateClone(gen.Clone); err != nil {
return fmt.Sprintf("clone.%s", path), err
}
}
if gen.Data != nil {
//TODO: is this required ?? as anchors can only be on pattern and not resource
// we can add this check by not sure if its needed here
if path, err := validatePattern(gen.Data, "/", []anchor.IsAnchor{}); err != nil {
return fmt.Sprintf("data.%s", path), fmt.Errorf("anchors not supported on generate resources: %v", err)
}
}
return "", nil
}
func validateClone(c kyverno.CloneFrom) (string, error) {
if c.Name == "" {
return "name", fmt.Errorf("name cannot be empty")
}
if c.Namespace == "" {
return "namespace", fmt.Errorf("namespace cannot be empty")
}
return "", nil
}
func validatePattern(patternElement interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
switch typedPatternElement := patternElement.(type) {
case map[string]interface{}:
return validateMap(typedPatternElement, path, supportedAnchors)
case []interface{}:
return validateArray(typedPatternElement, path, supportedAnchors)
case string, float64, int, int64, bool, nil:
//TODO? check operator
return "", nil
default:
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
}
}
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
// check if anchors are defined
for key, value := range patternMap {
// if key is anchor
// check regex () -> this is anchor
// ()
// single char ()
re, err := regexp.Compile(`^.?\(.+\)$`)
if err != nil {
return path + "/" + key, fmt.Errorf("Unable to parse the field %s: %v", key, err)
}
matched := re.MatchString(key)
// check the type of anchor
if matched {
// some type of anchor
// check if valid anchor
if !checkAnchors(key, supportedAnchors) {
return path + "/" + key, fmt.Errorf("Unsupported anchor %s", key)
}
// addition check for existence anchor
// value must be of type list
if anchor.IsExistenceAnchor(key) {
typedValue, ok := value.([]interface{})
if !ok {
return path + "/" + key, fmt.Errorf("Existence anchor should have value of type list")
}
// validate there is only one entry in the list
if len(typedValue) == 0 || len(typedValue) > 1 {
return path + "/" + key, fmt.Errorf("Existence anchor: single value expected, multiple specified")
}
}
}
// lets validate the values now :)
if errPath, err := validatePattern(value, path+"/"+key, supportedAnchors); err != nil {
return errPath, err
}
}
return "", nil
}
func validateArray(patternArray []interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
// lets validate the values now :)
if errPath, err := validatePattern(patternElement, currentPath, supportedAnchors); err != nil {
return errPath, err
}
}
return "", nil
}
func checkAnchors(key string, supportedAnchors []anchor.IsAnchor) bool {
for _, f := range supportedAnchors {
if f(key) {
return true
}
}
return false
}

View file

@ -0,0 +1,61 @@
package validate
import (
"fmt"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/policy/common"
)
// Validate provides implementation to validate 'validate' rule
type Validate struct {
// rule to hold 'validate' rule specifications
rule kyverno.Validation
}
//NewValidateFactory returns a new instance of Mutate validation checker
func NewValidateFactory(rule kyverno.Validation) *Validate {
m := Validate{
rule: rule,
}
return &m
}
//Validate validates the 'validate' rule
func (v *Validate) Validate() (string, error) {
rule := v.rule
if err := v.validateOverlayPattern(); err != nil {
// no need to proceed ahead
return "", err
}
if rule.Pattern != nil {
if path, err := common.ValidatePattern(rule.Pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistenceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
return fmt.Sprintf("pattern.%s", path), err
}
}
if len(rule.AnyPattern) != 0 {
for i, pattern := range rule.AnyPattern {
if path, err := common.ValidatePattern(pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistenceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
return fmt.Sprintf("anyPattern[%d].%s", i, path), err
}
}
}
return "", nil
}
// validateOverlayPattern checks one of pattern/anyPattern must exist
func (v *Validate) validateOverlayPattern() error {
rule := v.rule
if rule.Pattern == nil && len(rule.AnyPattern) == 0 {
return fmt.Errorf("a pattern or anyPattern must be specified")
}
if rule.Pattern != nil && len(rule.AnyPattern) != 0 {
return fmt.Errorf("only one operation allowed per validation rule(pattern or anyPattern)")
}
return nil
}

View file

@ -0,0 +1,381 @@
package validate
import (
"encoding/json"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"gotest.tools/assert"
)
func Test_Validate_OverlayPattern_Empty(t *testing.T) {
rawValidation := []byte(`
{}`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_OverlayPattern_Nil_PatternAnypattern(t *testing.T) {
rawValidation := []byte(`
{ "message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false"
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_OverlayPattern_Exist_PatternAnypattern(t *testing.T) {
rawValidation := []byte(`
{
"message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false",
"anyPattern": [
{
"spec": {
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
}
],
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
]
}
}
}`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_OverlayPattern_Valid(t *testing.T) {
rawValidation := []byte(`
{
"message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false",
"anyPattern": [
{
"spec": {
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
},
{
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
]
}
}
]
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_ExistingAnchor_AnchorOnMap(t *testing.T) {
rawValidation := []byte(`
{
"message": "validate container security contexts",
"anyPattern": [
{
"spec": {
"template": {
"spec": {
"containers": [
{
"^(securityContext)": {
"runAsNonRoot": true
}
}
]
}
}
}
}
]
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_ExistingAnchor_AnchorOnString(t *testing.T) {
rawValidation := []byte(`{
"message": "validate container security contexts",
"pattern": {
"spec": {
"template": {
"spec": {
"containers": [
{
"securityContext": {
"allowPrivilegeEscalation": "^(false)"
}
}
]
}
}
}
}
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_ExistingAnchor_Valid(t *testing.T) {
var err error
var validation kyverno.Validation
rawValidation := []byte(`
{
"message": "validate container security contexts",
"anyPattern": [
{
"spec": {
"template": {
"spec": {
"^(containers)": [
{
"securityContext": {
"runAsNonRoot": "true"
}
}
]
}
}
}
}
]
}`)
err = json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
rawValidation = []byte(`
{
"message": "validate container security contexts",
"pattern": {
"spec": {
"template": {
"spec": {
"^(containers)": [
{
"securityContext": {
"allowPrivilegeEscalation": "false"
}
}
]
}
}
}
}
} `)
err = json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker = NewValidateFactory(validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Validate_ValidAnchor(t *testing.T) {
var err error
var validate kyverno.Validation
var rawValidate []byte
// case 1
rawValidate = []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"anyPattern": [
{
"spec": {
"securityContext": {
"(runAsNonRoot)": true
}
}
},
{
"spec": {
"^(containers)": [
{
"name": "*",
"securityContext": {
"runAsNonRoot": true
}
}
]
}
}
]
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker := NewValidateFactory(validate)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
// case 2
validate = kyverno.Validation{}
rawValidate = []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"=(securityContext)": {
"runAsNonRoot": "true"
}
}
}
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker = NewValidateFactory(validate)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_Validate_Mismatched(t *testing.T) {
rawValidate := []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"+(runAsNonRoot)": true
}
}
]
}
}
}`)
var validate kyverno.Validation
err := json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker := NewValidateFactory(validate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Validate_Unsupported(t *testing.T) {
var err error
var validate kyverno.Validation
// case 1
rawValidate := []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"!(runAsNonRoot)": true
}
}
]
}
}
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker := NewValidateFactory(validate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
// case 2
rawValidate = []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"~(runAsNonRoot)": true
}
}
]
}
}
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker = NewValidateFactory(validate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
}

View file

@ -287,369 +287,6 @@ func Test_Validate_ResourceDescription_InvalidSelector(t *testing.T) {
assert.Assert(t, err != nil)
}
func Test_Validate_OverlayPattern_Empty(t *testing.T) {
rawValidation := []byte(`
{}`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_OverlayPattern_Nil_PatternAnypattern(t *testing.T) {
rawValidation := []byte(`
{ "message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false"
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_OverlayPattern_Exist_PatternAnypattern(t *testing.T) {
rawValidation := []byte(`
{
"message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false",
"anyPattern": [
{
"spec": {
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
}
],
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
]
}
}
}`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_OverlayPattern_Valid(t *testing.T) {
rawValidation := []byte(`
{
"message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false",
"anyPattern": [
{
"spec": {
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
},
{
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"allowPrivilegeEscalation": false,
"privileged": false
}
}
]
}
}
]
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_ExistingAnchor_AnchorOnMap(t *testing.T) {
rawValidation := []byte(`
{
"message": "validate container security contexts",
"anyPattern": [
{
"spec": {
"template": {
"spec": {
"containers": [
{
"^(securityContext)": {
"runAsNonRoot": true
}
}
]
}
}
}
}
]
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_ExistingAnchor_AnchorOnString(t *testing.T) {
rawValidation := []byte(`{
"message": "validate container security contexts",
"pattern": {
"spec": {
"template": {
"spec": {
"containers": [
{
"securityContext": {
"allowPrivilegeEscalation": "^(false)"
}
}
]
}
}
}
}
}
`)
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_ExistingAnchor_Valid(t *testing.T) {
var err error
var validation kyverno.Validation
rawValidation := []byte(`
{
"message": "validate container security contexts",
"anyPattern": [
{
"spec": {
"template": {
"spec": {
"^(containers)": [
{
"securityContext": {
"runAsNonRoot": "true"
}
}
]
}
}
}
}
]
}`)
err = json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.Assert(t, err != nil)
}
rawValidation = []byte(`
{
"message": "validate container security contexts",
"pattern": {
"spec": {
"template": {
"spec": {
"^(containers)": [
{
"securityContext": {
"allowPrivilegeEscalation": "false"
}
}
]
}
}
}
}
} `)
err = json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
if _, err := validateValidation(validation); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Validate_ValidAnchor(t *testing.T) {
var err error
var validate kyverno.Validation
var rawValidate []byte
// case 1
rawValidate = []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"anyPattern": [
{
"spec": {
"securityContext": {
"(runAsNonRoot)": true
}
}
},
{
"spec": {
"^(containers)": [
{
"name": "*",
"securityContext": {
"runAsNonRoot": true
}
}
]
}
}
]
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
if _, err := validateValidation(validate); err != nil {
assert.NilError(t, err)
}
// case 2
validate = kyverno.Validation{}
rawValidate = []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"=(securityContext)": {
"runAsNonRoot": "true"
}
}
}
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
if _, err := validateValidation(validate); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_Validate_Mismatched(t *testing.T) {
rawValidate := []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"+(runAsNonRoot)": true
}
}
]
}
}
}`)
var validate kyverno.Validation
err := json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
if _, err := validateValidation(validate); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Validate_Unsupported(t *testing.T) {
var err error
var validate kyverno.Validation
// case 1
rawValidate := []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"!(runAsNonRoot)": true
}
}
]
}
}
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
if _, err := validateValidation(validate); err != nil {
assert.Assert(t, err != nil)
}
// case 2
rawValidate = []byte(`
{
"message": "Root user is not allowed. Set runAsNonRoot to true.",
"pattern": {
"spec": {
"containers": [
{
"name": "*",
"securityContext": {
"~(runAsNonRoot)": true
}
}
]
}
}
}`)
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
if _, err := validateValidation(validate); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Policy(t *testing.T) {
rawPolicy := []byte(`
{
@ -736,225 +373,10 @@ func Test_Validate_Policy(t *testing.T) {
err := json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = Validate(policy)
err = Validate(policy, nil)
assert.NilError(t, err)
}
func Test_Validate_Mutate_ConditionAnchor(t *testing.T) {
rawMutate := []byte(`
{
"overlay": {
"spec": {
"(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
var mutate kyverno.Mutation
err := json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
if _, err := validateMutation(mutate); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_Mutate_PlusAnchor(t *testing.T) {
rawMutate := []byte(`
{
"overlay": {
"spec": {
"+(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
var mutate kyverno.Mutation
err := json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
if _, err := validateMutation(mutate); err != nil {
assert.NilError(t, err)
}
}
func Test_Validate_Mutate_Mismatched(t *testing.T) {
rawMutate := []byte(`
{
"overlay": {
"spec": {
"^(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
var mutateExistence kyverno.Mutation
err := json.Unmarshal(rawMutate, &mutateExistence)
assert.NilError(t, err)
if _, err := validateMutation(mutateExistence); err != nil {
assert.Assert(t, err != nil)
}
var mutateEqual kyverno.Mutation
rawMutate = []byte(`
{
"overlay": {
"spec": {
"=(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutateEqual)
assert.NilError(t, err)
if _, err := validateMutation(mutateEqual); err != nil {
assert.Assert(t, err != nil)
}
var mutateNegation kyverno.Mutation
rawMutate = []byte(`
{
"overlay": {
"spec": {
"X(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutateNegation)
assert.NilError(t, err)
if _, err := validateMutation(mutateEqual); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Mutate_Unsupported(t *testing.T) {
var err error
var mutate kyverno.Mutation
// case 1
rawMutate := []byte(`
{
"overlay": {
"spec": {
"!(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
if _, err := validateMutation(mutate); err != nil {
assert.Assert(t, err != nil)
}
// case 2
rawMutate = []byte(`
{
"overlay": {
"spec": {
"~(serviceAccountName)": "*",
"automountServiceAccountToken": false
}
}
}`)
err = json.Unmarshal(rawMutate, &mutate)
assert.NilError(t, err)
if _, err := validateMutation(mutate); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Generate(t *testing.T) {
rawGenerate := []byte(`
{
"kind": "NetworkPolicy",
"name": "defaultnetworkpolicy",
"data": {
"spec": {
"podSelector": {},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [
{}
],
"egress": [
{}
]
}
}
}`)
var generate kyverno.Generation
err := json.Unmarshal(rawGenerate, &generate)
assert.NilError(t, err)
if _, err := validateGeneration(generate); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_Generate_HasAnchors(t *testing.T) {
var err error
var generate kyverno.Generation
rawGenerate := []byte(`
{
"kind": "NetworkPolicy",
"name": "defaultnetworkpolicy",
"data": {
"spec": {
"(podSelector)": {},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [
{}
],
"egress": [
{}
]
}
}
}`)
err = json.Unmarshal(rawGenerate, &generate)
assert.NilError(t, err)
if _, err := validateGeneration(generate); err != nil {
assert.Assert(t, err != nil)
}
rawGenerate = []byte(`
{
"kind": "ConfigMap",
"name": "copied-cm",
"clone": {
"^(namespace)": "default",
"name": "game"
}
}`)
errNew := json.Unmarshal(rawGenerate, &generate)
assert.NilError(t, errNew)
err = json.Unmarshal(rawGenerate, &generate)
assert.NilError(t, err)
if _, err := validateGeneration(generate); err != nil {
assert.Assert(t, err != nil)
}
}
func Test_Validate_ErrorFormat(t *testing.T) {
rawPolicy := []byte(`
{
@ -1097,7 +519,7 @@ func Test_Validate_ErrorFormat(t *testing.T) {
err := json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = Validate(policy)
err = Validate(policy, nil)
assert.Assert(t, err != nil)
}

View file

@ -27,7 +27,7 @@ func (ws *WebhookServer) handlePolicyValidation(request *v1beta1.AdmissionReques
Message: fmt.Sprintf("Failed to unmarshal policy admission request err %v", err),
}}
}
if err := policyvalidate.Validate(*policy); err != nil {
if err := policyvalidate.Validate(*policy, ws.client); err != nil {
admissionResp = &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{