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:
commit
2bfb5fffb3
10 changed files with 121 additions and 16 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
3
go.mod
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue