From c75736486b704ba4521a424163433fd66ec81fd4 Mon Sep 17 00:00:00 2001 From: Nuno Nelas <47778287+nnelas@users.noreply.github.com> Date: Mon, 18 Dec 2023 14:25:33 +0000 Subject: [PATCH] Adds in-mem `PolicyReportStore` implementation (#107) (#150) * Adds in-mem `PolicyReportStore` implementation (#107) Part of #107 As specified in the original issue, this commit: - Create a store Go interface that satisfies the current store implementation at report/store.go - Refactor current report/store.go into report/store_kubernetes.go, as an implementation of the store interface - Adds a new memory store with 2 maps, one with `ClusterPolicyReports` and another one with `PolicyReports` - Provide an optional cli flag, --store TYPE, with possible types memory, kubernetes. Defaults to --store kubernetes for ease on first Kubewarden installations. Besides that, this commit also: - Changes `printJSON` to `outputScan` to make it more aligned with what the input params is - `outputScan` only outputs at the end of the flow, to prevent having a lot of logs whenever someone scans the entire cluster (we were outputting the entire store on each namespace loop) It also seems that running `make fmt` introduced a couple of unwanted changes that I'm happy to revert to keep this commit smaller. Finally, I also had to do a couple of tweaks to make golangci lint happy. Signed-off-by: Nuno Nelas * Review improvements: - Removes `RetryOnConflict` since we will only need them for Kubernetes backend - Changes "enums" to lower-case (and updates README accordingly) Signed-off-by: Nuno Nelas * Review improvements: - Removes Update methods from PolicyReportStore interface since there's no current usage for them Signed-off-by: Nuno Nelas * Review improvements: - Removes `mutex` since there's no parallel processing - Changes logLevels for both `PolicyReportStore` to debug to prevent output of non-relevant information by default. Also, aligns what is logged between `PolicyReport` and `ClusterPolicyReport` and also `MemoryPolicyReportStore` and `KubernetesPolicyReportStore` Signed-off-by: Nuno Nelas * Review improvements: - Removes `require` dependency from unit-tests. Instead, uses if-clauses to validate the returning errors - Given that I was changing go.mod, I also updated some of our dependencies - Still struggling with `ireturn`. Sometimes it works and build succeeds, sometimes it doesn't Signed-off-by: Nuno Nelas * Review improvements: - Changes godocs since it doesn't make much sense to have an interface knowing about it's implementations Signed-off-by: Nuno Nelas * Review improvements: - Simplifies cmd/root.go logic, since it should only read cmd flags and thus shouldn't have any business logic. There's still an `outputScan` that should also be moved after clarification of https://github.com/kubewarden/audit-scanner/pull/150#discussion_r1417210469 Signed-off-by: Nuno Nelas * Review improvements: - As discussed in https://github.com/kubewarden/audit-scanner/pull/150#discussion_r1417210469, this commit removes `outputScan` logic from `cmd` and moves it to `scanner.go` where it originally was. This ensures we don't add more business logic into a package that should only handle commands Signed-off-by: Nuno Nelas * Review improvements: - Removes "dumb" comment to try to fix golangci. The solution that @jvanz gave [here](https://github.com/kubewarden/audit-scanner/pull/150#discussion_r1425320000) worked like a charm! - Removes unused methods from `PolicyReportStore` interface Signed-off-by: Nuno Nelas * Review improvements: - Simplifies `NewMemoryPolicyReportStore` method signature, by removing `error` which is never used Signed-off-by: Nuno Nelas * Review improvements: - Reverts `wg-policy-prototypes` upgrade since this library is not correctly tagged nor has proper releases, so it's best to separate the upgrade to another PR that we can properly test Signed-off-by: Nuno Nelas --------- Signed-off-by: Nuno Nelas --- README.md | 5 +- cmd/root.go | 102 +++--- cmd/root_test.go | 3 - go.mod | 2 +- go.sum | 52 +-- internal/policies/fetcher_test.go | 50 +-- internal/report/report_test.go | 72 +++-- internal/report/store.go | 302 ++---------------- internal/report/store_kubernetes.go | 248 ++++++++++++++ ...store_test.go => store_kubernetes_test.go} | 140 +------- internal/report/store_memory.go | 160 ++++++++++ internal/report/store_memory_test.go | 106 ++++++ internal/resources/admission_request_test.go | 6 +- internal/resources/fetcher.go | 8 +- internal/resources/fetcher_test.go | 217 +++++++------ internal/scanner/scanner.go | 43 ++- internal/scanner/scanner_test.go | 38 +-- 17 files changed, 859 insertions(+), 695 deletions(-) create mode 100644 internal/report/store_kubernetes.go rename internal/report/{store_test.go => store_kubernetes_test.go} (50%) create mode 100644 internal/report/store_memory.go create mode 100644 internal/report/store_memory_test.go diff --git a/README.md b/README.md index 1c5ee395..2964879d 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,13 @@ The Audit scanner inspects the resources defined in the cluster and identifies the ones that are violating Kubewarden policies. -The results of the scan are made available via `PolicyReport` objects. Each Namespace +The results of the scan can be made available via `PolicyReport` objects. Each Namespace has its own dedicated `PolicyReport`. Cluster-wide resources compliance is available via the `ClusterPolicyReport` resource. +Instead of relying on `PolicyReport` objects, one can also configure Audit scanner to +save all this information in-memory only, by specifying `--store memory`. + # Deployment We recommend to rely on the [kubewarden-controller](https://github.com/kubewarden/kubewarden-controller) diff --git a/cmd/root.go b/cmd/root.go index 664d8bd9..948f08d3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,8 @@ package cmd import ( "fmt" + "github.com/kubewarden/audit-scanner/internal/report" + logconfig "github.com/kubewarden/audit-scanner/internal/log" "github.com/kubewarden/audit-scanner/internal/policies" "github.com/kubewarden/audit-scanner/internal/resources" @@ -27,7 +29,7 @@ type Scanner interface { var level logconfig.Level // print result of scan as JSON to stdout -var printJSON bool +var outputScan bool // list of namespaces to be skipped from scan var skippedNs []string @@ -35,55 +37,57 @@ var skippedNs []string // skip SSL cert validation when connecting to PolicyServers endpoints var insecureSSL bool -var ( - // rootCmd represents the base command when called without any subcommands - rootCmd = &cobra.Command{ - Use: "audit-scanner", - Short: "Reports evaluation of existing Kubernetes resources with your already deployed Kubewarden policies", - Long: `Scans resources in your kubernetes cluster with your already deployed Kubewarden policies. +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "audit-scanner", + Short: "Reports evaluation of existing Kubernetes resources with your already deployed Kubewarden policies", + Long: `Scans resources in your kubernetes cluster with your already deployed Kubewarden policies. Each namespace will have a PolicyReport with the outcome of the scan for resources within this namespace. There will be a ClusterPolicyReport with results for cluster-wide resources.`, - RunE: func(cmd *cobra.Command, args []string) error { - level.SetZeroLogLevel() - namespace, err := cmd.Flags().GetString("namespace") - if err != nil { - return err - } - kubewardenNamespace, err := cmd.Flags().GetString("kubewarden-namespace") - if err != nil { - return err - } - clusterWide, err := cmd.Flags().GetBool("cluster") - if err != nil { - return err - } - policyServerURL, err := cmd.Flags().GetString("policy-server-url") - if err != nil { - return err - } - caCertFile, err := cmd.Flags().GetString("extra-ca") - if err != nil { - return err - } - - policiesFetcher, err := policies.NewFetcher(kubewardenNamespace, skippedNs) - if err != nil { - return err - } - resourcesFetcher, err := resources.NewFetcher(kubewardenNamespace, policyServerURL) - if err != nil { - return err - } - scanner, err := scanner.NewScanner(policiesFetcher, resourcesFetcher, printJSON, insecureSSL, caCertFile) - if err != nil { - return err - } - - return startScanner(namespace, clusterWide, scanner) - }, - } -) + RunE: func(cmd *cobra.Command, args []string) error { + level.SetZeroLogLevel() + namespace, err := cmd.Flags().GetString("namespace") + if err != nil { + return err + } + kubewardenNamespace, err := cmd.Flags().GetString("kubewarden-namespace") + if err != nil { + return err + } + clusterWide, err := cmd.Flags().GetBool("cluster") + if err != nil { + return err + } + policyServerURL, err := cmd.Flags().GetString("policy-server-url") + if err != nil { + return err + } + caCertFile, err := cmd.Flags().GetString("extra-ca") + if err != nil { + return err + } + storeType, err := cmd.Flags().GetString("store") + if err != nil { + return err + } + + policiesFetcher, err := policies.NewFetcher(kubewardenNamespace, skippedNs) + if err != nil { + return err + } + resourcesFetcher, err := resources.NewFetcher(kubewardenNamespace, policyServerURL) + if err != nil { + return err + } + scanner, err := scanner.NewScanner(storeType, policiesFetcher, resourcesFetcher, outputScan, insecureSSL, caCertFile) + if err != nil { + return err + } + + return startScanner(namespace, clusterWide, scanner) + }, +} // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. @@ -96,6 +100,7 @@ func Execute() { log.Fatal().Err(err).Msg("Error on cmd.Execute()") } } + func startScanner(namespace string, clusterWide bool, scanner Scanner) error { if clusterWide && namespace != "" { log.Fatal().Msg("Cannot scan cluster wide and only a namespace at the same time") @@ -124,8 +129,9 @@ func init() { rootCmd.Flags().StringP("kubewarden-namespace", "k", defaultKubewardenNamespace, "namespace where the Kubewarden components (e.g. PolicyServer) are installed (required)") rootCmd.Flags().StringP("policy-server-url", "u", "", "URI to the PolicyServers the Audit Scanner will query. Example: https://localhost:3000. Useful for out-of-cluster debugging") rootCmd.Flags().VarP(&level, "loglevel", "l", fmt.Sprintf("level of the logs. Supported values are: %v", logconfig.SupportedValues)) - rootCmd.Flags().BoolVarP(&printJSON, "output-scan", "o", false, "print result of scan in JSON to stdout") + rootCmd.Flags().BoolVarP(&outputScan, "output-scan", "o", false, "print result of scan in JSON to stdout") rootCmd.Flags().StringSliceVarP(&skippedNs, "ignore-namespaces", "i", nil, "comma separated list of namespace names to be skipped from scan. This flag can be repeated") rootCmd.Flags().BoolVar(&insecureSSL, "insecure-ssl", false, "skip SSL cert validation when connecting to PolicyServers endpoints. Useful for development") rootCmd.Flags().StringP("extra-ca", "f", "", "File path to CA cert in PEM format of PolicyServer endpoints") + rootCmd.Flags().StringP("store", "s", report.KUBERNETES, fmt.Sprintf("PolicyReport store type. Supported values are: %v", report.SupportedTypes)) } diff --git a/cmd/root_test.go b/cmd/root_test.go index 27e89c1f..76287ae9 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -9,7 +9,6 @@ func TestStartScannerForANamespace(t *testing.T) { mockScanner := mockScanner{} err := startScanner(namespace, false, &mockScanner) - if err != nil { t.Errorf("err should be nil, but got %s", err.Error()) } @@ -29,7 +28,6 @@ func TestStartScannerForAllNamespaces(t *testing.T) { // analogous to passing no flags err := startScanner("", false, &mockScanner) - if err != nil { t.Errorf("err should be nil, but got %s", err.Error()) } @@ -48,7 +46,6 @@ func TestScanClusterResources(t *testing.T) { mockScanner := mockScanner{} err := startScanner("", true, &mockScanner) - if err != nil { t.Errorf("err should be nil, but got %s", err.Error()) } diff --git a/go.mod b/go.mod index 6257fe17..749694b3 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/kubewarden/kubewarden-controller v1.9.0 github.com/rs/zerolog v1.31.0 github.com/spf13/cobra v1.8.0 + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb k8s.io/api v0.29.0 k8s.io/apimachinery v0.29.0 k8s.io/client-go v0.29.0 @@ -51,7 +52,6 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum index 0f2e1173..870077a0 100644 --- a/go.sum +++ b/go.sum @@ -78,7 +78,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -124,9 +123,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= @@ -250,10 +246,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -311,12 +303,6 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubewarden/kubewarden-controller v1.9.0-rc1 h1:Udb996VVlQAQiWzVVltx+sRL1hBF8nx8uXXvEZ2H0go= -github.com/kubewarden/kubewarden-controller v1.9.0-rc1/go.mod h1:pyA8ocQ/vjEtEP1eQNlGSOVan0CsTuwKBKbNE6nVmJo= -github.com/kubewarden/kubewarden-controller v1.9.0-rc2 h1:+U5Z973fE+cz07wK5sSQVBeiCeo0BI04xb2YLYEYOG0= -github.com/kubewarden/kubewarden-controller v1.9.0-rc2/go.mod h1:YnlPvlF2Yky9WX2vUgFIuHgH8z/pFXhQAV1VNk74vuU= -github.com/kubewarden/kubewarden-controller v1.9.0-rc3 h1:cykO6EpSZwZ1pdHFo/lELNATZ/uy2EGTtQxiQcP3l90= -github.com/kubewarden/kubewarden-controller v1.9.0-rc3/go.mod h1:YnlPvlF2Yky9WX2vUgFIuHgH8z/pFXhQAV1VNk74vuU= github.com/kubewarden/kubewarden-controller v1.9.0 h1:/6PaCHJJ8qC8/sVdBrH2F+cpokljhhfVL3n1edoYhX4= github.com/kubewarden/kubewarden-controller v1.9.0/go.mod h1:YnlPvlF2Yky9WX2vUgFIuHgH8z/pFXhQAV1VNk74vuU= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -368,8 +354,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= -github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -416,8 +402,6 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -439,8 +423,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -492,8 +476,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -662,8 +646,8 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -765,10 +749,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= -k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= -k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= @@ -776,19 +756,11 @@ k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2E k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= -k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE= -k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= -k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= @@ -803,21 +775,15 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -832,8 +798,6 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h6 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/wg-policy-prototypes v0.0.0-20230505033312-51c21979086a h1:tbASfGvbUgbwsJuh9T0+IZ0hhUNOkryo9Vquv1vjw+o= diff --git a/internal/policies/fetcher_test.go b/internal/policies/fetcher_test.go index 33971f77..508ea858 100644 --- a/internal/policies/fetcher_test.go +++ b/internal/policies/fetcher_test.go @@ -40,7 +40,8 @@ func TestFindNamespacesForAllClusterAdmissionPolicies(t *testing.T) { NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"env": "test"}, }, - }} + }, + } tests := []struct { name string @@ -239,15 +240,16 @@ func TestGetClusterAdmissionPolicies(t *testing.T) { Spec: policiesv1.ClusterAdmissionPolicySpec{ PolicySpec: policiesv1.PolicySpec{ BackgroundAudit: true, - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{"", "apps"}, - APIVersions: []string{"v1", "alphav1"}, - Resources: []string{"pods", "deployments"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"", "apps"}, + APIVersions: []string{"v1", "alphav1"}, + Resources: []string{"pods", "deployments"}, + }, }, }, - }, }, }, Status: policiesv1.PolicyStatus{ @@ -271,15 +273,16 @@ func TestGetClusterAdmissionPolicies(t *testing.T) { Spec: policiesv1.ClusterAdmissionPolicySpec{ PolicySpec: policiesv1.PolicySpec{ BackgroundAudit: true, - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{"", "apps"}, - APIVersions: []string{"v1", "alphav1"}, - Resources: []string{"pods", "deployments"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"", "apps"}, + APIVersions: []string{"v1", "alphav1"}, + Resources: []string{"pods", "deployments"}, + }, }, }, - }, }, }, Status: policiesv1.PolicyStatus{ @@ -302,15 +305,16 @@ func TestGetClusterAdmissionPolicies(t *testing.T) { Spec: policiesv1.ClusterAdmissionPolicySpec{ PolicySpec: policiesv1.PolicySpec{ BackgroundAudit: false, - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{"", "apps"}, - APIVersions: []string{"v1", "alphav1"}, - Resources: []string{"pods", "deployments"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"", "apps"}, + APIVersions: []string{"v1", "alphav1"}, + Resources: []string{"pods", "deployments"}, + }, }, }, - }, }, }, Status: policiesv1.PolicyStatus{ @@ -357,12 +361,14 @@ var nsDefault = v1.Namespace{ Labels: map[string]string{"kubernetes.io/metadata.name": "default"}, }, } + var nsTest = v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Labels: map[string]string{"kubernetes.io/metadata.name": "test", "env": "test"}, }, } + var nsKubewarden = v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "kubewarden", diff --git a/internal/report/report_test.go b/internal/report/report_test.go index afc22a7f..a8cb7a9d 100644 --- a/internal/report/report_test.go +++ b/internal/report/report_test.go @@ -73,15 +73,16 @@ func TestFindClusterPolicyReportResult(t *testing.T) { Spec: policiesv1.ClusterAdmissionPolicySpec{ PolicySpec: policiesv1.PolicySpec{ BackgroundAudit: true, - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"namespaces"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"namespaces"}, + }, }, }, - }, }, }, } @@ -92,17 +93,18 @@ func TestFindClusterPolicyReportResult(t *testing.T) { }) var expectedResource unstructured.Unstructured - for i := 0; i < 5; i++ { - namespaceResource := unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Namespace", - "metadata": map[string]interface{}{ - "name": "testingns" + fmt.Sprint(i), - "creationTimestamp": nil, + for idx := 0; idx < 5; idx++ { + namespaceResource := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "testingns" + fmt.Sprint(idx), + "creationTimestamp": nil, + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{}, }, - "spec": map[string]interface{}{}, - "status": map[string]interface{}{}, - }, } report.AddResult(report.CreateResult(&policy, namespaceResource, &admReviewPass, nil)) expectedResource = namespaceResource @@ -147,15 +149,16 @@ func TestFindPolicyReportResult(t *testing.T) { Spec: policiesv1.AdmissionPolicySpec{ PolicySpec: policiesv1.PolicySpec{ BackgroundAudit: true, - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + }, }, }, - }, }, }, } @@ -166,17 +169,18 @@ func TestFindPolicyReportResult(t *testing.T) { }) var expectedResource unstructured.Unstructured - for i := 0; i < 5; i++ { - namespaceResource := unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Pod", - "metadata": map[string]interface{}{ - "name": "testingns" + fmt.Sprint(i), - "creationTimestamp": nil, + for idx := 0; idx < 5; idx++ { + namespaceResource := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "testingns" + fmt.Sprint(idx), + "creationTimestamp": nil, + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{}, }, - "spec": map[string]interface{}{}, - "status": map[string]interface{}{}, - }, } report.AddResult(report.CreateResult(&policy, namespaceResource, &admReviewPass, nil)) expectedResource = namespaceResource diff --git a/internal/report/store.go b/internal/report/store.go index dbf6fc75..fcc2dda4 100644 --- a/internal/report/store.go +++ b/internal/report/store.go @@ -1,291 +1,31 @@ package report -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "strings" - - "github.com/kubewarden/audit-scanner/internal/constants" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - errorMachinery "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/util/retry" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - polReport "sigs.k8s.io/wg-policy-prototypes/policy-report/pkg/api/wgpolicyk8s.io/v1alpha2" +const ( + KUBERNETES string = "kubernetes" + MEMORY string = "memory" ) -// PolicyReportStore caches the latest version of PolicyReports -type PolicyReportStore struct { - // For now, the store has K8s/etcd backend only - // client used to instantiate PolicyReport resources - client client.Client -} - -// NewPolicyReportStore construct a PolicyReportStore, initializing the -// clusterwide ClusterPolicyReport and namesapcedPolicyReports. -func NewPolicyReportStore() (*PolicyReportStore, error) { - config := ctrl.GetConfigOrDie() - customScheme := scheme.Scheme - customScheme.AddKnownTypes( - polReport.SchemeGroupVersion, - &polReport.PolicyReport{}, - &polReport.ClusterPolicyReport{}, - &polReport.PolicyReportList{}, - &polReport.ClusterPolicyReportList{}, - ) - metav1.AddToGroupVersion(customScheme, polReport.SchemeGroupVersion) - client, err := client.New(config, client.Options{Scheme: customScheme}) - if err != nil { - return nil, fmt.Errorf("failed when creating new client: %w", err) - } - - return &PolicyReportStore{client: client}, nil -} - -// MockNewPolicyReportStore constructs a PolicyReportStore, initializing the -// clusterwide ClusterPolicyReport and namespacedPolicyReports, but setting the -// client. Useful for testing. -func MockNewPolicyReportStore(client client.Client) *PolicyReportStore { - return &PolicyReportStore{client: client} -} - -// GetPolicyReport returns the Policy Report defined inside of the -// given namespace. -// An empty PolicyReport is returned when nothing is found -func (s *PolicyReportStore) GetPolicyReport(namespace string) (PolicyReport, error) { - report := polReport.PolicyReport{} - getErr := s.client.Get(context.TODO(), types.NamespacedName{ - Namespace: namespace, - Name: getNamespacedReportName(namespace), - }, &report) - if getErr != nil { - if errorMachinery.IsNotFound(getErr) { - return PolicyReport{}, constants.ErrResourceNotFound - } - return PolicyReport{}, getErr - } - policyReport := &PolicyReport{ - report, - } - log.Debug().Dict("dict", zerolog.Dict(). - Str("report name", report.GetName()). - Str("report ns", report.GetNamespace()). - Str("report resourceVersion", report.GetResourceVersion())). - Msg("PolicyReport found") - return *policyReport, nil -} - -// Get the ClusterPolicyReport -func (s *PolicyReportStore) GetClusterPolicyReport(name string) (ClusterPolicyReport, error) { - result := polReport.ClusterPolicyReport{} - if !strings.HasPrefix(name, PrefixNameClusterPolicyReport) { - name = getClusterReportName(name) - } - getErr := s.client.Get(context.Background(), client.ObjectKey{Name: name}, &result) - if getErr != nil { - if errorMachinery.IsNotFound(getErr) { - return ClusterPolicyReport{}, constants.ErrResourceNotFound - } - return ClusterPolicyReport{}, getErr - } - return ClusterPolicyReport{ - result, - }, nil -} - -// Update namespaced PolicyReport -func (s *PolicyReportStore) UpdatePolicyReport(report *PolicyReport) error { - err := s.client.Update(context.Background(), &report.PolicyReport) - if err != nil { - return err - } - summary, _ := report.GetSummaryJSON() - log.Info(). - Dict("dict", zerolog.Dict(). - Str("report name", report.GetName()). - Str("report ns", report.GetNamespace()). - Str("report resourceVersion", report.GetResourceVersion()). - Str("summary", summary), - ).Msg("updated PolicyReport") - return nil -} - -// Update ClusterPolicyReport or PolicyReport. ns argument is used in case -// of namespaced PolicyReport -func (s *PolicyReportStore) UpdateClusterPolicyReport(report *ClusterPolicyReport) error { - err := s.client.Update(context.Background(), &report.ClusterPolicyReport) - if err != nil { - return err - } - summary, _ := report.GetSummaryJSON() - log.Info(). - Dict("dict", zerolog.Dict(). - Str("report name", report.GetName()). - Str("report ns", report.GetNamespace()). - Str("summary", summary), - ).Msg("updated ClusterPolicyReport") - return nil -} - -// Delete PolicyReport by namespace -func (s *PolicyReportStore) RemovePolicyReport(namespace string) error { - if report, err := s.GetPolicyReport(namespace); err == nil { - err := s.client.Delete(context.Background(), &report.PolicyReport) - if err != nil { - return err - } - } - return nil -} - -// Delete all namespaced PolicyReports -func (s *PolicyReportStore) RemoveAllNamespacedPolicyReports() error { - err := s.client.DeleteAllOf(context.Background(), &polReport.PolicyReport{}, - client.MatchingLabels(map[string]string{ - LabelAppManagedBy: LabelApp, - })) - if err != nil { - return err - } - return nil -} +var SupportedTypes = [2]string{KUBERNETES, MEMORY} -// createPolicyReport should not be called directly. Use the SavePolicyReport -func (s *PolicyReportStore) createPolicyReport(report *PolicyReport) error { - err := s.client.Create(context.Background(), &report.PolicyReport) - if err != nil { - return fmt.Errorf("create failed: %w", err) - } - summary, _ := report.GetSummaryJSON() - log.Info(). - Dict("dict", zerolog.Dict(). - Str("report name", report.GetName()). - Str("report ns", report.GetNamespace()). - Str("summary", summary), - ).Msg("created PolicyReport") - return nil -} - -// createClusterPolicyReport should not be called directly. Use the SaveClusterPolicyReport -func (s *PolicyReportStore) createClusterPolicyReport(report *ClusterPolicyReport) error { - err := s.client.Create(context.Background(), &report.ClusterPolicyReport) - if err != nil { - return fmt.Errorf("create failed: %w", err) - } - summary, _ := report.GetSummaryJSON() - log.Info(). - Dict("dict", zerolog.Dict(). - Str("report name", report.GetName()). - Str("report ns", report.GetNamespace()). - Str("summary", summary), - ).Msg("created ClusterPolicyReport") - return nil -} +// PolicyReportStore caches the latest version of `PolicyReports` and `ClusterPolicyReports`. +// It also provides functions to read, delete and save these resources, +// only updating them if there is indeed a change in data. +type PolicyReportStore interface { + // GetPolicyReport returns the Policy Report defined inside a given namespace. + // An empty PolicyReport is returned when nothing is found + GetPolicyReport(namespace string) (PolicyReport, error) -// SavePolicyReport instantiates the passed namespaced PolicyReport if it doesn't exist, or -// updates it if one is found -func (s *PolicyReportStore) SavePolicyReport(report *PolicyReport) error { - // Check for existing Policy Report - _, getErr := s.GetPolicyReport(report.GetNamespace()) - if getErr != nil { - // Create new Policy Report if not found - if errors.Is(getErr, constants.ErrResourceNotFound) { - return s.createPolicyReport(report) - } - return getErr - } - // Update existing Policy Report - retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - // get the latest report version to be updated - latestReport, err := s.GetPolicyReport(report.GetNamespace()) - if err != nil { - return err - } - latestReport.Summary = report.Summary - latestReport.Results = report.Results - return s.UpdatePolicyReport(&latestReport) - }) - if retryErr != nil { - return fmt.Errorf("update failed: %w", retryErr) - } - return nil -} + // GetClusterPolicyReport gets the ClusterPolicyReport + GetClusterPolicyReport(name string) (ClusterPolicyReport, error) -// SavePolicyClusterPolicyReport instantiates the ClusterPolicyReport if it doesn't exist, or -// updates it one is found -func (s *PolicyReportStore) SaveClusterPolicyReport(report *ClusterPolicyReport) error { - // Check for existing Policy Report - _, getErr := s.GetClusterPolicyReport(report.GetName()) - if getErr != nil { - // Create new Policy Report if not found - if errors.Is(getErr, constants.ErrResourceNotFound) { - return s.createClusterPolicyReport(report) - } - return getErr - } - // Update existing Policy Report - retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - // get the latest report version to be updated - latestReport, err := s.GetClusterPolicyReport(report.GetName()) - if err != nil { - return err - } - latestReport.Summary = report.Summary - latestReport.Results = report.Results - return s.UpdateClusterPolicyReport(&latestReport) - }) - if retryErr != nil { - return fmt.Errorf("update failed: %w", retryErr) - } - return nil -} - -// listPolicyReports returns all the Policy Reports -// An empty PolicyReport list is returned when nothing is found or an error happens -func (s *PolicyReportStore) listPolicyReports() ([]PolicyReport, error) { - reportList := polReport.PolicyReportList{} - err := s.client.List(context.Background(), &reportList, &client.ListOptions{ - LabelSelector: labels.SelectorFromSet(labels.Set{LabelAppManagedBy: LabelApp}), - }) - if err != nil { - if errorMachinery.IsNotFound(err) { - return []PolicyReport{}, constants.ErrResourceNotFound - } - return []PolicyReport{}, err - } - results := make([]PolicyReport, 0, len(reportList.Items)) - for _, item := range reportList.Items { - results = append(results, PolicyReport{item}) - } - return results, nil -} + // SavePolicyReport instantiates the passed namespaced PolicyReport if it doesn't exist, or + // updates it if one is found + SavePolicyReport(report *PolicyReport) error -// Marshal the contents of the store into a JSON string -func (s *PolicyReportStore) ToJSON() (string, error) { - recapJSON := make(map[string]interface{}) - clusterReport, err := s.GetClusterPolicyReport(constants.DefaultClusterwideReportName) - if err != nil { - log.Error().Err(err).Msg("error fetching ClusterPolicyReport. Ignoring this error to allow user to read the namespaced reports") - } - recapJSON["cluster"] = clusterReport - nsReports, err := s.listPolicyReports() - if err != nil { - return "", err - } - recapJSON["namespaces"] = nsReports + // SaveClusterPolicyReport instantiates the ClusterPolicyReport if it doesn't exist, or + // updates it one is found + SaveClusterPolicyReport(report *ClusterPolicyReport) error - marshaled, err := json.Marshal(recapJSON) - if err != nil { - return "", err - } - return string(marshaled), nil + // ToJSON marshals the contents of the store into a JSON string + ToJSON() (string, error) } diff --git a/internal/report/store_kubernetes.go b/internal/report/store_kubernetes.go new file mode 100644 index 00000000..c2678da7 --- /dev/null +++ b/internal/report/store_kubernetes.go @@ -0,0 +1,248 @@ +package report + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/kubewarden/audit-scanner/internal/constants" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + errorMachinery "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/util/retry" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + polReport "sigs.k8s.io/wg-policy-prototypes/policy-report/pkg/api/wgpolicyk8s.io/v1alpha2" +) + +// KubernetesPolicyReportStore is an implementation of `PolicyReportStore` +// that uses Kubernetes (etcd) to store `PolicyReports` and `ClusterPolicyReports` +type KubernetesPolicyReportStore struct { + // client used to instantiate PolicyReport resources + client client.Client +} + +func NewKubernetesPolicyReportStore() (*KubernetesPolicyReportStore, error) { + config := ctrl.GetConfigOrDie() + customScheme := scheme.Scheme + customScheme.AddKnownTypes( + polReport.SchemeGroupVersion, + &polReport.PolicyReport{}, + &polReport.ClusterPolicyReport{}, + &polReport.PolicyReportList{}, + &polReport.ClusterPolicyReportList{}, + ) + metav1.AddToGroupVersion(customScheme, polReport.SchemeGroupVersion) + client, err := client.New(config, client.Options{Scheme: customScheme}) + if err != nil { + return nil, fmt.Errorf("failed when creating new client: %w", err) + } + + return &KubernetesPolicyReportStore{client: client}, nil +} + +// MockNewKubernetesPolicyReportStore constructs a PolicyReportStore, initializing the +// clusterwide ClusterPolicyReport and namespacedPolicyReports, but setting the +// client. Useful for testing. +func MockNewKubernetesPolicyReportStore(client client.Client) *KubernetesPolicyReportStore { + return &KubernetesPolicyReportStore{client: client} +} + +func (s *KubernetesPolicyReportStore) GetPolicyReport(namespace string) (PolicyReport, error) { + report := polReport.PolicyReport{} + getErr := s.client.Get(context.TODO(), types.NamespacedName{ + Namespace: namespace, + Name: getNamespacedReportName(namespace), + }, &report) + if getErr != nil { + if errorMachinery.IsNotFound(getErr) { + return PolicyReport{}, constants.ErrResourceNotFound + } + return PolicyReport{}, getErr + } + policyReport := &PolicyReport{ + report, + } + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report ns", report.GetNamespace()). + Str("report resourceVersion", report.GetResourceVersion())). + Msg("PolicyReport found") + return *policyReport, nil +} + +func (s *KubernetesPolicyReportStore) GetClusterPolicyReport(name string) (ClusterPolicyReport, error) { + report := polReport.ClusterPolicyReport{} + if !strings.HasPrefix(name, PrefixNameClusterPolicyReport) { + name = getClusterReportName(name) + } + getErr := s.client.Get(context.Background(), client.ObjectKey{Name: name}, &report) + if getErr != nil { + if errorMachinery.IsNotFound(getErr) { + return ClusterPolicyReport{}, constants.ErrResourceNotFound + } + return ClusterPolicyReport{}, getErr + } + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report resourceVersion", report.GetResourceVersion())). + Msg("ClusterPolicyReport found") + return ClusterPolicyReport{ + report, + }, nil +} + +func (s *KubernetesPolicyReportStore) createPolicyReport(report *PolicyReport) error { + err := s.client.Create(context.Background(), &report.PolicyReport) + if err != nil { + return fmt.Errorf("create failed: %w", err) + } + summary, _ := report.GetSummaryJSON() + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report ns", report.GetNamespace()). + Str("summary", summary)). + Msg("created PolicyReport") + return nil +} + +func (s *KubernetesPolicyReportStore) updatePolicyReport(report *PolicyReport) error { + err := s.client.Update(context.Background(), &report.PolicyReport) + if err != nil { + return err + } + summary, _ := report.GetSummaryJSON() + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report ns", report.GetNamespace()). + Str("report resourceVersion", report.GetResourceVersion()). + Str("summary", summary)). + Msg("updated PolicyReport") + return nil +} + +func (s *KubernetesPolicyReportStore) createClusterPolicyReport(report *ClusterPolicyReport) error { + err := s.client.Create(context.Background(), &report.ClusterPolicyReport) + if err != nil { + return fmt.Errorf("create failed: %w", err) + } + summary, _ := report.GetSummaryJSON() + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("summary", summary)). + Msg("created ClusterPolicyReport") + return nil +} + +func (s *KubernetesPolicyReportStore) updateClusterPolicyReport(report *ClusterPolicyReport) error { + err := s.client.Update(context.Background(), &report.ClusterPolicyReport) + if err != nil { + return err + } + summary, _ := report.GetSummaryJSON() + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report resourceVersion", report.GetResourceVersion()). + Str("summary", summary)). + Msg("updated ClusterPolicyReport") + return nil +} + +func (s *KubernetesPolicyReportStore) SavePolicyReport(report *PolicyReport) error { + // Check for existing Policy Report + _, getErr := s.GetPolicyReport(report.GetNamespace()) + if getErr != nil { + // Create new Policy Report if not found + if errors.Is(getErr, constants.ErrResourceNotFound) { + return s.createPolicyReport(report) + } + return getErr + } + // Update existing Policy Report + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + // get the latest report version to be updated + latestReport, err := s.GetPolicyReport(report.GetNamespace()) + if err != nil { + return err + } + latestReport.Summary = report.Summary + latestReport.Results = report.Results + return s.updatePolicyReport(&latestReport) + }) + if retryErr != nil { + return fmt.Errorf("update failed: %w", retryErr) + } + return nil +} + +func (s *KubernetesPolicyReportStore) SaveClusterPolicyReport(report *ClusterPolicyReport) error { + // Check for existing Policy Report + _, getErr := s.GetClusterPolicyReport(report.GetName()) + if getErr != nil { + // Create new Policy Report if not found + if errors.Is(getErr, constants.ErrResourceNotFound) { + return s.createClusterPolicyReport(report) + } + return getErr + } + // Update existing Policy Report + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + // get the latest report version to be updated + latestReport, err := s.GetClusterPolicyReport(report.GetName()) + if err != nil { + return err + } + latestReport.Summary = report.Summary + latestReport.Results = report.Results + return s.updateClusterPolicyReport(&latestReport) + }) + if retryErr != nil { + return fmt.Errorf("update failed: %w", retryErr) + } + return nil +} + +func (s *KubernetesPolicyReportStore) listPolicyReports() ([]PolicyReport, error) { + reportList := polReport.PolicyReportList{} + err := s.client.List(context.Background(), &reportList, &client.ListOptions{ + LabelSelector: labels.SelectorFromSet(labels.Set{LabelAppManagedBy: LabelApp}), + }) + if err != nil { + if errorMachinery.IsNotFound(err) { + return []PolicyReport{}, constants.ErrResourceNotFound + } + return []PolicyReport{}, err + } + results := make([]PolicyReport, 0, len(reportList.Items)) + for _, item := range reportList.Items { + results = append(results, PolicyReport{item}) + } + return results, nil +} + +func (s *KubernetesPolicyReportStore) ToJSON() (string, error) { + recapJSON := make(map[string]interface{}) + clusterReport, err := s.GetClusterPolicyReport(constants.DefaultClusterwideReportName) + if err != nil { + log.Error().Err(err).Msg("error fetching ClusterPolicyReport. Ignoring this error to allow user to read the namespaced reports") + } + recapJSON["cluster"] = clusterReport + nsReports, err := s.listPolicyReports() + if err != nil { + return "", err + } + recapJSON["namespaces"] = nsReports + + marshaled, err := json.Marshal(recapJSON) + if err != nil { + return "", err + } + return string(marshaled), nil +} diff --git a/internal/report/store_test.go b/internal/report/store_kubernetes_test.go similarity index 50% rename from internal/report/store_test.go rename to internal/report/store_kubernetes_test.go index ea3c23d5..47b98c06 100644 --- a/internal/report/store_test.go +++ b/internal/report/store_kubernetes_test.go @@ -15,7 +15,7 @@ import ( var labels = map[string]string{"app.kubernetes.io/managed-by": "kubewarden"} var cpr = report.ClusterPolicyReport{ - v1alpha2.ClusterPolicyReport{ + ClusterPolicyReport: v1alpha2.ClusterPolicyReport{ ObjectMeta: metav1.ObjectMeta{ Name: "polr-clusterwide", CreationTimestamp: metav1.Now(), @@ -27,7 +27,7 @@ var cpr = report.ClusterPolicyReport{ } var npr = report.PolicyReport{ - v1alpha2.PolicyReport{ + PolicyReport: v1alpha2.PolicyReport{ ObjectMeta: metav1.ObjectMeta{ Name: "polr-ns-test", Namespace: "test", @@ -39,7 +39,7 @@ var npr = report.PolicyReport{ }, } -func TestAddPolicyReportStore(t *testing.T) { +func TestAddKubernetesPolicyReportStore(t *testing.T) { customScheme := scheme.Scheme customScheme.AddKnownTypes( v1alpha2.SchemeGroupVersion, @@ -51,7 +51,7 @@ func TestAddPolicyReportStore(t *testing.T) { t.Run("Add then Get namespaced PolicyReport", func(t *testing.T) { client := fake.NewClientBuilder().WithScheme(customScheme).Build() - store := report.MockNewPolicyReportStore(client) + store := report.MockNewKubernetesPolicyReportStore(client) _, err := store.GetPolicyReport(npr.GetNamespace()) if err == nil { t.Fatalf("Should not be found in empty Store") @@ -71,7 +71,7 @@ func TestAddPolicyReportStore(t *testing.T) { client := fake.NewClientBuilder().WithScheme(customScheme). WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport). Build() - store := report.MockNewPolicyReportStore(client) + store := report.MockNewKubernetesPolicyReportStore(client) _ = store.SaveClusterPolicyReport(&cpr) _, err := store.GetClusterPolicyReport(cpr.ObjectMeta.Name) if err != nil { @@ -80,123 +80,7 @@ func TestAddPolicyReportStore(t *testing.T) { }) } -func TestUpdatePolicyReportStore(t *testing.T) { - customScheme := scheme.Scheme - customScheme.AddKnownTypes( - v1alpha2.SchemeGroupVersion, - &v1alpha2.PolicyReport{}, - &v1alpha2.ClusterPolicyReport{}, - &v1alpha2.PolicyReportList{}, - &v1alpha2.ClusterPolicyReportList{}, - ) - - //nolint:dupl - t.Run("Update then Get namespaced PolicyReport", func(t *testing.T) { - client := fake.NewClientBuilder().WithScheme(customScheme). - WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport).Build() - store := report.MockNewPolicyReportStore(client) - - err := store.SavePolicyReport(&npr) - if err != nil { - t.Fatalf("Cannot save PolicyReport: %v", err) - } - resource, err := store.GetPolicyReport(npr.GetNamespace()) - if err != nil { - t.Fatalf("Should be found in Store after adding PolicyReport report to the store: %v", err) - } - if resource.Summary.Skip != 0 { - t.Errorf("Expected Summary.Skip to be 0") - } - // copy first resource version - upr := resource - // do some change in the resource - upr.Summary = v1alpha2.PolicyReportSummary{Skip: 1} - - err = store.UpdatePolicyReport(&upr) - if err != nil { - t.Fatalf("Cannot update PolicyReport: %v", err) - } - r2, _ := store.GetPolicyReport(npr.GetNamespace()) - if r2.Summary.Skip != 1 { - t.Errorf("PolicyReport Expected Summary.Skip to be 1 after update") - } - }) - - //nolint:dupl - t.Run("Clusterwide Update then Get", func(t *testing.T) { - client := fake.NewClientBuilder().WithScheme(customScheme). - WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport). - Build() - store := report.MockNewPolicyReportStore(client) - - err := store.SaveClusterPolicyReport(&cpr) - if err != nil { - t.Fatalf("Cannot save ClusterPolicyReport: %v", err) - } - - resource, err := store.GetClusterPolicyReport(cpr.GetName()) - if err != nil { - t.Errorf("Should be found in Store after adding ClusterPolicyReport report to the store: %v", err) - } - if resource.Summary.Skip != 0 { - t.Errorf("Expected Summary.Skip to be 0") - } - - cprWithSkip := resource - cprWithSkip.Summary = v1alpha2.PolicyReportSummary{Skip: 1} - - err = store.UpdateClusterPolicyReport(&cprWithSkip) - if err != nil { - t.Fatalf("Cannot update ClusterPolicyReport: %v", err) - } - r2, _ := store.GetClusterPolicyReport(cprWithSkip.GetName()) - if r2.Summary.Skip != 1 { - t.Errorf("ClusterPolicyReport Expected Summary.Skip to be 1 after update") - } - }) -} - -func TestDeletePolicyReportStore(t *testing.T) { - customScheme := scheme.Scheme - customScheme.AddKnownTypes( - v1alpha2.SchemeGroupVersion, - &v1alpha2.PolicyReport{}, - &v1alpha2.ClusterPolicyReport{}, - &v1alpha2.PolicyReportList{}, - &v1alpha2.ClusterPolicyReportList{}, - ) - - t.Run("Delete then Get namespaced PolicyReport", func(t *testing.T) { - client := fake.NewClientBuilder().WithScheme(customScheme). - WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport).Build() - store := report.MockNewPolicyReportStore(client) - _, err := store.GetPolicyReport(npr.GetNamespace()) - if err != nil { - t.Errorf("Should be found in Store after adding report to the store") - } - - _ = store.RemovePolicyReport(npr.GetNamespace()) - _, err = store.GetPolicyReport(npr.GetNamespace()) - if err == nil { - t.Fatalf("Should not be found after Remove report from Store") - } - }) - - t.Run("Remove all namespaced", func(t *testing.T) { - client := fake.NewClientBuilder().WithScheme(customScheme). - WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport).Build() - store := report.MockNewPolicyReportStore(client) - _ = store.SavePolicyReport(&npr) - - _ = store.RemoveAllNamespacedPolicyReports() - _, err := store.GetPolicyReport(npr.GetNamespace()) - if err == nil { - t.Fatalf("Should have no results after CleanUp") - } - }) -} - -func TestSaveReports(t *testing.T) { +func TestSaveKubernetesReports(t *testing.T) { customScheme := scheme.Scheme customScheme.AddKnownTypes( v1alpha2.SchemeGroupVersion, @@ -208,20 +92,20 @@ func TestSaveReports(t *testing.T) { t.Run("Save ClusterPolicyReport (create)", func(t *testing.T) { client := fake.NewClientBuilder().WithScheme(customScheme).WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport).Build() - store := report.MockNewPolicyReportStore(client) + store := report.MockNewKubernetesPolicyReportStore(client) report := report.NewClusterPolicyReport("testing") if err := store.SaveClusterPolicyReport(&report); err != nil { - // always updates ClusterPolicyReport, store initializes with blank - // ClusterPolicReport + // always updates ClusterPolicyReport, + // store initializes with blank ClusterPolicyReport t.Errorf("Should not return errors: %v", err) } }) t.Run("Save PolicyReport (create)", func(t *testing.T) { client := fake.NewClientBuilder().WithScheme(customScheme).WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport).Build() - store := report.MockNewPolicyReportStore(client) + store := report.MockNewKubernetesPolicyReportStore(client) npr2 := report.PolicyReport{ - v1alpha2.PolicyReport{ + PolicyReport: v1alpha2.PolicyReport{ ObjectMeta: metav1.ObjectMeta{ Name: "polr-ns-test2", Namespace: "test2", @@ -247,7 +131,7 @@ func TestSaveReports(t *testing.T) { t.Run("Save PolicyReport (update)", func(t *testing.T) { client := fake.NewClientBuilder().WithScheme(customScheme).WithObjects(&npr.PolicyReport, &cpr.ClusterPolicyReport).Build() - store := report.MockNewPolicyReportStore(client) + store := report.MockNewKubernetesPolicyReportStore(client) // copy first resource version upr := npr // do some change diff --git a/internal/report/store_memory.go b/internal/report/store_memory.go new file mode 100644 index 00000000..6ad561fc --- /dev/null +++ b/internal/report/store_memory.go @@ -0,0 +1,160 @@ +package report + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "golang.org/x/exp/maps" + + "github.com/kubewarden/audit-scanner/internal/constants" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +// MemoryPolicyReportStore is an implementation of `PolicyReportStore` +// that uses in-memory cache to store `PolicyReports` and `ClusterPolicyReports` +type MemoryPolicyReportStore struct { + // prCache is a map between a namespace and a PolicyReport + prCache map[string]PolicyReport + + // cprCache is a map between a name and a ClusterPolicyReport + cprCache map[string]ClusterPolicyReport +} + +func NewMemoryPolicyReportStore() *MemoryPolicyReportStore { + return &MemoryPolicyReportStore{ + prCache: make(map[string]PolicyReport), + cprCache: make(map[string]ClusterPolicyReport), + } +} + +func (s *MemoryPolicyReportStore) GetPolicyReport(namespace string) (PolicyReport, error) { + report, found := s.prCache[namespace] + + if !found { + return PolicyReport{}, constants.ErrResourceNotFound + } + + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report ns", report.GetNamespace()). + Str("report resourceVersion", report.GetResourceVersion())). + Msg("PolicyReport found") + return report, nil +} + +func (s *MemoryPolicyReportStore) GetClusterPolicyReport(name string) (ClusterPolicyReport, error) { + if !strings.HasPrefix(name, PrefixNameClusterPolicyReport) { + name = getClusterReportName(name) + } + + report, found := s.cprCache[name] + if !found { + return ClusterPolicyReport{}, constants.ErrResourceNotFound + } + + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report resourceVersion", report.GetResourceVersion())). + Msg("ClusterPolicyReport found") + return report, nil +} + +func (s *MemoryPolicyReportStore) updatePolicyReport(report *PolicyReport) error { + s.prCache[report.GetNamespace()] = *report + + summary, _ := report.GetSummaryJSON() + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report ns", report.GetNamespace()). + Str("report resourceVersion", report.GetResourceVersion()). + Str("summary", summary)). + Msg("updated PolicyReport") + return nil +} + +func (s *MemoryPolicyReportStore) updateClusterPolicyReport(report *ClusterPolicyReport) error { + s.cprCache[report.GetName()] = *report + + summary, _ := report.GetSummaryJSON() + log.Debug().Dict("dict", zerolog.Dict(). + Str("report name", report.GetName()). + Str("report resourceVersion", report.GetResourceVersion()). + Str("summary", summary)). + Msg("updated ClusterPolicyReport") + return nil +} + +func (s *MemoryPolicyReportStore) SavePolicyReport(report *PolicyReport) error { + // Check for existing Policy Report + _, getErr := s.GetPolicyReport(report.GetNamespace()) + if getErr != nil { + // Create new Policy Report if not found + if errors.Is(getErr, constants.ErrResourceNotFound) { + // Update will create a new one if it doesn't exist + return s.updatePolicyReport(report) + } + return getErr + } + + // get the latest report version to be updated + latestReport, err := s.GetPolicyReport(report.GetNamespace()) + if err != nil { + return fmt.Errorf("update failed: %w", err) + } + + // Update existing Policy Report + latestReport.Summary = report.Summary + latestReport.Results = report.Results + return s.updatePolicyReport(&latestReport) +} + +func (s *MemoryPolicyReportStore) SaveClusterPolicyReport(report *ClusterPolicyReport) error { + // Check for existing Policy Report + _, getErr := s.GetClusterPolicyReport(report.GetName()) + if getErr != nil { + // Create new Policy Report if not found + if errors.Is(getErr, constants.ErrResourceNotFound) { + // Update will create a new one if it doesn't exist + return s.updateClusterPolicyReport(report) + } + return getErr + } + + // get the latest report version to be updated + latestReport, err := s.GetClusterPolicyReport(report.GetName()) + if err != nil { + return fmt.Errorf("update failed: %w", err) + } + + // Update existing Policy Report + latestReport.Summary = report.Summary + latestReport.Results = report.Results + return s.updateClusterPolicyReport(&latestReport) +} + +func (s *MemoryPolicyReportStore) listPolicyReports() ([]PolicyReport, error) { //nolint:unparam // respect the interface + return maps.Values(s.prCache), nil +} + +func (s *MemoryPolicyReportStore) ToJSON() (string, error) { + recapJSON := make(map[string]interface{}) + clusterReport, err := s.GetClusterPolicyReport(constants.DefaultClusterwideReportName) + if err != nil { + log.Error().Err(err).Msg("error fetching ClusterPolicyReport. Ignoring this error to allow user to read the namespaced reports") + } + recapJSON["cluster"] = clusterReport + nsReports, err := s.listPolicyReports() + if err != nil { + return "", err + } + recapJSON["namespaces"] = nsReports + + marshaled, err := json.Marshal(recapJSON) + if err != nil { + return "", err + } + return string(marshaled), nil +} diff --git a/internal/report/store_memory_test.go b/internal/report/store_memory_test.go new file mode 100644 index 00000000..89e333af --- /dev/null +++ b/internal/report/store_memory_test.go @@ -0,0 +1,106 @@ +package report_test + +import ( + "testing" + + "github.com/kubewarden/audit-scanner/internal/report" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/wg-policy-prototypes/policy-report/pkg/api/wgpolicyk8s.io/v1alpha2" +) + +func TestAddMemoryPolicyReportStore(t *testing.T) { + t.Run("Add then Get namespaced PolicyReport", func(t *testing.T) { + store := report.NewMemoryPolicyReportStore() + + _, err := store.GetPolicyReport(npr.GetNamespace()) + if err == nil { + t.Errorf("Should not find PolicyReport in empty Store") + } + + err = store.SavePolicyReport(&npr) + if err != nil { + t.Errorf("Cannot save report: %v", err) + } + + _, err = store.GetPolicyReport(npr.GetNamespace()) + if err != nil { + t.Errorf("Should be found in Store after adding report to the store: %v.", err) + } + }) + + t.Run("Clusterwide Add then Get", func(t *testing.T) { + store := report.NewMemoryPolicyReportStore() + + err := store.SaveClusterPolicyReport(&cpr) + if err != nil { + t.Errorf("Cannot save report: %v", err) + } + + _, err = store.GetClusterPolicyReport(cpr.ObjectMeta.Name) + if err != nil { + t.Errorf("Should be found in Store after adding report to the store") + } + }) +} + +func TestSaveMemoryReports(t *testing.T) { + t.Run("Save ClusterPolicyReport (create)", func(t *testing.T) { + store := report.NewMemoryPolicyReportStore() + + report := report.NewClusterPolicyReport("testing") + err := store.SaveClusterPolicyReport(&report) + // always updates ClusterPolicyReport, + // store initializes with blank ClusterPolicyReport + if err != nil { + t.Errorf("Should not return errors: %v", err) + } + }) + + t.Run("Save PolicyReport (create)", func(t *testing.T) { + store := report.NewMemoryPolicyReportStore() + + npr2 := report.PolicyReport{ + PolicyReport: v1alpha2.PolicyReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: "polr-ns-test2", + Namespace: "test2", + CreationTimestamp: metav1.Now(), + }, + Summary: v1alpha2.PolicyReportSummary{}, + Results: []*v1alpha2.PolicyReportResult{}, + }, + } + + err := store.SavePolicyReport(&npr2) + if err != nil { + t.Errorf("Should not return errors: %v", err) + } + + _, err = store.GetPolicyReport(npr2.GetNamespace()) + if err != nil { + t.Errorf("Should not return errors: %v", err) + } + }) + + t.Run("Save PolicyReport (update)", func(t *testing.T) { + store := report.NewMemoryPolicyReportStore() + + // copy first resource version + upr := npr + // do some change + upr.Summary = v1alpha2.PolicyReportSummary{Skip: 1} + + err := store.SavePolicyReport(&upr) + if err != nil { + t.Errorf("Should not return errors: %v", err) + } + + getObj, err := store.GetPolicyReport(npr.GetNamespace()) + if err != nil { + t.Errorf("Should not return errors: %v", err) + } + if getObj.Summary.Skip != 1 { + t.Errorf("Expected Summary.Skip to be 1 after update. Object returned: %v", getObj) + } + }) +} diff --git a/internal/resources/admission_request_test.go b/internal/resources/admission_request_test.go index 988b0140..b24774c3 100644 --- a/internal/resources/admission_request_test.go +++ b/internal/resources/admission_request_test.go @@ -10,8 +10,10 @@ import ( "k8s.io/apimachinery/pkg/types" ) -const resourceName = "testing-name" -const resourceNamespace = "testing-namespace" +const ( + resourceName = "testing-name" + resourceNamespace = "testing-namespace" +) func generateUnstructuredPodObject() unstructured.Unstructured { groupVersionKind := schema.GroupVersionKind{ diff --git a/internal/resources/fetcher.go b/internal/resources/fetcher.go index d759bc6c..a72319aa 100644 --- a/internal/resources/fetcher.go +++ b/internal/resources/fetcher.go @@ -55,7 +55,7 @@ func NewFetcher(kubewardenNamespace string, policyServerURL string) (*Fetcher, e dynamicClient := dynamic.NewForConfigOrDie(config) clientset := kubernetes.NewForConfigOrDie(config) if policyServerURL != "" { - log.Info().Msg(fmt.Sprintf("querying PolicyServers at %s for debugging purposes. Don't forget to start `kwctl port-forward` if needed", policyServerURL)) + log.Info().Msg(fmt.Sprintf("querying PolicyServers at %s for debugging purposes. Don't forget to start `kubectl port-forward` if needed", policyServerURL)) } return &Fetcher{dynamicClient, kubewardenNamespace, policyServerURL, clientset}, nil } @@ -197,7 +197,8 @@ func (f *Fetcher) GetClusterWideResourcesForPolicies(ctx context.Context, polici func (f *Fetcher) getResourcesDynamically(ctx context.Context, resourceFilter *resourceFilter, namespace string) ( - *unstructured.UnstructuredList, error) { + *unstructured.UnstructuredList, error, +) { resourceID := schema.GroupVersionResource{ Group: resourceFilter.groupVersionResource.Group, Version: resourceFilter.groupVersionResource.Version, @@ -220,7 +221,8 @@ func (f *Fetcher) getResourcesDynamically(ctx context.Context, } func (f *Fetcher) getClusterWideResourcesDynamically(ctx context.Context, resourceFilter *resourceFilter) ( - *unstructured.UnstructuredList, error) { + *unstructured.UnstructuredList, error, +) { resourceID := schema.GroupVersionResource{ Group: resourceFilter.groupVersionResource.Group, Version: resourceFilter.groupVersionResource.Version, diff --git a/internal/resources/fetcher_test.go b/internal/resources/fetcher_test.go index 670e318c..9ad4ef2a 100644 --- a/internal/resources/fetcher_test.go +++ b/internal/resources/fetcher_test.go @@ -28,44 +28,47 @@ import ( var policy1 = policiesv1.AdmissionPolicy{ Spec: policiesv1.AdmissionPolicySpec{PolicySpec: policiesv1.PolicySpec{ - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: nil, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: nil, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + }, }, }, - }, }}, } // used to test incorrect or unknown GVKs var policy2 = policiesv1.ClusterAdmissionPolicy{ Spec: policiesv1.ClusterAdmissionPolicySpec{PolicySpec: policiesv1.PolicySpec{ - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: nil, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{"", "apps"}, - APIVersions: []string{"v1", "alphav1"}, - Resources: []string{"pods", "deployments"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: nil, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"", "apps"}, + APIVersions: []string{"v1", "alphav1"}, + Resources: []string{"pods", "deployments"}, + }, }, }, - }, }}, } var policy3 = policiesv1.AdmissionPolicy{ Spec: policiesv1.AdmissionPolicySpec{PolicySpec: policiesv1.PolicySpec{ - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: nil, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{"", "apps"}, - APIVersions: []string{"v1"}, - Resources: []string{"pods", "deployments"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: nil, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"", "apps"}, + APIVersions: []string{"v1"}, + Resources: []string{"pods", "deployments"}, + }, }, }, - }, }}, } @@ -88,30 +91,32 @@ var policy4 = policiesv1.AdmissionPolicy{ // used to test incorrect or unknown GVKs var policyIncorrectRules = policiesv1.ClusterAdmissionPolicy{ Spec: policiesv1.ClusterAdmissionPolicySpec{PolicySpec: policiesv1.PolicySpec{ - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: nil, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods", "Unexistent"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: nil, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods", "Unexistent"}, + }, }, }, - }, }}, } // used to test skipping of clusterwide resources var policyPodsNamespaces = policiesv1.ClusterAdmissionPolicy{ Spec: policiesv1.ClusterAdmissionPolicySpec{PolicySpec: policiesv1.PolicySpec{ - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: nil, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods", "namespaces"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: nil, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods", "namespaces"}, + }, }, }, - }, }}, } @@ -481,15 +486,16 @@ func TestGetClusterWideResourcesForPolicies(t *testing.T) { Spec: policiesv1.ClusterAdmissionPolicySpec{ PolicySpec: policiesv1.PolicySpec{ BackgroundAudit: true, - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods", "namespaces"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods", "namespaces"}, + }, }, }, - }, }, }, Status: policiesv1.PolicyStatus{ @@ -512,15 +518,16 @@ func TestGetClusterWideResourcesForPolicies(t *testing.T) { Spec: policiesv1.ClusterAdmissionPolicySpec{ PolicySpec: policiesv1.PolicySpec{ BackgroundAudit: true, - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods", "namespaces"}, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods", "namespaces"}, + }, }, }, - }, ObjectSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"testing": "label"}, }, @@ -555,7 +562,8 @@ func TestGetClusterWideResourcesForPolicies(t *testing.T) { "name": "testingns-with-label", "creationTimestamp": nil, "labels": map[string]interface{}{ - "testing": "label"}, + "testing": "label", + }, }, "spec": map[string]interface{}{}, "status": map[string]interface{}{}, @@ -622,74 +630,83 @@ func TestIsNamespacedResource(t *testing.T) { expectedIsNamespaced bool expectedErr error }{ - {"pods", metav1.APIResourceList{ - GroupVersion: "v1", - APIResources: []metav1.APIResource{ - { - Name: "namespaces", - SingularName: "namespace", - Kind: "Namespace", - Namespaced: false, - }, - { - Name: "pods", - SingularName: "pod", - Kind: "Pod", - Namespaced: true, + { + "pods", + metav1.APIResourceList{ + GroupVersion: "v1", + APIResources: []metav1.APIResource{ + { + Name: "namespaces", + SingularName: "namespace", + Kind: "Namespace", + Namespaced: false, + }, + { + Name: "pods", + SingularName: "pod", + Kind: "Pod", + Namespaced: true, + }, }, }, - }, schema.GroupVersionResource{ Group: "", Version: "v1", Resource: "pods", - }, true, nil, - }, - {"namespaces", metav1.APIResourceList{ - GroupVersion: "v1", - APIResources: []metav1.APIResource{ - { - Name: "namespaces", - SingularName: "namespace", - Kind: "Namespace", - Namespaced: false, - }, - { - Name: "pods", - SingularName: "pod", - Kind: "Pod", - Namespaced: true, + }, + true, nil, + }, + { + "namespaces", + metav1.APIResourceList{ + GroupVersion: "v1", + APIResources: []metav1.APIResource{ + { + Name: "namespaces", + SingularName: "namespace", + Kind: "Namespace", + Namespaced: false, + }, + { + Name: "pods", + SingularName: "pod", + Kind: "Pod", + Namespaced: true, + }, }, }, - }, schema.GroupVersionResource{ Group: "", Version: "v1", Resource: "namespaces", - }, false, nil, - }, - {"resource not found", metav1.APIResourceList{ - GroupVersion: "v1", - APIResources: []metav1.APIResource{ - { - Name: "namespaces", - SingularName: "namespace", - Kind: "Namespace", - Namespaced: false, - }, - { - Name: "pods", - SingularName: "pod", - Kind: "Pod", - Namespaced: true, + }, + false, nil, + }, + { + "resource not found", + metav1.APIResourceList{ + GroupVersion: "v1", + APIResources: []metav1.APIResource{ + { + Name: "namespaces", + SingularName: "namespace", + Kind: "Namespace", + Namespaced: false, + }, + { + Name: "pods", + SingularName: "pod", + Kind: "Pod", + Namespaced: true, + }, }, }, - }, schema.GroupVersionResource{ Group: "", Version: "v1", Resource: "foos", - }, false, constants.ErrResourceNotFound, + }, + false, constants.ErrResourceNotFound, }, } diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 17e15b0c..abf0f7d0 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -51,20 +51,24 @@ type Scanner struct { reportStore report.PolicyReportStore // http client used to make requests against the Policy Server httpClient http.Client - printJSON bool + outputScan bool } // NewScanner creates a new scanner with the PoliciesFetcher provided. If // insecureClient is false, it will read the caCertFile and add it to the in-app -// cert trust store. This gets used by the httpCLient when connection to +// cert trust store. This gets used by the httpClient when connection to // PolicyServers endpoints. -func NewScanner(policiesFetcher PoliciesFetcher, resourcesFetcher ResourcesFetcher, - printJSON bool, - insecureClient bool, caCertFile string, +func NewScanner( + storeType string, + policiesFetcher PoliciesFetcher, + resourcesFetcher ResourcesFetcher, + outputScan bool, + insecureClient bool, + caCertFile string, ) (*Scanner, error) { - report, err := report.NewPolicyReportStore() + store, err := getPolicyReportStore(storeType) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create PolicyReportStore: %w", err) } // Get the SystemCertPool to build an in-app cert pool from it @@ -104,7 +108,24 @@ func NewScanner(policiesFetcher PoliciesFetcher, resourcesFetcher ResourcesFetch log.Warn().Msg("connecting to PolicyServers endpoints without validating TLS connection") } - return &Scanner{policiesFetcher, resourcesFetcher, *report, httpClient, printJSON}, nil + return &Scanner{ + policiesFetcher: policiesFetcher, + resourcesFetcher: resourcesFetcher, + reportStore: store, + httpClient: httpClient, + outputScan: outputScan, + }, nil +} + +func getPolicyReportStore(storeType string) (report.PolicyReportStore, error) { //nolint:ireturn // returning a generic type is ok here + switch storeType { + case report.KUBERNETES: + return report.NewKubernetesPolicyReportStore() + case report.MEMORY: + return report.NewMemoryPolicyReportStore(), nil + default: + return nil, fmt.Errorf("invalid policyReport store type: %s", storeType) + } } // ScanNamespace scans resources for a given namespace. @@ -158,13 +179,14 @@ func (s *Scanner) ScanNamespace(nsName string) error { } log.Info().Str("namespace", nsName).Msg("namespace scan finished") - if s.printJSON { + if s.outputScan { str, err := s.reportStore.ToJSON() if err != nil { log.Error().Err(err).Msg("error marshaling reportStore to JSON") } fmt.Println(str) //nolint:forbidigo } + return nil } @@ -228,13 +250,14 @@ func (s *Scanner) ScanClusterWideResources() error { } log.Info().Msg("clusterwide resources scan finished") - if s.printJSON { + if s.outputScan { str, err := s.reportStore.ToJSON() if err != nil { log.Error().Err(err).Msg("error marshaling reportStore to JSON") } fmt.Println(str) //nolint:forbidigo } + return nil } diff --git a/internal/scanner/scanner_test.go b/internal/scanner/scanner_test.go index 921a7dd5..c7e8e45c 100644 --- a/internal/scanner/scanner_test.go +++ b/internal/scanner/scanner_test.go @@ -121,16 +121,17 @@ func TestEvaluationClusterReportCache(t *testing.T) { Kind: constants.KubewardenKindClusterAdmissionPolicy, }) policy.SetResourceVersion("1") - resource := unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Namespace", - "metadata": map[string]interface{}{ - "name": "testingns", - "resourceVersion": "2", + resource := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "testingns", + "resourceVersion": "2", + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{}, }, - "spec": map[string]interface{}{}, - "status": map[string]interface{}{}, - }, } auditableResource := resources.AuditableResources{ @@ -183,16 +184,17 @@ func TestEvaluationNamespaceReportCache(t *testing.T) { Kind: constants.KubewardenKindAdmissionPolicy, }) policy.SetResourceVersion("1") - resource := unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Pod", - "metadata": map[string]interface{}{ - "name": "testingpod", - "resourceVersion": "2", + resource := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "testingpod", + "resourceVersion": "2", + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{}, }, - "spec": map[string]interface{}{}, - "status": map[string]interface{}{}, - }, } auditableResource := resources.AuditableResources{