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

validation of policy against crd

This commit is contained in:
NoSkillGirl 2020-08-19 10:21:32 +05:30
parent a827f88dc7
commit 70b13d06dc
3 changed files with 186 additions and 2 deletions
go.mod
pkg/kyverno
common
validate

3
go.mod
View file

@ -10,6 +10,9 @@ require (
github.com/evanphx/json-patch/v5 v5.0.0 // indirect
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
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/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect
github.com/googleapis/gnostic v0.3.1

View file

@ -4,9 +4,17 @@ import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
yaml_v2 "gopkg.in/yaml.v2"
"io"
"io/ioutil"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"os"
"path/filepath"
"regexp"
@ -211,3 +219,159 @@ func MutatePolicy(policy *v1.ClusterPolicy, logger logr.Logger) (*v1.ClusterPoli
return &p, nil
}
func ValidatePolicyAgainstCrd(policy *v1.ClusterPolicy, path string) error {
log := log.Log
path = filepath.Clean(path)
fileDesc, err := os.Stat(path)
if err != nil {
log.Error(err, "failed to describe crd file")
return err
}
if fileDesc.IsDir() {
return errors.New("crd path should be a file")
}
bytes, err := ioutil.ReadFile(path)
if err != nil {
log.Error(err, "failed to crd read file")
return err
}
var crd unstructured.Unstructured
err = json.Unmarshal(bytes, &crd)
if err != nil {
return err
}
// crdDefinitionPrior represents CRDs version prior to 1.16
var crdDefinitionPrior struct {
Spec struct {
Names struct {
Kind string `json:"kind"`
} `json:"names"`
Validation struct {
OpenAPIV3Schema interface{} `json:"openAPIV3Schema"`
} `json:"validation"`
} `json:"spec"`
}
// crdDefinitionNew represents CRDs version 1.16+
var crdDefinitionNew struct {
Spec struct {
Names struct {
Kind string `json:"kind"`
} `json:"names"`
Versions []struct {
Schema struct {
OpenAPIV3Schema interface{} `json:"openAPIV3Schema"`
} `json:"schema"`
Storage bool `json:"storage"`
} `json:"versions"`
} `json:"spec"`
}
crdRaw, _ := json.Marshal(crd.Object)
_ = json.Unmarshal(crdRaw, &crdDefinitionPrior)
openV3schema := crdDefinitionPrior.Spec.Validation.OpenAPIV3Schema
crdName := crdDefinitionPrior.Spec.Names.Kind
if openV3schema == nil {
_ = json.Unmarshal(crdRaw, &crdDefinitionNew)
for _, crdVersion := range crdDefinitionNew.Spec.Versions {
if crdVersion.Storage {
openV3schema = crdVersion.Schema.OpenAPIV3Schema
crdName = crdDefinitionNew.Spec.Names.Kind
break
}
}
}
schemaRaw, _ := json.Marshal(openV3schema)
if len(schemaRaw) < 1 {
//log.Log.V(3).Info("could not parse crd schema", "name", crdName)
return err
}
schemaRaw, err = addingDefaultFieldsToSchema(schemaRaw)
if err != nil {
//log.Log.Error(err, "could not parse crd schema", "name", crdName)
return err
}
schema := new(spec.Schema)
_ = json.Unmarshal(schemaRaw, schema)
input := map[string]interface{}{}
// JSON data to validate
//inputJSON := `{"name": "Ivan","address-1": "sesame street"}`
//_ = json.Unmarshal([]byte(inputJSON), &input)
// strfmt.Default is the registry of recognized formats
err = validate.AgainstSchema(schema, policy, strfmt.Default)
if err != nil {
fmt.Printf("JSON does not validate against schema: %v", err)
} else {
fmt.Printf("OK")
}
//var schema yaml_v2.MapSlice
//_ = yaml_v2.Unmarshal(schemaRaw, &schema)
//
//parsedSchema, err := openapi_v2.NewSchema(schema, compiler.NewContext("schema", nil))
//if err != nil {
// //log.Log.Error(err, "could not parse crd schema", "name", crdName)
// return
//}
//var spec yaml_v2.MapSlice
//err := yaml_v2.Unmarshal([]byte(data.SwaggerDoc), &spec)
//if err != nil {
// return err
//}
//
//crdDoc, err := openapi_v2.NewDocument(spec, compiler.NewContext("$root", nil))
//if err != nil {
// return err
//}
//
//crdDoc
return nil
}
// addingDefaultFieldsToSchema will add any default missing fields like apiVersion, metadata
func addingDefaultFieldsToSchema(schemaRaw []byte) ([]byte, error) {
var schema struct {
Properties map[string]interface{} `json:"properties"`
}
_ = json.Unmarshal(schemaRaw, &schema)
if len(schema.Properties) < 1 {
return nil, errors.New("crd schema has no properties")
}
if schema.Properties["apiVersion"] == nil {
apiVersionDefRaw := `{"description":"APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources","type":"string"}`
apiVersionDef := make(map[string]interface{})
_ = json.Unmarshal([]byte(apiVersionDefRaw), &apiVersionDef)
schema.Properties["apiVersion"] = apiVersionDef
}
if schema.Properties["metadata"] == nil {
metadataDefRaw := `{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta","description":"Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"}`
metadataDef := make(map[string]interface{})
_ = json.Unmarshal([]byte(metadataDefRaw), &metadataDef)
schema.Properties["metadata"] = metadataDef
}
schemaWithDefaultFields, _ := json.Marshal(schema)
return schemaWithDefaultFields, nil
}

View file

@ -21,7 +21,7 @@ import (
)
func Command() *cobra.Command {
var outputType string
var outputType, crdPath string
cmd := &cobra.Command{
Use: "validate",
Short: "Validates kyverno policies",
@ -31,7 +31,7 @@ func Command() *cobra.Command {
if err != nil {
if !sanitizedError.IsErrorSanitized(err) {
log.Log.Error(err, "failed to sanitize")
err = fmt.Errorf("Internal error")
err = fmt.Errorf("internal error")
}
}
}()
@ -49,6 +49,22 @@ func Command() *cobra.Command {
invalidPolicyFound := false
for _, policy := range policies {
//if common.PolicyHasVariables(*policy) {
// invalidPolicyFound = true
// fmt.Printf("Policy %s is invalid.\n", policy.Name)
// log.Log.Error(errors.New("'validate' does not support policies with variables"), "Policy "+policy.Name+" is invalid")
// continue
//}
// if crd is passed, then validate policy against the crd
if crdPath != "" {
err := common.ValidatePolicyAgainstCrd(*policy, crdPath)
if err != nil {
log.Log.Error(err, "policy "+policy.Name+" is invalid")
os.Exit(1)
}
}
err := policy2.Validate(utils.MarshalPolicy(*policy), nil, true, openAPIController)
if err != nil {
fmt.Printf("Policy %s is invalid.\n", policy.Name)
@ -84,5 +100,6 @@ func Command() *cobra.Command {
},
}
cmd.Flags().StringVarP(&outputType, "output", "o", "", "Prints the mutated policy")
cmd.Flags().StringVarP(&crdPath, "crd", "c", "", "Path to resource files")
return cmd
}