From 06de5d1622a4ac44a58d9b9d0e2cba2c83e9f7a6 Mon Sep 17 00:00:00 2001 From: Henry Mortimer Date: Thu, 19 Jan 2023 10:11:53 +0000 Subject: [PATCH 1/4] feat: add generate command --- .gitignore | 3 +- src/cmd/execute/execute.go | 72 +++++++++++++++++++++++++++++++++++--- src/cmd/root.go | 3 +- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index a7f5c07b..a84fe8e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ lula -compliance_report-* \ No newline at end of file +compliance_report-* +out/ \ No newline at end of file diff --git a/src/cmd/execute/execute.go b/src/cmd/execute/execute.go index cff1f945..120ca2d6 100644 --- a/src/cmd/execute/execute.go +++ b/src/cmd/execute/execute.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "os" + "path" "path/filepath" "strings" "time" @@ -21,6 +22,7 @@ import ( "github.com/kyverno/kyverno/pkg/openapi" policy2 "github.com/kyverno/kyverno/pkg/policy" "github.com/kyverno/kyverno/pkg/policyreport" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -59,6 +61,11 @@ To execute without creation of any report files lula execute ./oscal-component.yaml -d ` +var generateHelp = ` +To generate kyverno policies: + lula generate ./oscal-component.yaml -o ./out +` + var resourcePaths []string var cluster, dryRun bool @@ -80,13 +87,38 @@ var executeCmd = &cobra.Command{ }, } -func Command() *cobra.Command { +func ExecuteCommand() *cobra.Command { executeCmd.Flags().StringArrayVarP(&resourcePaths, "resource", "r", []string{}, "Path to resource files") executeCmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "Specifies whether to write reports to filesystem") return executeCmd } +var outDirectory string + +var generateCmd = &cobra.Command{ + Use: "generate", + Short: "generate", + Example: generateHelp, + Run: func(cmd *cobra.Command, componentDefinitionPaths []string) { + if len(componentDefinitionPaths) == 0 { + fmt.Println("Path to the local OSCAL file must be present") + os.Exit(1) + } + + err := conductGenerate(componentDefinitionPaths, outDirectory) + if err != nil { + os.Exit(1) + } + }, +} + +func GenerateCommand() *cobra.Command { + generateCmd.Flags().StringVarP(&outDirectory, "out", "o", "./out/", "Path to output kyverno policies") + + return generateCmd +} + func check(e error) { if e != nil { panic(e) @@ -111,7 +143,7 @@ func conductExecute(componentDefinitionPaths []string, resourcePaths []string, d implementedReqs, err := getImplementedReqs(oscalComponentDefinitions) for _, implementedReq := range implementedReqs { - path, err := generatePolicy(implementedReq) + path, err := generatePolicy(implementedReq, "./") if err != nil { log.Log.Error(err, "error string") } @@ -156,6 +188,35 @@ func conductExecute(componentDefinitionPaths []string, resourcePaths []string, d return nil } +func conductGenerate(componentDefinitionPaths []string, outDirectory string) error { + err := os.Mkdir(outDirectory, 0755) + if err != nil { + logrus.Error(err, "error creating output directory") + return err + } + + oscalComponentDefinitions, err := oscalComponentDefinitionsFromPaths(componentDefinitionPaths) + if err != nil { + logrus.Error(err, "error getting oscal component definitions") + return err + } + + implementedReqs, err := getImplementedReqs(oscalComponentDefinitions) + if err != nil { + logrus.Error(err, "error getting implemented requirements") + return err + } + + for _, implementedReq := range implementedReqs { + _, err := generatePolicy(implementedReq, outDirectory) + if err != nil { + logrus.Error(err, "error generating policy") + return err + } + } + return nil +} + // Open files and attempt to unmarshall to oscal component definition structs func oscalComponentDefinitionsFromPaths(filepaths []string) (oscalComponentDefinitions []types.OscalComponentDefinition, err error) { for _, path := range filepaths { @@ -201,7 +262,7 @@ func getImplementedReqs(componentDefinitions []types.OscalComponentDefinition) ( // Turn a ruleset into a ClusterPolicy resource for ability to use Kyverno applyCommandHelper without modification // This needs to copy the rules into a Cluster Policy resource (yaml) and write to individual files // Kyverno will perform applying these and generating pass/fail results -func generatePolicy(implementedRequirement types.ImplementedRequirementsCustom) (policyPath string, err error) { +func generatePolicy(implementedRequirement types.ImplementedRequirementsCustom, outDir string) (policyPath string, err error) { if len(implementedRequirement.Rules) != 0 { // fmt.Printf("%v", implementedRequirement.Rules[0].Validation.RawPattern) @@ -224,11 +285,12 @@ func generatePolicy(implementedRequirement types.ImplementedRequirementsCustom) } fileName := implementedRequirement.UUID + ".yaml" - err = ioutil.WriteFile(fileName, yamlData, 0644) + policyPath = path.Join(outDir, fileName) + err = ioutil.WriteFile(policyPath, yamlData, 0644) if err != nil { + logrus.Error(err) panic("Unable to write data into the file") } - policyPath = "./" + implementedRequirement.UUID + ".yaml" } return diff --git a/src/cmd/root.go b/src/cmd/root.go index 50fd9fd9..1b84eab4 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -16,7 +16,8 @@ var rootCmd = &cobra.Command{ func Execute() { commands := []*cobra.Command{ - execute.Command(), + execute.ExecuteCommand(), + execute.GenerateCommand(), } rootCmd.AddCommand(commands...) From 7887221771a6c4e961a06f738461bf14a07a3e69 Mon Sep 17 00:00:00 2001 From: Henry Mortimer Date: Fri, 20 Jan 2023 14:12:37 +0000 Subject: [PATCH 2/4] refactor: update error message formatting in generate command --- src/cmd/execute/execute.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cmd/execute/execute.go b/src/cmd/execute/execute.go index 120ca2d6..69b18c97 100644 --- a/src/cmd/execute/execute.go +++ b/src/cmd/execute/execute.go @@ -191,26 +191,26 @@ func conductExecute(componentDefinitionPaths []string, resourcePaths []string, d func conductGenerate(componentDefinitionPaths []string, outDirectory string) error { err := os.Mkdir(outDirectory, 0755) if err != nil { - logrus.Error(err, "error creating output directory") + logrus.Error("error creating output directory: ", err) return err } oscalComponentDefinitions, err := oscalComponentDefinitionsFromPaths(componentDefinitionPaths) if err != nil { - logrus.Error(err, "error getting oscal component definitions") + logrus.Error("error getting oscal component definitions: ", err) return err } implementedReqs, err := getImplementedReqs(oscalComponentDefinitions) if err != nil { - logrus.Error(err, "error getting implemented requirements") + logrus.Error("error getting implemented requirements: ", err) return err } for _, implementedReq := range implementedReqs { _, err := generatePolicy(implementedReq, outDirectory) if err != nil { - logrus.Error(err, "error generating policy") + logrus.Error("error generating policy: ", err) return err } } From 138a7362a628f79ed4a2d2e355becb4d7276f978 Mon Sep 17 00:00:00 2001 From: Henry Mortimer Date: Fri, 20 Jan 2023 14:15:04 +0000 Subject: [PATCH 3/4] fix: don't error if policy out dir exists --- src/cmd/execute/execute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/execute/execute.go b/src/cmd/execute/execute.go index 69b18c97..259e72e2 100644 --- a/src/cmd/execute/execute.go +++ b/src/cmd/execute/execute.go @@ -189,7 +189,7 @@ func conductExecute(componentDefinitionPaths []string, resourcePaths []string, d } func conductGenerate(componentDefinitionPaths []string, outDirectory string) error { - err := os.Mkdir(outDirectory, 0755) + err := os.MkdirAll(outDirectory, 0755) if err != nil { logrus.Error("error creating output directory: ", err) return err From 973487680e47440639a18b4fe93532d5399996eb Mon Sep 17 00:00:00 2001 From: Henry Mortimer Date: Mon, 23 Jan 2023 11:32:19 +0000 Subject: [PATCH 4/4] fix: use lowercase name for kyverno policies --- src/cmd/execute/execute.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cmd/execute/execute.go b/src/cmd/execute/execute.go index 259e72e2..0537f51b 100644 --- a/src/cmd/execute/execute.go +++ b/src/cmd/execute/execute.go @@ -266,13 +266,14 @@ func generatePolicy(implementedRequirement types.ImplementedRequirementsCustom, if len(implementedRequirement.Rules) != 0 { // fmt.Printf("%v", implementedRequirement.Rules[0].Validation.RawPattern) + policyName := strings.ToLower(implementedRequirement.UUID) policy := v1.ClusterPolicy{ TypeMeta: metav1.TypeMeta{ Kind: "ClusterPolicy", APIVersion: "kyverno.io/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: implementedRequirement.UUID, + Name: policyName, }, Spec: v1.Spec{ Rules: implementedRequirement.Rules, @@ -284,7 +285,7 @@ func generatePolicy(implementedRequirement types.ImplementedRequirementsCustom, fmt.Printf("Error while Marshaling. %v", err) } - fileName := implementedRequirement.UUID + ".yaml" + fileName := policyName + ".yaml" policyPath = path.Join(outDir, fileName) err = ioutil.WriteFile(policyPath, yamlData, 0644) if err != nil {