From 79999c49482796cae14a624a93095b6788953e38 Mon Sep 17 00:00:00 2001 From: shravan <shravan.ss@zopsmart.com> Date: Fri, 17 Jan 2020 00:05:15 +0530 Subject: [PATCH] extended cli --- Makefile | 1 - cmd/cli/kyverno/main.go | 9 + cmd/cli/main.go | 25 -- definitions/install.yaml | 4 +- pkg/config/config.go | 2 +- pkg/engine/mutate/overlay.go | 4 +- pkg/engine/mutation.go | 4 +- pkg/engine/utils/utils.go | 3 +- pkg/kyverno/apply/apply.go | 251 ---------------- pkg/kyverno/apply/command.go | 267 ++++++++++++++++++ pkg/kyverno/apply/util.go | 105 ------- pkg/kyverno/cmd.go | 27 -- pkg/kyverno/main.go | 48 ++++ pkg/kyverno/validate/command.go | 64 +++++ pkg/kyverno/version/command.go | 21 ++ pkg/kyverno/version/version.go | 29 -- pkg/webhookconfig/common.go | 4 - pkg/webhooks/mutation.go | 2 - .../disallow_default_namespace.yaml | 1 - 19 files changed, 417 insertions(+), 454 deletions(-) create mode 100644 cmd/cli/kyverno/main.go delete mode 100644 cmd/cli/main.go delete mode 100644 pkg/kyverno/apply/apply.go create mode 100644 pkg/kyverno/apply/command.go delete mode 100644 pkg/kyverno/apply/util.go delete mode 100644 pkg/kyverno/cmd.go create mode 100644 pkg/kyverno/main.go create mode 100644 pkg/kyverno/validate/command.go create mode 100644 pkg/kyverno/version/command.go delete mode 100644 pkg/kyverno/version/version.go diff --git a/Makefile b/Makefile index 506f4fb2a1..732a1facce 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,6 @@ REGISTRY=index.docker.io REPO=$(REGISTRY)/nirmata/kyverno IMAGE_TAG=$(GIT_VERSION) GOOS ?= $(shell go env GOOS) -PACKAGE ?=github.com/nirmata/kyverno LD_FLAGS="-s -w -X $(PACKAGE)/pkg/version.BuildVersion=$(GIT_VERSION) -X $(PACKAGE)/pkg/version.BuildHash=$(GIT_HASH) -X $(PACKAGE)/pkg/version.BuildTime=$(TIMESTAMP)" ################################## diff --git a/cmd/cli/kyverno/main.go b/cmd/cli/kyverno/main.go new file mode 100644 index 0000000000..a5adfa5a9e --- /dev/null +++ b/cmd/cli/kyverno/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/nirmata/kyverno/pkg/kyverno" +) + +func main() { + kyverno.CLI() +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go deleted file mode 100644 index 33bc524e9c..0000000000 --- a/cmd/cli/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - goflag "flag" - "fmt" - "os" - - "github.com/nirmata/kyverno/pkg/config" - kyverno "github.com/nirmata/kyverno/pkg/kyverno" - flag "github.com/spf13/pflag" -) - -func main() { - cmd := kyverno.NewDefaultKyvernoCommand() - if err := cmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } -} - -func init() { - flag.CommandLine.AddGoFlagSet(goflag.CommandLine) - config.LogDefaultFlags() - flag.Parse() -} diff --git a/definitions/install.yaml b/definitions/install.yaml index 8e5b660096..fd7fc1a58b 100644 --- a/definitions/install.yaml +++ b/definitions/install.yaml @@ -520,10 +520,10 @@ spec: serviceAccountName: kyverno-service-account initContainers: - name: kyverno-pre - image: nirmata/kyvernopre:v1.1.1 + image: nirmata/kyvernopre:v1.1.0 containers: - name: kyverno - image: nirmata/kyverno:v1.1.1 + image: nirmata/kyverno:v1.1.0 args: - "--filterK8Resources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]" # customize webhook timout diff --git a/pkg/config/config.go b/pkg/config/config.go index f553fa8122..c975a1cea7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -83,6 +83,6 @@ func CreateClientConfig(kubeconfig string) (*rest.Config, error) { glog.Info("Using in-cluster configuration") return rest.InClusterConfig() } - glog.Infof("Using configuration from '%s'", kubeconfig) + glog.V(4).Infof("Using configuration from '%s'", kubeconfig) return clientcmd.BuildConfigFromFlags("", kubeconfig) } diff --git a/pkg/engine/mutate/overlay.go b/pkg/engine/mutate/overlay.go index 59d1f50865..51b0640b25 100644 --- a/pkg/engine/mutate/overlay.go +++ b/pkg/engine/mutate/overlay.go @@ -387,11 +387,11 @@ func preparePath(path string) string { } annPath := "/metadata/annotations/" + idx := strings.Index(path, annPath) // escape slash in annotation patch if strings.Contains(path, annPath) { - idx := strings.Index(path, annPath) p := path[idx+len(annPath):] - path = path[:idx+len(annPath)] + strings.ReplaceAll(p, "/", "~1") + path = annPath + strings.ReplaceAll(p, "/", "~1") } return path } diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index a8c36b7262..ee0cc32295 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -105,7 +105,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { continue } - glog.Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName()) + glog.V(4).Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName()) } resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse) @@ -156,7 +156,7 @@ var podTemplateRule = kyverno.Rule{ "template": map[string]interface{}{ "metadata": map[string]interface{}{ "annotations": map[string]interface{}{ - "+(pod-policies.kyverno.io/autogen-applied)": "true", + "pod-policies.kyverno.io/autogen-applied": "true", }, }, }, diff --git a/pkg/engine/utils/utils.go b/pkg/engine/utils/utils.go index 8dbb1bec26..de103fe930 100644 --- a/pkg/engine/utils/utils.go +++ b/pkg/engine/utils/utils.go @@ -25,12 +25,11 @@ func (ri RuleType) String() string { } // ApplyPatches patches given resource with given patches and returns patched document -// return origin resource if any error occurs func ApplyPatches(resource []byte, patches [][]byte) ([]byte, error) { joinedPatches := JoinPatches(patches) patch, err := jsonpatch.DecodePatch(joinedPatches) if err != nil { - return resource, err + return nil, err } patchedDocument, err := patch.Apply(resource) diff --git a/pkg/kyverno/apply/apply.go b/pkg/kyverno/apply/apply.go deleted file mode 100644 index bbdbdb6e71..0000000000 --- a/pkg/kyverno/apply/apply.go +++ /dev/null @@ -1,251 +0,0 @@ -package apply - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - - "github.com/golang/glog" - kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" - "github.com/nirmata/kyverno/pkg/engine" - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - yaml "k8s.io/apimachinery/pkg/util/yaml" - memory "k8s.io/client-go/discovery/cached/memory" - dynamic "k8s.io/client-go/dynamic" - kubernetes "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/restmapper" -) - -const ( - applyExample = ` # Apply a policy to the resource. - kyverno apply @policy.yaml @resource.yaml - kyverno apply @policy.yaml @resourceDir/ - kyverno apply @policy.yaml @resource.yaml --kubeconfig=$PATH_TO_KUBECONFIG_FILE` - - defaultYamlSeparator = "---" -) - -// NewCmdApply returns the apply command for kyverno -func NewCmdApply(in io.Reader, out, errout io.Writer) *cobra.Command { - var kubeconfig string - cmd := &cobra.Command{ - Use: "apply", - Short: "Apply policy on the resource(s)", - Example: applyExample, - Run: func(cmd *cobra.Command, args []string) { - policy, resources := complete(kubeconfig, args) - output := applyPolicy(policy, resources) - fmt.Printf("%v\n", output) - }, - } - - cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "path to kubeconfig file") - return cmd -} - -func complete(kubeconfig string, args []string) (*kyverno.ClusterPolicy, []*resourceInfo) { - policyDir, resourceDir, err := validateDir(args) - if err != nil { - glog.Errorf("Failed to parse file path, err: %v\n", err) - os.Exit(1) - } - - // extract policy - policy, err := extractPolicy(policyDir) - if err != nil { - glog.Errorf("Failed to extract policy: %v\n", err) - os.Exit(1) - } - - // extract rawResource - resources, err := extractResource(resourceDir, kubeconfig) - if err != nil { - glog.Errorf("Failed to parse resource: %v", err) - os.Exit(1) - } - - return policy, resources -} - -func applyPolicy(policy *kyverno.ClusterPolicy, resources []*resourceInfo) (output string) { - for _, resource := range resources { - patchedDocument, err := applyPolicyOnRaw(policy, resource.rawResource, resource.gvk) - if err != nil { - glog.Errorf("Error applying policy on resource %s, err: %v\n", resource.gvk.Kind, err) - continue - } - - out, err := prettyPrint(patchedDocument) - if err != nil { - glog.Errorf("JSON parse error: %v\n", err) - continue - } - - output = output + fmt.Sprintf("---\n%s", string(out)) - } - return -} - -func applyPolicyOnRaw(policy *kyverno.ClusterPolicy, rawResource []byte, gvk *metav1.GroupVersionKind) ([]byte, error) { - patchedResource := rawResource - var err error - - rname := engine.ParseNameFromObject(rawResource) - rns := engine.ParseNamespaceFromObject(rawResource) - resource, err := ConvertToUnstructured(rawResource) - if err != nil { - return nil, err - } - //TODO check if the kind information is present resource - // Process Mutation - engineResponse := engine.Mutate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) - if !engineResponse.IsSuccesful() { - glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns) - for _, r := range engineResponse.PolicyResponse.Rules { - glog.Warning(r.Message) - } - } else if len(engineResponse.PolicyResponse.Rules) > 0 { - glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns) - - // Process Validation - engineResponse := engine.Validate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) - - if !engineResponse.IsSuccesful() { - glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns) - for _, r := range engineResponse.PolicyResponse.Rules { - glog.Warning(r.Message) - } - return patchedResource, fmt.Errorf("policy %s on resource %s/%s not satisfied", policy.Name, rname, rns) - } else if len(engineResponse.PolicyResponse.Rules) > 0 { - glog.Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns) - } - } - return patchedResource, nil -} - -func extractPolicy(fileDir string) (*kyverno.ClusterPolicy, error) { - policy := &kyverno.ClusterPolicy{} - - file, err := loadFile(fileDir) - if err != nil { - return nil, fmt.Errorf("failed to load file: %v", err) - } - - policyBytes, err := yaml.ToJSON(file) - if err != nil { - return nil, err - } - - if err := json.Unmarshal(policyBytes, policy); err != nil { - return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err) - } - - if policy.TypeMeta.Kind != "ClusterPolicy" { - return nil, fmt.Errorf("failed to parse policy") - } - - return policy, nil -} - -type resourceInfo struct { - rawResource []byte - gvk *metav1.GroupVersionKind -} - -func extractResource(fileDir, kubeconfig string) ([]*resourceInfo, error) { - var files []string - var resources []*resourceInfo - - // check if applied on multiple resources - isDir, err := isDir(fileDir) - if err != nil { - return nil, err - } - - if isDir { - files, err = scanDir(fileDir) - if err != nil { - return nil, err - } - } else { - files = []string{fileDir} - } - - for _, dir := range files { - data, err := loadFile(dir) - if err != nil { - glog.Warningf("Error while loading file: %v\n", err) - continue - } - - dd := bytes.Split(data, []byte(defaultYamlSeparator)) - - for _, d := range dd { - decode := scheme.Codecs.UniversalDeserializer().Decode - obj, gvk, err := decode([]byte(d), nil, nil) - if err != nil { - glog.Warningf("Error while decoding YAML object, err: %s\n", err) - continue - } - - actualObj, err := convertToActualObject(kubeconfig, gvk, obj) - if err != nil { - glog.V(3).Infof("Failed to convert resource %s to actual k8s object: %v\n", gvk.Kind, err) - glog.V(3).Infof("Apply policy on raw resource.\n") - } - - raw, err := json.Marshal(actualObj) - if err != nil { - glog.Warningf("Error while marshalling manifest, err: %v\n", err) - continue - } - - gvkInfo := &metav1.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind} - resources = append(resources, &resourceInfo{rawResource: raw, gvk: gvkInfo}) - } - } - - return resources, err -} - -func convertToActualObject(kubeconfig string, gvk *schema.GroupVersionKind, obj runtime.Object) (interface{}, error) { - clientConfig, err := createClientConfig(kubeconfig) - if err != nil { - return obj, err - } - - dynamicClient, err := dynamic.NewForConfig(clientConfig) - if err != nil { - return obj, err - } - - kclient, err := kubernetes.NewForConfig(clientConfig) - if err != nil { - return obj, err - } - - asUnstructured := &unstructured.Unstructured{} - if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil { - return obj, err - } - - mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(kclient.Discovery())) - mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) - if err != nil { - return obj, err - } - - actualObj, err := dynamicClient.Resource(mapping.Resource).Namespace("default").Create(asUnstructured, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}) - if err != nil { - return obj, err - } - - return actualObj, nil -} diff --git a/pkg/kyverno/apply/command.go b/pkg/kyverno/apply/command.go new file mode 100644 index 0000000000..bedc2e2ee9 --- /dev/null +++ b/pkg/kyverno/apply/command.go @@ -0,0 +1,267 @@ +package apply + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/nirmata/kyverno/pkg/config" + "k8s.io/client-go/discovery" + + "k8s.io/apimachinery/pkg/util/yaml" + + "github.com/nirmata/kyverno/pkg/engine" + + engineutils "github.com/nirmata/kyverno/pkg/engine/utils" + + "k8s.io/apimachinery/pkg/runtime" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + "github.com/spf13/cobra" + yamlv2 "gopkg.in/yaml.v2" + "k8s.io/client-go/kubernetes/scheme" +) + +func Command() *cobra.Command { + var resourcePath, kubeConfig string + + cmd := &cobra.Command{ + Use: "apply", + Short: "Applies policies on resources", + Example: fmt.Sprintf("To apply on a resource:\nkyverno apply /path/to/policy1 /path/to/policy2 --resource=/path/to/resource\n\nTo apply on a cluster\nkyverno apply /path/to/policy1 /path/to/policy2 --kubeConfig=/path/to/kubeConfig"), + RunE: func(cmd *cobra.Command, policyPaths []string) error { + if resourcePath == "" && kubeConfig == "" { + fmt.Println("Specify path to resource file or kube config") + } + + var policies []*v1.ClusterPolicy + for _, policyPath := range policyPaths { + policy, err := getPolicy(policyPath) + if err != nil { + return err + } + + policies = append(policies, policy) + } + + resources, err := getResources(policies, kubeConfig, resourcePath) + if err != nil { + return err + } + + for i, policy := range policies { + for j, resource := range resources { + if !(j == 0 && i == 0) { + fmt.Printf("\n\n=======================================================================\n") + } + + err = applyPolicyOnResource(policy, resource) + if err != nil { + return err + } + } + } + + return nil + }, + } + + cmd.Flags().StringVar(&resourcePath, "resource", "", "path to resource file") + cmd.Flags().StringVar(&kubeConfig, "kubeConfig", "", "path to .kube/config file") + return cmd +} + +func getResources(policies []*v1.ClusterPolicy, kubeConfig, resourcePath string) ([]*unstructured.Unstructured, error) { + var resources []*unstructured.Unstructured + var err error + + if kubeConfig != "" { + var resourceTypesMap = make(map[string]bool) + var resourceTypes []string + for _, policy := range policies { + for _, rule := range policy.Spec.Rules { + for _, kind := range rule.MatchResources.Kinds { + resourceTypesMap[kind] = true + } + } + } + + for kind := range resourceTypesMap { + resourceTypes = append(resourceTypes, kind) + } + + resources, err = getResourcesOfTypeFromCluster(resourceTypes, kubeConfig) + if err != nil { + return nil, err + } + } + + if resourcePath != "" { + resource, err := getResource(resourcePath) + if err != nil { + return nil, err + } + + resources = append(resources, resource) + } + + return resources, nil +} + +func getResourcesOfTypeFromCluster(resourceTypes []string, kubeConfig string) ([]*unstructured.Unstructured, error) { + var resources []*unstructured.Unstructured + + clientConfig, err := config.CreateClientConfig(kubeConfig) + if err != nil { + return nil, err + } + + dClient, err := discovery.NewDiscoveryClientForConfig(clientConfig) + if err != nil { + return nil, err + } + + //var kindToListApi = map[string]string{ + // "pod": "/api/v1/pods", + // "services": "/api/v1/services", + //} + + for _, kind := range resourceTypes { + listObjectRaw, err := dClient.RESTClient().Get().RequestURI("/api/v1/" + strings.ToLower(kind) + "s").Do().Raw() + if err != nil { + log.Println(2, err) + } + + listObject, err := engineutils.ConvertToUnstructured(listObjectRaw) + if err != nil { + return nil, err + } + + resourceList, err := listObject.ToList() + if err != nil { + return nil, err + } + + version := resourceList.GetAPIVersion() + for _, resource := range resourceList.Items { + resource.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "", + Version: version, + Kind: kind, + }) + resources = append(resources, resource.DeepCopy()) + } + } + + return resources, nil +} + +func getPolicy(path string) (*v1.ClusterPolicy, error) { + policy := &v1.ClusterPolicy{} + + file, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to load file: %v", err) + } + + policyBytes, err := yaml.ToJSON(file) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(policyBytes, policy); err != nil { + return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err) + } + + if policy.TypeMeta.Kind != "ClusterPolicy" { + return nil, fmt.Errorf("failed to parse policy") + } + + return policy, nil +} + +func getResource(path string) (*unstructured.Unstructured, error) { + + resourceYaml, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + decode := scheme.Codecs.UniversalDeserializer().Decode + resourceObject, metaData, err := decode(resourceYaml, nil, nil) + if err != nil { + return nil, err + } + + resourceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&resourceObject) + if err != nil { + return nil, err + } + + resourceJSON, err := json.Marshal(resourceUnstructured) + if err != nil { + return nil, err + } + + resource, err := engineutils.ConvertToUnstructured(resourceJSON) + if err != nil { + return nil, err + } + + resource.SetGroupVersionKind(*metaData) + + if resource.GetNamespace() == "" { + resource.SetNamespace("default") + } + + return resource, nil +} + +func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured) error { + + fmt.Printf("\n\nApplying Policy %s on Resource %s/%s/%s/%s", policy.Name, resource.GetNamespace(), resource.GetKind(), resource.GetName(), resource.GetUID()) + + mutateResponse := engine.Mutate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) + if !mutateResponse.IsSuccesful() { + fmt.Printf("\n\nMutation:") + fmt.Printf("\nFailed to apply mutation") + for i, r := range mutateResponse.PolicyResponse.Rules { + fmt.Printf("\n%d. %s", i+1, r.Message) + } + fmt.Printf("\n\n") + } else { + if len(mutateResponse.PolicyResponse.Rules) > 0 { + fmt.Printf("\n\nMutation:") + fmt.Printf("\nMutation has been applied succesfully") + yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object) + if err != nil { + return err + } + + fmt.Printf("\n\n" + strings.ReplaceAll(string(yamlEncodedResource), "\n", "\n")) + } + } + + validateResponse := engine.Validate(engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource}) + if !validateResponse.IsSuccesful() { + fmt.Printf("\n\nValidation:") + fmt.Printf("\nResource is invalid") + for i, r := range validateResponse.PolicyResponse.Rules { + fmt.Printf("\n%d. %s", i+1, r.Message) + } + fmt.Printf("\n\n") + } else { + if len(validateResponse.PolicyResponse.Rules) > 0 { + fmt.Printf("\n\nValidation:") + fmt.Printf("\nResource is valid") + } + } + + return nil +} diff --git a/pkg/kyverno/apply/util.go b/pkg/kyverno/apply/util.go deleted file mode 100644 index 26e0390300..0000000000 --- a/pkg/kyverno/apply/util.go +++ /dev/null @@ -1,105 +0,0 @@ -package apply - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/golang/glog" - yamlv2 "gopkg.in/yaml.v2" - unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - rest "k8s.io/client-go/rest" - clientcmd "k8s.io/client-go/tools/clientcmd" -) - -func createClientConfig(kubeconfig string) (*rest.Config, error) { - if kubeconfig == "" { - defaultKC := defaultKubeconfigPath() - if _, err := os.Stat(defaultKC); err == nil { - kubeconfig = defaultKC - } - } - - return clientcmd.BuildConfigFromFlags("", kubeconfig) -} - -func defaultKubeconfigPath() string { - home, err := os.UserHomeDir() - if err != nil { - glog.Warningf("Warning: failed to get home dir: %v\n", err) - return "" - } - - return filepath.Join(home, ".kube", "config") -} - -func loadFile(fileDir string) ([]byte, error) { - if _, err := os.Stat(fileDir); os.IsNotExist(err) { - return nil, err - } - - return ioutil.ReadFile(fileDir) -} - -func validateDir(args []string) (policyDir, resourceDir string, err error) { - if len(args) != 2 { - return "", "", fmt.Errorf("missing policy and/or resource manifest") - } - - if strings.HasPrefix(args[0], "@") { - policyDir = args[0][1:] - } - - if strings.HasPrefix(args[1], "@") { - resourceDir = args[1][1:] - } - return -} - -func prettyPrint(data []byte) ([]byte, error) { - out := make(map[interface{}]interface{}) - if err := yamlv2.Unmarshal(data, &out); err != nil { - return nil, err - } - - return yamlv2.Marshal(&out) -} - -func isDir(dir string) (bool, error) { - fi, err := os.Stat(dir) - if err != nil { - return false, err - } - - return fi.IsDir(), nil -} - -func scanDir(dir string) ([]string, error) { - var res []string - - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return fmt.Errorf("prevent panic by handling failure accessing a path %q: %v", dir, err) - } - - res = append(res, path) - return nil - }) - - if err != nil { - return nil, fmt.Errorf("error walking the path %q: %v", dir, err) - } - - return res[1:], nil -} -func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) { - resource := &unstructured.Unstructured{} - err := resource.UnmarshalJSON(data) - if err != nil { - glog.V(4).Infof("failed to unmarshall resource: %v", err) - return nil, err - } - return resource, nil -} diff --git a/pkg/kyverno/cmd.go b/pkg/kyverno/cmd.go deleted file mode 100644 index ac31e2a24f..0000000000 --- a/pkg/kyverno/cmd.go +++ /dev/null @@ -1,27 +0,0 @@ -package cmd - -import ( - "io" - "os" - - "github.com/nirmata/kyverno/pkg/kyverno/apply" - "github.com/nirmata/kyverno/pkg/kyverno/version" - "github.com/spf13/cobra" -) - -// NewDefaultKyvernoCommand ... -func NewDefaultKyvernoCommand() *cobra.Command { - return NewKyvernoCommand(os.Stdin, os.Stdout, os.Stderr) -} - -// NewKyvernoCommand returns the new kynerno command -func NewKyvernoCommand(in io.Reader, out, errout io.Writer) *cobra.Command { - cmds := &cobra.Command{ - Use: "kyverno", - Short: "kyverno manages native policies of Kubernetes", - } - - cmds.AddCommand(apply.NewCmdApply(in, out, errout)) - cmds.AddCommand(version.NewCmdVersion(out)) - return cmds -} diff --git a/pkg/kyverno/main.go b/pkg/kyverno/main.go new file mode 100644 index 0000000000..43b1fd05a5 --- /dev/null +++ b/pkg/kyverno/main.go @@ -0,0 +1,48 @@ +package kyverno + +import ( + "flag" + "os" + + "github.com/nirmata/kyverno/pkg/kyverno/validate" + + "github.com/nirmata/kyverno/pkg/kyverno/apply" + + "github.com/nirmata/kyverno/pkg/kyverno/version" + + "github.com/spf13/cobra" +) + +func CLI() { + cli := &cobra.Command{ + Use: "kyverno", + Short: "kyverno manages native policies of Kubernetes", + } + + configureGlog(cli) + + commands := []*cobra.Command{ + version.Command(), + apply.Command(), + validate.Command(), + } + + cli.AddCommand(commands...) + + if err := cli.Execute(); err != nil { + os.Exit(1) + } +} + +func configureGlog(cli *cobra.Command) { + flag.Parse() + flag.Set("logtostderr", "true") + + cli.PersistentFlags().AddGoFlagSet(flag.CommandLine) + cli.PersistentFlags().MarkHidden("alsologtostderr") + cli.PersistentFlags().MarkHidden("logtostderr") + cli.PersistentFlags().MarkHidden("log_dir") + cli.PersistentFlags().MarkHidden("log_backtrace_at") + cli.PersistentFlags().MarkHidden("stderrthreshold") + cli.PersistentFlags().MarkHidden("vmodule") +} diff --git a/pkg/kyverno/validate/command.go b/pkg/kyverno/validate/command.go new file mode 100644 index 0000000000..b792ca171a --- /dev/null +++ b/pkg/kyverno/validate/command.go @@ -0,0 +1,64 @@ +package validate + +import ( + "encoding/json" + "fmt" + "io/ioutil" + + policyvalidate "github.com/nirmata/kyverno/pkg/engine/policy" + + v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/yaml" +) + +func Command() *cobra.Command { + cmd := &cobra.Command{ + Use: "validate", + Short: "Validates kyverno policies", + Example: "kyverno validate /path/to/policy1 /path/to/policy2", + RunE: func(cmd *cobra.Command, policyPaths []string) error { + for _, policyPath := range policyPaths { + policy, err := getPolicy(policyPath) + if err != nil { + return err + } + + err = policyvalidate.Validate(*policy) + if err != nil { + return err + } + + fmt.Println("Policy " + policy.Name + " is valid") + } + + return nil + }, + } + + return cmd +} + +func getPolicy(path string) (*v1.ClusterPolicy, error) { + policy := &v1.ClusterPolicy{} + + file, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to load file: %v", err) + } + + policyBytes, err := yaml.ToJSON(file) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(policyBytes, policy); err != nil { + return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err) + } + + if policy.TypeMeta.Kind != "ClusterPolicy" { + return nil, fmt.Errorf("failed to parse policy") + } + + return policy, nil +} diff --git a/pkg/kyverno/version/command.go b/pkg/kyverno/version/command.go new file mode 100644 index 0000000000..25d1324f2a --- /dev/null +++ b/pkg/kyverno/version/command.go @@ -0,0 +1,21 @@ +package version + +import ( + "fmt" + + "github.com/nirmata/kyverno/pkg/version" + "github.com/spf13/cobra" +) + +func Command() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Shows current version of kyverno", + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Printf("Version: %s\n", version.BuildVersion) + fmt.Printf("Time: %s\n", version.BuildTime) + fmt.Printf("Git commit ID: %s\n", version.BuildHash) + return nil + }, + } +} diff --git a/pkg/kyverno/version/version.go b/pkg/kyverno/version/version.go deleted file mode 100644 index 8d1f5dfb75..0000000000 --- a/pkg/kyverno/version/version.go +++ /dev/null @@ -1,29 +0,0 @@ -package version - -import ( - "fmt" - "io" - - "github.com/nirmata/kyverno/pkg/version" - "github.com/spf13/cobra" -) - -// NewCmdVersion is a command to display the build version -func NewCmdVersion(cmdOut io.Writer) *cobra.Command { - - versionCmd := &cobra.Command{ - Use: "version", - Short: "", - Run: func(cmd *cobra.Command, args []string) { - showVersion() - }, - } - - return versionCmd -} - -func showVersion() { - fmt.Printf("Version: %s\n", version.BuildVersion) - fmt.Printf("Time: %s\n", version.BuildTime) - fmt.Printf("Git commit ID: %s\n", version.BuildHash) -} diff --git a/pkg/webhookconfig/common.go b/pkg/webhookconfig/common.go index d73fcafb13..00cfa79ef3 100644 --- a/pkg/webhookconfig/common.go +++ b/pkg/webhookconfig/common.go @@ -63,7 +63,6 @@ func (wrc *WebhookRegistrationClient) constructOwner() v1.OwnerReference { func generateDebugWebhook(name, url string, caData []byte, validate bool, timeoutSeconds int32, resource, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.Webhook { sideEffect := admregapi.SideEffectClassNoneOnDryRun - failurePolicy := admregapi.Ignore return admregapi.Webhook{ Name: name, ClientConfig: admregapi.WebhookClientConfig{ @@ -89,13 +88,11 @@ func generateDebugWebhook(name, url string, caData []byte, validate bool, timeou }, AdmissionReviewVersions: []string{"v1beta1"}, TimeoutSeconds: &timeoutSeconds, - FailurePolicy: &failurePolicy, } } func generateWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32, resource, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.Webhook { sideEffect := admregapi.SideEffectClassNoneOnDryRun - failurePolicy := admregapi.Ignore return admregapi.Webhook{ Name: name, ClientConfig: admregapi.WebhookClientConfig{ @@ -125,6 +122,5 @@ func generateWebhook(name, servicePath string, caData []byte, validation bool, t }, AdmissionReviewVersions: []string{"v1beta1"}, TimeoutSeconds: &timeoutSeconds, - FailurePolicy: &failurePolicy, } } diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index f10289eced..7193661630 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -91,8 +91,6 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest, resou // gather patches patches = append(patches, engineResponse.GetPatches()...) glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName()) - - policyContext.NewResource = engineResponse.PatchedResource } // generate annotations diff --git a/samples/best_practices/disallow_default_namespace.yaml b/samples/best_practices/disallow_default_namespace.yaml index 0f579f4d49..64b1fe8844 100644 --- a/samples/best_practices/disallow_default_namespace.yaml +++ b/samples/best_practices/disallow_default_namespace.yaml @@ -3,7 +3,6 @@ kind: ClusterPolicy metadata: name: disallow-default-namespace annotations: - pod-policies.kyverno.io/autogen-controllers: none policies.kyverno.io/category: Workload Isolation policies.kyverno.io/description: Kubernetes namespaces are an optional feature that provide a way to segment and isolate cluster resources across multiple