From 6fbc7fc6d055665ac9a546472c987c7b5f914e11 Mon Sep 17 00:00:00 2001 From: Takumi Yanagawa Date: Wed, 18 Oct 2023 17:28:47 +0900 Subject: [PATCH] Add oscal2policy cmd for kyverno to c2pcli --- cmd/c2pcli/cmd/cmd.go | 2 + cmd/c2pcli/subcommands/kyverno.go | 39 ++++++++ cmd/compose-kyverno/cmd/cmd.go | 83 +++++++++++++++++ cmd/compose-kyverno/main.go | 30 +++++++ cmd/compose-kyverno/options/options.go | 50 +++++++++++ cmd/tools/subcommands/kyverno/cmd.go | 50 +++++++---- pkg/kyverno/composer.go | 67 ++++++++++++++ pkg/kyverno/composer_test.go | 81 +++++++++++++++++ pkg/oscal/parser.go | 2 + .../kyverno/component-definition.json | 88 +++++++++++++++++++ .../02-setup.yaml | 29 ++++++ .../advanced-restrict-image-registries.yaml | 65 ++++++++++++++ scripts/kyverno/README.md | 38 ++++++++ scripts/kyverno/collect/cronjob.yaml | 86 ++++++++++++++++++ scripts/kyverno/collect/rbac.yaml | 35 ++++++++ scripts/setup-argocd.sh | 1 + 16 files changed, 730 insertions(+), 16 deletions(-) create mode 100644 cmd/c2pcli/subcommands/kyverno.go create mode 100644 cmd/compose-kyverno/cmd/cmd.go create mode 100644 cmd/compose-kyverno/main.go create mode 100644 cmd/compose-kyverno/options/options.go create mode 100644 pkg/kyverno/composer.go create mode 100644 pkg/kyverno/composer_test.go create mode 100644 pkg/testdata/kyverno/component-definition.json create mode 100644 pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/02-setup.yaml create mode 100644 pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/advanced-restrict-image-registries.yaml create mode 100755 scripts/kyverno/README.md create mode 100644 scripts/kyverno/collect/cronjob.yaml create mode 100644 scripts/kyverno/collect/rbac.yaml diff --git a/cmd/c2pcli/cmd/cmd.go b/cmd/c2pcli/cmd/cmd.go index 1994af4..acfffd6 100644 --- a/cmd/c2pcli/cmd/cmd.go +++ b/cmd/c2pcli/cmd/cmd.go @@ -20,6 +20,7 @@ import ( "github.com/spf13/cobra" "github.com/IBM/compliance-to-policy/cmd/c2pcli/options" + "github.com/IBM/compliance-to-policy/cmd/c2pcli/subcommands" composecmd "github.com/IBM/compliance-to-policy/cmd/compose/cmd" reportutilscmd "github.com/IBM/compliance-to-policy/cmd/report-utils/cmd" reportcmd "github.com/IBM/compliance-to-policy/cmd/report/cmd" @@ -48,6 +49,7 @@ func New() *cobra.Command { command.AddCommand(composecmd.New()) command.AddCommand(reportcmd.New()) command.AddCommand(reportutilscmd.New()) + command.AddCommand(subcommands.NewKyvernoSubCommand()) return command } diff --git a/cmd/c2pcli/subcommands/kyverno.go b/cmd/c2pcli/subcommands/kyverno.go new file mode 100644 index 0000000..ac2afb7 --- /dev/null +++ b/cmd/c2pcli/subcommands/kyverno.go @@ -0,0 +1,39 @@ +/* +Copyright 2023 IBM Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package subcommands + +import ( + "github.com/spf13/cobra" + + "github.com/IBM/compliance-to-policy/cmd/c2pcli/options" + composecmd "github.com/IBM/compliance-to-policy/cmd/compose-kyverno/cmd" +) + +func NewKyvernoSubCommand() *cobra.Command { + opts := options.NewOptions() + + command := &cobra.Command{ + Use: "kyverno", + Short: "C2P CLI Kyverno plugin", + } + + opts.AddFlags(command.Flags()) + + command.AddCommand(composecmd.New()) + + return command +} diff --git a/cmd/compose-kyverno/cmd/cmd.go b/cmd/compose-kyverno/cmd/cmd.go new file mode 100644 index 0000000..822e86b --- /dev/null +++ b/cmd/compose-kyverno/cmd/cmd.go @@ -0,0 +1,83 @@ +/* +Copyright 2023 IBM Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/IBM/compliance-to-policy/cmd/compose-kyverno/options" + "github.com/IBM/compliance-to-policy/pkg" + "github.com/IBM/compliance-to-policy/pkg/c2pcr" + "github.com/IBM/compliance-to-policy/pkg/kyverno" + typec2pcr "github.com/IBM/compliance-to-policy/pkg/types/c2pcr" +) + +func New() *cobra.Command { + opts := options.NewOptions() + + command := &cobra.Command{ + Use: "compose", + Short: "Compose deliverable Kyverno policies from OSCAL", + RunE: func(cmd *cobra.Command, args []string) error { + if err := opts.Complete(); err != nil { + return err + } + + if err := opts.Validate(); err != nil { + return err + } + return Run(opts) + }, + } + + opts.AddFlags(command.Flags()) + + return command +} + +func Run(options *options.Options) error { + if err := os.MkdirAll(options.OutputDir, os.ModePerm); err != nil { + return err + } + + var c2pcrSpec typec2pcr.Spec + if err := pkg.LoadYamlFileToObject(options.C2PCRPath, &c2pcrSpec); err != nil { + return err + } + + gitUtils := pkg.NewGitUtils(pkg.NewTempDirectory(options.TempDirPath)) + c2pcrParser := c2pcr.NewParser(gitUtils) + c2pcrParsed, err := c2pcrParser.Parse(c2pcrSpec) + if err != nil { + return err + } + + tmpdir := pkg.NewTempDirectory(options.TempDirPath) + composer := kyverno.NewComposer(c2pcrParsed.PolicyResoureDir, tmpdir) + if err := composer.Compose(c2pcrParsed); err != nil { + return err + } + + if options.OutputDir != "" { + if err := composer.CopyAllTo(options.OutputDir); err != nil { + return err + } + } + return nil +} diff --git a/cmd/compose-kyverno/main.go b/cmd/compose-kyverno/main.go new file mode 100644 index 0000000..f8450dd --- /dev/null +++ b/cmd/compose-kyverno/main.go @@ -0,0 +1,30 @@ +/* +Copyright 2023 IBM Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "os" + + "github.com/IBM/compliance-to-policy/cmd/compose-kyverno/cmd" +) + +func main() { + err := cmd.New().Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/cmd/compose-kyverno/options/options.go b/cmd/compose-kyverno/options/options.go new file mode 100644 index 0000000..90ac571 --- /dev/null +++ b/cmd/compose-kyverno/options/options.go @@ -0,0 +1,50 @@ +/* +Copyright 2023 IBM Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "errors" + + "github.com/spf13/pflag" +) + +type Options struct { + C2PCRPath string + TempDirPath string + OutputDir string +} + +func NewOptions() *Options { + return &Options{} +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.C2PCRPath, "c2pcr", "", "path to c2p CR") + fs.StringVar(&o.TempDirPath, "temp-dir", "", "path to temp directory") + fs.StringVar(&o.OutputDir, "out", ".", "path to a directory for output manifest files of generated Kyverno Policy manifests") +} + +func (o *Options) Complete() error { + return nil +} + +func (o *Options) Validate() error { + if o.C2PCRPath == "" { + return errors.New("--c2pcr is required") + } + return nil +} diff --git a/cmd/tools/subcommands/kyverno/cmd.go b/cmd/tools/subcommands/kyverno/cmd.go index 6d572e6..d2a8976 100644 --- a/cmd/tools/subcommands/kyverno/cmd.go +++ b/cmd/tools/subcommands/kyverno/cmd.go @@ -56,11 +56,20 @@ func New() *cobra.Command { } type policyResourceIndex struct { - Kind string - ApiVersion string - Name string - SourcePath string - HasContext bool + Kind string `json:"kind,omitempty"` + ApiVersion string `json:"apiVersion,omitempty"` + Name string `json:"name,omitempty"` + SrcPath string `json:"srcPath,omitempty"` + DestPath string `json:"destPath,omitempty"` + HasContext bool `json:"hasContext,omitempty"` +} + +type Summary struct { + ResourcesHavingContext []string `json:"resourcesHavingContext,omitempty"` +} +type Result struct { + PolicyResourceIndice []policyResourceIndex `json:"policyResourceIndice,omitempty"` + Summary Summary `json:"summary,omitempty"` } func mapLoadedObject(unstObj *unstructured.Unstructured, path string) *policyResourceIndex { @@ -70,7 +79,7 @@ func mapLoadedObject(unstObj *unstructured.Unstructured, path string) *policyRes ApiVersion: unstObj.GetAPIVersion(), Kind: unstObj.GetKind(), Name: name, - SourcePath: path, + SrcPath: path, } } @@ -165,46 +174,55 @@ func Run(options *Options) error { return err } - inverseMap := map[string][]policyResourceIndex{} - for _, pri := range policyResourceIndice { + inverseMap := map[string][]*policyResourceIndex{} + for idx, pri := range policyResourceIndice { _, found := inverseMap[pri.Name] if found { - inverseMap[pri.Name] = append(inverseMap[pri.Name], pri) + inverseMap[pri.Name] = append(inverseMap[pri.Name], &policyResourceIndice[idx]) } else { - inverseMap[pri.Name] = []policyResourceIndex{pri} + inverseMap[pri.Name] = []*policyResourceIndex{&policyResourceIndice[idx]} } } for name, pris := range inverseMap { if len(pris) > 1 { logger.Warn(fmt.Sprintf("There are duplicate policies for %s", name)) for _, pri := range pris { - logger.Warn(fmt.Sprintf(" - %s", pri.SourcePath)) + logger.Warn(fmt.Sprintf(" - %s", pri.SrcPath)) } } } fnameCreator := pkg.NewFilenameCreator("", &pkg.FilenameCreatorOption{UnlabelToZero: true}) for name, pris := range inverseMap { - for _, pri := range pris { + for idx, pri := range pris { numberedName := fnameCreator.Get(name) targetDir, err := pkg.MakeDir(destDir + "/" + numberedName) if err != nil { return err } - if err := cp.Copy(pri.SourcePath, targetDir+"/"+pri.Name+".yaml"); err != nil { - logger.Error(fmt.Sprintf("Failed to copy %s", pri.SourcePath)) + pris[idx].DestPath = targetDir + "/" + pri.Name + ".yaml" + if err := cp.Copy(pri.SrcPath, pris[idx].DestPath); err != nil { + logger.Error(fmt.Sprintf("Failed to copy %s", pri.SrcPath)) return err } } } + resourcesHavingContext := []string{} for name, pris := range inverseMap { for _, pri := range pris { if pri.HasContext { - println(fmt.Sprintf("%s has 'context' field: source %s", name, pri.SourcePath)) + resourcesHavingContext = append(resourcesHavingContext, name) } } } - return nil + result := Result{ + PolicyResourceIndice: policyResourceIndice, + Summary: Summary{ + ResourcesHavingContext: resourcesHavingContext, + }, + } + + return pkg.WriteObjToJsonFile(destDir+"/result.json", result) } diff --git a/pkg/kyverno/composer.go b/pkg/kyverno/composer.go new file mode 100644 index 0000000..cfaf752 --- /dev/null +++ b/pkg/kyverno/composer.go @@ -0,0 +1,67 @@ +/* +Copyright 2023 IBM Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kyverno + +import ( + "fmt" + + "github.com/IBM/compliance-to-policy/pkg" + typec2pcr "github.com/IBM/compliance-to-policy/pkg/types/c2pcr" + cp "github.com/otiai10/copy" + "go.uber.org/zap" +) + +type Composer struct { + policiesDir string + tempDir pkg.TempDirectory + logger *zap.Logger +} + +func NewComposer(policiesDir string, tempDir pkg.TempDirectory) *Composer { + return &Composer{ + policiesDir: policiesDir, + tempDir: tempDir, + logger: pkg.GetLogger("kyverno/composer"), + } +} + +func (c *Composer) Compose(c2pParsed typec2pcr.C2PCRParsed) error { + for _, componentObject := range c2pParsed.ComponentObjects { + if componentObject.ComponentType == "validation" { + continue + } + for _, ruleObject := range componentObject.RuleObjects { + sourceDir := fmt.Sprintf("%s/%s", c.policiesDir, ruleObject.RuleId) + destDir := fmt.Sprintf("%s/%s", c.tempDir.GetTempDir(), ruleObject.RuleId) + err := cp.Copy(sourceDir, destDir) + if err != nil { + return err + } + } + } + return nil +} + +func (c *Composer) CopyAllTo(destDir string) error { + if _, err := pkg.MakeDir(destDir); err != nil { + return err + } + if err := cp.Copy(c.tempDir.GetTempDir(), destDir); err != nil { + return err + } + return nil +} diff --git a/pkg/kyverno/composer_test.go b/pkg/kyverno/composer_test.go new file mode 100644 index 0000000..5b457cf --- /dev/null +++ b/pkg/kyverno/composer_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2023 IBM Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kyverno + +import ( + "os" + "testing" + + "github.com/IBM/compliance-to-policy/pkg" + "github.com/IBM/compliance-to-policy/pkg/c2pcr" + typec2pcr "github.com/IBM/compliance-to-policy/pkg/types/c2pcr" + "github.com/stretchr/testify/assert" +) + +func TestComposer(t *testing.T) { + policyDir := pkg.PathFromPkgDirectory("./testdata/kyverno/policy-resources") + catalogPath := pkg.PathFromPkgDirectory("./testdata/oscal/catalog.json") + profilePath := pkg.PathFromPkgDirectory("./testdata/oscal/profile.json") + cdPath := pkg.PathFromPkgDirectory("./testdata/kyverno/component-definition.json") + // expectedDir := pkg.PathFromPkgDirectory("./composer/testdata/expected/c2pcr-parser-composed-policies") + + tempDirPath := pkg.PathFromPkgDirectory("./testdata/_test") + err := os.MkdirAll(tempDirPath, os.ModePerm) + assert.NoError(t, err, "Should not happen") + tempDir := pkg.NewTempDirectory(tempDirPath) + + gitUtils := pkg.NewGitUtils(tempDir) + + c2pcrSpec := typec2pcr.Spec{ + Compliance: typec2pcr.Compliance{ + Name: "Test Compliance", + Catalog: typec2pcr.ResourceRef{ + Url: catalogPath, + }, + Profile: typec2pcr.ResourceRef{ + Url: profilePath, + }, + ComponentDefinition: typec2pcr.ResourceRef{ + Url: cdPath, + }, + }, + PolicyResources: typec2pcr.ResourceRef{ + Url: policyDir, + }, + PolicyRersults: typec2pcr.ResourceRef{ + Url: "/1/2/3", + }, + ClusterGroups: []typec2pcr.ClusterGroup{{ + Name: "test-group", + MatchLabels: &map[string]string{"environment": "test"}, + }}, + Binding: typec2pcr.Binding{ + Compliance: "Test Compliance", + ClusterGroups: []string{"test-group"}, + }, + Target: typec2pcr.Target{ + Namespace: "", + }, + } + c2pcrParser := c2pcr.NewParser(gitUtils) + c2pcrParsed, err := c2pcrParser.Parse(c2pcrSpec) + assert.NoError(t, err, "Should not happen") + + composer := NewComposer(c2pcrParsed.PolicyResoureDir, tempDir) + err = composer.Compose(c2pcrParsed) + assert.NoError(t, err, "Should not happen") +} diff --git a/pkg/oscal/parser.go b/pkg/oscal/parser.go index d78285f..065fd84 100644 --- a/pkg/oscal/parser.go +++ b/pkg/oscal/parser.go @@ -40,6 +40,7 @@ type ControlImpleObject struct { type ComponentObject struct { ComponentTitle string + ComponentType string RuleObjects []RuleObject ControlImpleObjects []ControlImpleObject } @@ -106,6 +107,7 @@ func ParseComponentDefinition(cd ComponentDefinitionRoot) []ComponentObject { } componentObjects = append(componentObjects, ComponentObject{ ComponentTitle: component.Title, + ComponentType: component.Type, RuleObjects: ruleObjects, ControlImpleObjects: controlImpleObjects, }) diff --git a/pkg/testdata/kyverno/component-definition.json b/pkg/testdata/kyverno/component-definition.json new file mode 100644 index 0000000..9aa80b3 --- /dev/null +++ b/pkg/testdata/kyverno/component-definition.json @@ -0,0 +1,88 @@ +{ + "component-definition": { + "uuid": "a065e23d-6ac0-4b73-a7a6-c3f76d2b59d2", + "metadata": { + "title": "Component Definition for Kube", + "last-modified": "2023-10-17T22:21:08+00:00", + "version": "1.0", + "oscal-version": "1.0.4" + }, + "components": [ + { + "uuid": "04d90c66-6249-42d2-ad12-e94f2ecbeaed", + "type": "software", + "title": "Kubernetes", + "description": "Kubernetes", + "props": [ + { + "name": "Rule_Id", + "ns": "http://ibm.github.io/compliance-trestle/schemas/oscal/cd/kubernetes", + "value": "advanced-restrict-image-registries", + "remarks": "rule_set_0" + }, + { + "name": "Rule_Description", + "ns": "http://ibm.github.io/compliance-trestle/schemas/oscal/cd/kubernetes", + "value": "In instances where a ClusterPolicy defines all the approved image registries is insufficient, more granular control may be needed to set permitted registries, especially in multi-tenant use cases where some registries may be based on the Namespace. This policy shows an advanced version of the Restrict Image Registries policy which gets a global approved registry from a ConfigMap and, based upon an annotation at the Namespace level, gets the registry approved for that Namespace.", + "remarks": "rule_set_0" + } + ], + "control-implementations": [ + { + "uuid": "bcdb290a-e726-4350-a06a-b7726b826e72", + "source": "https://raw.githubusercontent.com/usnistgov/oscal-content/master/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json", + "description": "NIST r5", + "implemented-requirements": [ + { + "uuid": "850a08cf-eaeb-425f-9587-b3e18153862a", + "control-id": "cm-8.3", + "description": "", + "statements": [ + { + "statement-id": "cm-8.3_smt.a", + "uuid": "7c0ac8ea-5613-451f-8242-7702791727a2", + "description": "", + "props": [ + { + "name": "Rule_Id", + "ns": "http://ibm.github.io/compliance-trestle/schemas/oscal/cd/kubernetes", + "value": "advanced-restrict-image-registries" + } + ] + } + ] + } + ] + } + ] + }, + { + "uuid": "e3e0eb21-c1a5-44d8-b87a-aa983fe703ac", + "type": "validation", + "title": "Kyverno", + "description": "Kyverno", + "props": [ + { + "name": "Rule_Id", + "ns": "http://ibm.github.io/compliance-trestle/schemas/oscal/cd/kyverno", + "value": "advanced-restrict-image-registries", + "remarks": "rule_set_1" + }, + { + "name": "Check_Id", + "ns": "http://ibm.github.io/compliance-trestle/schemas/oscal/cd/kyverno", + "value": "advanced-restrict-image-registries", + "remarks": "rule_set_1" + }, + { + "name": "Check_Description", + "ns": "http://ibm.github.io/compliance-trestle/schemas/oscal/cd/kyverno", + "value": "advanced-restrict-image-registries", + "remarks": "rule_set_1" + } + ], + "control-implementations": [] + } + ] + } +} \ No newline at end of file diff --git a/pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/02-setup.yaml b/pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/02-setup.yaml new file mode 100644 index 0000000..5008eb2 --- /dev/null +++ b/pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/02-setup.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Namespace +metadata: + annotations: + corp.com/allowed-registries: "img.corp.com/*" + name: imageregistries-ns01 +--- +apiVersion: v1 +kind: Namespace +metadata: + annotations: + corp.com/allowed-registries: "docker.io/*" + name: imageregistries-ns02 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: clusterregistries + namespace: imageregistries-ns01 +data: + registries: "corp.img.io/*" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: clusterregistries + namespace: default +data: + registries: "ghcr.io/*" \ No newline at end of file diff --git a/pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/advanced-restrict-image-registries.yaml b/pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/advanced-restrict-image-registries.yaml new file mode 100644 index 0000000..dd8d68c --- /dev/null +++ b/pkg/testdata/kyverno/policy-resources/advanced-restrict-image-registries/advanced-restrict-image-registries.yaml @@ -0,0 +1,65 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: advanced-restrict-image-registries + annotations: + policies.kyverno.io/title: Advanced Restrict Image Registries + policies.kyverno.io/category: Other + policies.kyverno.io/severity: medium + kyverno.io/kyverno-version: 1.6.0 + policies.kyverno.io/minversion: 1.6.0 + kyverno.io/kubernetes-version: "1.23" + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + In instances where a ClusterPolicy defines all the approved image registries + is insufficient, more granular control may be needed to set permitted registries, + especially in multi-tenant use cases where some registries may be based on + the Namespace. This policy shows an advanced version of the Restrict Image Registries + policy which gets a global approved registry from a ConfigMap and, based upon an + annotation at the Namespace level, gets the registry approved for that Namespace. +spec: + validationFailureAction: audit + background: false + rules: + - name: validate-corp-registries + match: + any: + - resources: + kinds: + - Pod + context: + # Get the value of the Namespace annotation called `corp.com/allowed-registries` and store. The value + # must end with a wildcard. Currently assumes there is only a single registry name in the value. + - name: nsregistries + apiCall: + urlPath: "/api/v1/namespaces/{{request.namespace}}" + jmesPath: "metadata.annotations.\"corp.com/allowed-registries\" || ''" + # Get the ConfigMap in the `default` Namespace called `clusterregistries` and store. The value of the key + # must end with a wildcard. Currently assumes there is only a single registry name in the value. + - name: clusterregistries + configMap: + name: clusterregistries + namespace: default + preconditions: + any: + - key: "{{request.operation || 'BACKGROUND'}}" + operator: AnyIn + value: + - CREATE + - UPDATE + validate: + message: This Pod names an image that is not from an approved registry. + foreach: + # Create a flattened array of all containers in the Pod. + - list: "request.object.spec.[initContainers, ephemeralContainers, containers][]" + deny: + conditions: + all: + # Loop over every image and deny the Pod if any image doesn't match either the allowed registry in the + # cluster ConfigMap or the annotation on the Namespace where the Pod is created. + - key: "{{element.image}}" + operator: NotEquals + value: "{{nsregistries}}" + - key: "{{element.image}}" + operator: NotEquals + value: "{{clusterregistries.data.registries}}" \ No newline at end of file diff --git a/scripts/kyverno/README.md b/scripts/kyverno/README.md new file mode 100755 index 0000000..a6bf663 --- /dev/null +++ b/scripts/kyverno/README.md @@ -0,0 +1,38 @@ +## +1. Set environment variables + ``` + export GITHUB_ORG= + export GITHUB_REPO= + export GITHUB_USER= + export GITHUB_TOKEN= + export KUBECONFIG= + ``` +1. Init directory + ``` + init.sh + ``` +1. Collect results + ``` + collect.sh + ``` + +# Install collect cronjob +1. Create github token in secret +``` +kubectl -n c2p create secret generic --save-config kyverno-policy-report-secret --from-literal=token=$GITHUB_TOKEN --from-literal=user=$GITHUB_USER --from-literal=repo=$GITHUB_REPO --from-literal=org=$GITHUB_ORG +``` +1. Deploy c2p status collector +``` +kubectl apply -f ./scripts/collect +``` + +# Install GitOps +1. Create Secret +``` +kubectl -n c2p create secret generic --save-config git-secret --from-literal=user=$GITHUB_USER --from-literal=accessToken=$GITHUB_TOKEN +``` +1. Deploy channel and subscription +``` +kubectl -n c2p apply -f ./scripts/gitops +``` + diff --git a/scripts/kyverno/collect/cronjob.yaml b/scripts/kyverno/collect/cronjob.yaml new file mode 100644 index 0000000..2950aa2 --- /dev/null +++ b/scripts/kyverno/collect/cronjob.yaml @@ -0,0 +1,86 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: collect-kyverno-policy-report + namespace: c2p +spec: + schedule: "*/1 * * * *" + concurrencyPolicy: Replace + jobTemplate: + spec: + template: + spec: + containers: + - name: collector + image: ghcr.io/yana1205/compliance-to-policy-ocm-status-collector:latest + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - | + git clone https://$GITHUB_USER:$GITHUB_TOKEN@github.com/$GITHUB_ORG/$GITHUB_REPO.git collect + git config --global user.name "C2P Status Collector" + git config --global user.email "" + + cd collect + resultdir=./policy-reports + mkdir -p $resultdir + while true + do + git pull + rm -rf $resultdir/* + cd $resultdir + kubectl get policies.kyverno.io -A -o yaml > 00.policies.kyverno.io.yaml + kubectl get clusterpolicies.kyverno.io -o yaml > 00.clusterpolicies.kyverno.io.yaml + kubectl get policyreports.wgpolicyk8s.io -A -o yaml > 00.policyreports.wgpolicyk8s.io.yaml + kubectl get clusterpolicyreports.wgpolicyk8s.io -o yaml > 00.clusterpolicyreports.wgpolicyk8s.io.yaml + + yq '.items[]' 00.policies.kyverno.io.yaml -s '.kind + "." + .metadata.namespace + "." + .metadata.name' + yq '.items[]' 00.clusterpolicies.kyverno.io.yaml -s '.kind + ".." + .metadata.name' + yq '.items[]' 00.policyreports.wgpolicyk8s.io.yaml -s '.kind + "." + .metadata.namespace + "." + .metadata.name' + yq '.items[]' 00.clusterpolicyreports.wgpolicyk8s.io.yaml -s '.kind + ".." + .metadata.name' + cd .. + + echo "git diff" + git diff + echo "" + + git add $resultdir + git commit -m "Push $resultdir at `date`" + if [[ "$?" == "0" ]];then + echo "Push $resultdir to github" + git push + else + echo "Nothing to push $resultdir to github" + fi + + if [[ "$INTERVAL" == "0" || "$INTERVAL" == "" ]];then + break + fi + sleep $INTERVAL + done + env: + - name: GITHUB_USER + valueFrom: + secretKeyRef: + name: kyverno-policy-report-secret + key: user + - name: GITHUB_ORG + valueFrom: + secretKeyRef: + name: kyverno-policy-report-secret + key: org + - name: GITHUB_REPO + valueFrom: + secretKeyRef: + name: kyverno-policy-report-secret + key: repo + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: kyverno-policy-report-secret + key: token + - name: INTERVAL + value: "0" + restartPolicy: OnFailure + serviceAccountName: collector diff --git a/scripts/kyverno/collect/rbac.yaml b/scripts/kyverno/collect/rbac.yaml new file mode 100644 index 0000000..5a6e394 --- /dev/null +++ b/scripts/kyverno/collect/rbac.yaml @@ -0,0 +1,35 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kyverno-collector +rules: +- apiGroups: + - kyverno.io + - wgpolicyk8s.io + resources: + - policies + - clusterpolicies + - policyreports + - clusterpolicyreports + verbs: + - get + - list +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kyverno-collector + namespace: c2p +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kyverno-collector +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kyverno-collector +subjects: +- kind: ServiceAccount + name: kyverno-collector + namespace: c2p \ No newline at end of file diff --git a/scripts/setup-argocd.sh b/scripts/setup-argocd.sh index 70491d1..d418c7e 100755 --- a/scripts/setup-argocd.sh +++ b/scripts/setup-argocd.sh @@ -60,6 +60,7 @@ argocd login localhost:8080 --username=admin --password="${admin_pass}" --insecu argocd repocreds add --upsert https://github.com/$org/$repo --username $user --password $token # argocd app create $appname --repo https://github.com/$org/$repo.git --path $path --dest-server https://kubernetes.default.svc --dest-namespace $ns --sync-option Replace=true --sync-policy automated --allow-empty --auto-prune argocd app create $appname --repo https://github.com/$org/$repo.git --path $path --dest-server https://kubernetes.default.svc --dest-namespace $ns --sync-option Replace=true --sync-policy automated --allow-empty +argocd app set $appname --directory-recurse argocd app get $appname kill $pid \ No newline at end of file