1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

Merge pull request #1080 from NoSkillGirl/feature/crd_validation

Supporting CRD validation in CLI
This commit is contained in:
Pooja Singh 2020-09-01 08:51:02 +05:30 committed by GitHub
commit 2bfb5fffb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 121 additions and 16 deletions

View file

@ -145,7 +145,7 @@ func main() {
// KYVERNO CRD INFORMER // KYVERNO CRD INFORMER
// watches CRD resources: // watches CRD resources:
// - Policy // - Policy
// - PolicyVolation // - PolicyViolation
pInformer := kyvernoinformer.NewSharedInformerFactoryWithOptions(pclient, resyncPeriod) pInformer := kyvernoinformer.NewSharedInformerFactoryWithOptions(pclient, resyncPeriod)
// Configuration Data // Configuration Data
@ -332,7 +332,7 @@ func main() {
go auditHandler.Run(10, stopCh) go auditHandler.Run(10, stopCh)
openAPISync.Run(1, stopCh) openAPISync.Run(1, stopCh)
// verifys if the admission control is enabled and active // verifies if the admission control is enabled and active
// resync: 60 seconds // resync: 60 seconds
// deadline: 60 seconds (send request) // deadline: 60 seconds (send request)
// max deadline: deadline*3 (set the deployment annotation as false) // max deadline: deadline*3 (set the deployment annotation as false)

View file

@ -61,6 +61,12 @@ Example:
kyverno validate /path/to/policy1.yaml /path/to/policy2.yaml /path/to/folderFullOfPolicies -o yaml kyverno validate /path/to/policy1.yaml /path/to/policy2.yaml /path/to/folderFullOfPolicies -o yaml
``` ```
Policy can also be validated with CRDs. Use -c flag to pass the CRD, can pass multiple CRD files or even an entire folder containin CRDs.
Example:
```
kyverno validate /path/to/policy1.yaml -c /path/to/crd.yaml -c /path/to/folderFullOfCRDs
```
#### Apply #### Apply
Applies policies on resources, and supports applying multiple policies on multiple resources in a single command. Applies policies on resources, and supports applying multiple policies on multiple resources in a single command.

3
go.mod
View file

@ -10,6 +10,9 @@ require (
github.com/evanphx/json-patch/v5 v5.0.0 // indirect github.com/evanphx/json-patch/v5 v5.0.0 // indirect
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
github.com/go-logr/logr v0.1.0 github.com/go-logr/logr v0.1.0
github.com/go-openapi/spec v0.19.5
github.com/go-openapi/strfmt v0.19.5
github.com/go-openapi/validate v0.19.8
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect
github.com/googleapis/gnostic v0.3.1 github.com/googleapis/gnostic v0.3.1

View file

@ -11,7 +11,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
//CanIOptions provides utility ti check if user has authorization for the given operation //CanIOptions provides utility to check if user has authorization for the given operation
type CanIOptions struct { type CanIOptions struct {
namespace string namespace string
verb string verb string
@ -20,7 +20,7 @@ type CanIOptions struct {
log logr.Logger log logr.Logger
} }
//NewCanI returns a new instance of operation access controler evaluator //NewCanI returns a new instance of operation access controller evaluator
func NewCanI(client *client.Client, kind, namespace, verb string, log logr.Logger) *CanIOptions { func NewCanI(client *client.Client, kind, namespace, verb string, log logr.Logger) *CanIOptions {
o := CanIOptions{ o := CanIOptions{
client: client, client: client,
@ -38,7 +38,7 @@ func NewCanI(client *client.Client, kind, namespace, verb string, log logr.Logge
// - operation is a combination of namespace, kind, verb // - operation is a combination of namespace, kind, verb
// - can only evaluate a single verb // - can only evaluate a single verb
// - group version resource is determined from the kind using the discovery client REST mapper // - 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 // - If disallowed, the reason and evaluationError is available in the logs
// - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions // - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions
func (o *CanIOptions) RunAccessCheck() (bool, error) { func (o *CanIOptions) RunAccessCheck() (bool, error) {
// get GroupVersionResource from RESTMapper // get GroupVersionResource from RESTMapper

View file

@ -95,7 +95,7 @@ func (t *LastReqTime) Run(pLister kyvernolister.ClusterPolicyLister, eventGen ev
timeDiff := time.Since(t.Time()) timeDiff := time.Since(t.Time())
if timeDiff > maxDeadline { if timeDiff > maxDeadline {
err := fmt.Errorf("Admission control configuration error") err := fmt.Errorf("admission control configuration error")
logger.Error(err, "webhook check failed", "deadline", maxDeadline) logger.Error(err, "webhook check failed", "deadline", maxDeadline)
if err := statuscontrol.FailedStatus(); err != nil { if err := statuscontrol.FailedStatus(); err != nil {
logger.Error(err, "error setting webhook check status to failed") logger.Error(err, "error setting webhook check status to failed")

View file

@ -12,6 +12,9 @@ import (
"regexp" "regexp"
"strings" "strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
yaml_v2 "sigs.k8s.io/yaml"
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch"
"github.com/go-logr/logr" "github.com/go-logr/logr"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1" v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@ -211,3 +214,80 @@ func MutatePolicy(policy *v1.ClusterPolicy, logger logr.Logger) (*v1.ClusterPoli
return &p, nil return &p, nil
} }
// GetCRDs - Extracting the crds from multiple YAML
func GetCRDs(paths []string) (unstructuredCrds []*unstructured.Unstructured, err error) {
log := log.Log
unstructuredCrds = make([]*unstructured.Unstructured, 0)
for _, path := range paths {
path = filepath.Clean(path)
fileDesc, err := os.Stat(path)
if err != nil {
log.Error(err, "failed to describe file")
return nil, err
}
if fileDesc.IsDir() {
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, sanitizedError.NewWithError(fmt.Sprintf("failed to parse %v", path), err)
}
listOfFiles := make([]string, 0)
for _, file := range files {
listOfFiles = append(listOfFiles, filepath.Join(path, file.Name()))
}
policiesFromDir, err := GetCRDs(listOfFiles)
if err != nil {
log.Error(err, fmt.Sprintf("failed to extract crds from %v", listOfFiles))
return nil, sanitizedError.NewWithError(("failed to extract crds"), err)
}
unstructuredCrds = append(unstructuredCrds, policiesFromDir...)
} else {
getCRDs, err := GetCRD(path)
if err != nil {
fmt.Printf("failed to extract crds: %s\n", err)
os.Exit(2)
}
unstructuredCrds = append(unstructuredCrds, getCRDs...)
}
}
return unstructuredCrds, nil
}
// GetCRD - Extracts crds from a YAML
func GetCRD(path string) (unstructuredCrds []*unstructured.Unstructured, err error) {
log := log.Log
path = filepath.Clean(path)
unstructuredCrds = make([]*unstructured.Unstructured, 0)
yamlbytes, err := ioutil.ReadFile(path)
if err != nil {
log.Error(err, "failed to read file", "file", path)
return nil, err
}
buf := bytes.NewBuffer(yamlbytes)
reader := yaml.NewYAMLReader(bufio.NewReader(buf))
for {
// Read one YAML document at a time, until io.EOF is returned
b, err := reader.Read()
if err == io.EOF || len(b) == 0 {
break
} else if err != nil {
log.Error(err, "unable to read yaml")
}
var u unstructured.Unstructured
err = yaml_v2.Unmarshal(b, &u)
if err != nil {
log.Error(err, "failed to convert file into unstructured object", "file", path)
return nil, err
}
unstructuredCrds = append(unstructuredCrds, &u)
}
return unstructuredCrds, nil
}

View file

@ -22,16 +22,19 @@ import (
func Command() *cobra.Command { func Command() *cobra.Command {
var outputType string var outputType string
var crdPaths []string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "validate", Use: "validate",
Short: "Validates kyverno policies", Short: "Validates kyverno policies",
Example: "kyverno validate /path/to/policy.yaml /path/to/folderOfPolicies", Example: "kyverno validate /path/to/policy.yaml /path/to/folderOfPolicies",
RunE: func(cmd *cobra.Command, policyPaths []string) (err error) { RunE: func(cmd *cobra.Command, policyPaths []string) (err error) {
log := log.Log
defer func() { defer func() {
if err != nil { if err != nil {
if !sanitizedError.IsErrorSanitized(err) { if !sanitizedError.IsErrorSanitized(err) {
log.Log.Error(err, "failed to sanitize") log.Error(err, "failed to sanitize")
err = fmt.Errorf("Internal error") err = fmt.Errorf("internal error")
} }
} }
}() }()
@ -47,17 +50,30 @@ func Command() *cobra.Command {
return err return err
} }
// if CRD's are passed, add these to OpenAPIController
if len(crdPaths) > 0 {
crds, err := common.GetCRDs(crdPaths)
if err != nil {
log.Error(err, "crd is invalid", "file", crdPaths)
os.Exit(1)
}
for _, crd := range crds {
openAPIController.ParseCRD(*crd)
}
}
invalidPolicyFound := false invalidPolicyFound := false
for _, policy := range policies { for _, policy := range policies {
err := policy2.Validate(utils.MarshalPolicy(*policy), nil, true, openAPIController) err := policy2.Validate(utils.MarshalPolicy(*policy), nil, true, openAPIController)
if err != nil { if err != nil {
fmt.Printf("Policy %s is invalid.\n", policy.Name) fmt.Printf("Policy %s is invalid.\n", policy.Name)
log.Log.Error(err, "policy "+policy.Name+" is invalid") log.Error(err, "policy "+policy.Name+" is invalid")
invalidPolicyFound = true invalidPolicyFound = true
} else { } else {
fmt.Printf("Policy %s is valid.\n\n", policy.Name) fmt.Printf("Policy %s is valid.\n\n", policy.Name)
if outputType != "" { if outputType != "" {
logger := log.Log.WithName("validate") logger := log.WithName("validate")
p, err := common.MutatePolicy(policy, logger) p, err := common.MutatePolicy(policy, logger)
if err != nil { if err != nil {
if !sanitizedError.IsErrorSanitized(err) { if !sanitizedError.IsErrorSanitized(err) {
@ -74,7 +90,6 @@ func Command() *cobra.Command {
} }
} }
} }
fmt.Println("-----------------------------------------------------------------------")
} }
if invalidPolicyFound == true { if invalidPolicyFound == true {
@ -84,5 +99,6 @@ func Command() *cobra.Command {
}, },
} }
cmd.Flags().StringVarP(&outputType, "output", "o", "", "Prints the mutated policy") cmd.Flags().StringVarP(&outputType, "output", "o", "", "Prints the mutated policy")
cmd.Flags().StringArrayVarP(&crdPaths, "crd", "c", []string{}, "Path to CRD files")
return cmd return cmd
} }

View file

@ -102,7 +102,7 @@ func (c *crdSync) sync() {
c.controller.deleteCRDFromPreviousSync() c.controller.deleteCRDFromPreviousSync()
for _, crd := range crds.Items { for _, crd := range crds.Items {
c.controller.parseCRD(crd) c.controller.ParseCRD(crd)
} }
} }
@ -115,7 +115,7 @@ func (o *Controller) deleteCRDFromPreviousSync() {
o.crdList = make([]string, 0) o.crdList = make([]string, 0)
} }
func (o *Controller) parseCRD(crd unstructured.Unstructured) { func (o *Controller) ParseCRD(crd unstructured.Unstructured) {
var err error var err error
crdRaw, _ := json.Marshal(crd.Object) crdRaw, _ := json.Marshal(crd.Object)

View file

@ -69,7 +69,7 @@ func CRDInstalled(discovery client.IDiscovery, log logr.Logger) bool {
logger := log.WithName("CRDInstalled") logger := log.WithName("CRDInstalled")
check := func(kind string) bool { check := func(kind string) bool {
gvr := discovery.GetGVRFromKind(kind) gvr := discovery.GetGVRFromKind(kind)
if reflect.DeepEqual(gvr, (schema.GroupVersionResource{})) { if reflect.DeepEqual(gvr, schema.GroupVersionResource{}) {
logger.Info("CRD not installed", "kind", kind) logger.Info("CRD not installed", "kind", kind)
return false return false
} }

View file

@ -288,7 +288,7 @@ func (ws *WebhookServer) ResourceMutation(request *v1beta1.AdmissionRequest) *v1
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request, ws.configHandler) roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request, ws.configHandler)
if err != nil { if err != nil {
// TODO(shuting): continue apply policy if error getting roleRef? // TODO(shuting): continue apply policy if error getting roleRef?
logger.Error(err, "failed to get RBAC infromation for request") logger.Error(err, "failed to get RBAC information for request")
} }
} }
@ -508,7 +508,7 @@ func (ws *WebhookServer) RunAsync(stopCh <-chan struct{}) {
}(ws) }(ws)
logger.Info("starting") logger.Info("starting")
// verifys if the admission control is enabled and active // verifies if the admission control is enabled and active
// resync: 60 seconds // resync: 60 seconds
// deadline: 60 seconds (send request) // deadline: 60 seconds (send request)
// max deadline: deadline*3 (set the deployment annotation as false) // max deadline: deadline*3 (set the deployment annotation as false)