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:
parent
8480276f23
commit
b1063a95e1
17 changed files with 1332 additions and 793 deletions
108
pkg/auth/auth.go
Normal file
108
pkg/auth/auth.go
Normal 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
48
pkg/auth/auth_test.go
Normal 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()
|
||||
|
||||
// }
|
14
pkg/engine/variables/common.go
Normal file
14
pkg/engine/variables/common.go
Normal 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
50
pkg/policy/actions.go
Normal 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
|
||||
}
|
85
pkg/policy/common/common.go
Normal file
85
pkg/policy/common/common.go
Normal 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
|
||||
}
|
71
pkg/policy/generate/auth.go
Normal file
71
pkg/policy/generate/auth.go
Normal 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
|
||||
}
|
21
pkg/policy/generate/fake.go
Normal file
21
pkg/policy/generate/fake.go
Normal 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
|
||||
}
|
31
pkg/policy/generate/fake/auth.go
Normal file
31
pkg/policy/generate/fake/auth.go
Normal 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
|
||||
}
|
148
pkg/policy/generate/validate.go
Normal file
148
pkg/policy/generate/validate.go
Normal 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
|
||||
}
|
89
pkg/policy/generate/validate_test.go
Normal file
89
pkg/policy/generate/validate_test.go
Normal 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)
|
||||
}
|
||||
}
|
63
pkg/policy/mutate/validate.go
Normal file
63
pkg/policy/mutate/validate.go
Normal 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)
|
||||
}
|
151
pkg/policy/mutate/validate_test.go
Normal file
151
pkg/policy/mutate/validate_test.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
61
pkg/policy/validate/validate.go
Normal file
61
pkg/policy/validate/validate.go
Normal 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
|
||||
}
|
381
pkg/policy/validate/validate_test.go
Normal file
381
pkg/policy/validate/validate_test.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Add table
Reference in a new issue