diff --git a/pkg/configauditreport/controller/helper.go b/pkg/configauditreport/controller/helper.go index a9fcaa899..79cdcd6a2 100644 --- a/pkg/configauditreport/controller/helper.go +++ b/pkg/configauditreport/controller/helper.go @@ -21,7 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func Policies(ctx context.Context, config etc.Config, c client.Client, cac configauditreport.ConfigAuditConfig, log logr.Logger) (*policy.Policies, error) { +func Policies(ctx context.Context, config etc.Config, c client.Client, cac configauditreport.ConfigAuditConfig, log logr.Logger, clusterVersion ...string) (*policy.Policies, error) { cm := &corev1.ConfigMap{} err := c.Get(ctx, client.ObjectKey{ @@ -33,7 +33,11 @@ func Policies(ctx context.Context, config etc.Config, c client.Client, cac confi return nil, fmt.Errorf("failed getting policies from configmap: %s/%s: %w", config.Namespace, trivyoperator.PoliciesConfigMapName, err) } } - return policy.NewPolicies(cm.Data, cac, log), nil + var version string + if len(clusterVersion) > 0 { + version = clusterVersion[0] + } + return policy.NewPolicies(cm.Data, cac, log, version), nil } func evaluate(ctx context.Context, policies *policy.Policies, resource client.Object, bi trivyoperator.BuildInfo, cd trivyoperator.ConfigData, c etc.Config, inputs ...[]byte) (Misconfiguration, error) { diff --git a/pkg/configauditreport/controller/policyconfig.go b/pkg/configauditreport/controller/policyconfig.go index bde939d3c..4b5794c71 100644 --- a/pkg/configauditreport/controller/policyconfig.go +++ b/pkg/configauditreport/controller/policyconfig.go @@ -35,6 +35,7 @@ type PolicyConfigController struct { kube.ObjectResolver trivyoperator.PluginContext configauditreport.PluginInMemory + ClusterVersion string } // Controller for trivy-operator-policies-config in the operator namespace; must be cluster scoped even with namespace predicate @@ -118,7 +119,7 @@ func (r *PolicyConfigController) reconcileConfig(kind kube.Kind) reconcile.Func if err != nil { return ctrl.Result{}, err } - policies, err := Policies(ctx, r.Config, r.Client, cac, r.Logger) + policies, err := Policies(ctx, r.Config, r.Client, cac, r.Logger, r.ClusterVersion) if err != nil { return ctrl.Result{}, fmt.Errorf("getting policies: %w", err) } diff --git a/pkg/configauditreport/controller/resource.go b/pkg/configauditreport/controller/resource.go index 4d9501c0d..04c0b13f2 100644 --- a/pkg/configauditreport/controller/resource.go +++ b/pkg/configauditreport/controller/resource.go @@ -45,6 +45,7 @@ type ResourceController struct { RbacReadWriter rbacassessment.ReadWriter InfraReadWriter infraassessment.ReadWriter trivyoperator.BuildInfo + ClusterVersion string } // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch @@ -167,7 +168,7 @@ func (r *ResourceController) reconcileResource(resourceKind kube.Kind) reconcile if err != nil { return ctrl.Result{}, err } - policies, err := Policies(ctx, r.Config, r.Client, cac, r.Logger) + policies, err := Policies(ctx, r.Config, r.Client, cac, r.Logger, r.ClusterVersion) if err != nil { return ctrl.Result{}, fmt.Errorf("getting policies: %w", err) } diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 77d2d5b2e..57a3ea497 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/aquasecurity/trivy-operator/pkg/compliance" "github.com/aquasecurity/trivy-operator/pkg/configauditreport" @@ -266,6 +267,10 @@ func Start(ctx context.Context, buildInfo trivyoperator.BuildInfo, operatorConfi if err != nil { return fmt.Errorf("initializing %s plugin: %w", pluginContext.GetName(), err) } + var gitVersion string + if version, err := clientSet.ServerVersion(); err == nil { + gitVersion = strings.TrimPrefix(version.GitVersion, "v") + } setupLog.Info("Enabling built-in configuration audit scanner") if err = (&controller.ResourceController{ Logger: ctrl.Log.WithName("resourcecontroller"), @@ -278,6 +283,7 @@ func Start(ctx context.Context, buildInfo trivyoperator.BuildInfo, operatorConfi RbacReadWriter: rbacassessment.NewReadWriter(&objectResolver), InfraReadWriter: infraassessment.NewReadWriter(&objectResolver), BuildInfo: buildInfo, + ClusterVersion: gitVersion, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to setup resource controller: %w", err) } @@ -287,6 +293,7 @@ func Start(ctx context.Context, buildInfo trivyoperator.BuildInfo, operatorConfi ObjectResolver: objectResolver, PluginContext: pluginContext, PluginInMemory: plugin, + ClusterVersion: gitVersion, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to setup resource controller: %w", err) } diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index f7dde6410..2e7251f1d 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -5,13 +5,15 @@ import ( "encoding/json" "errors" "fmt" - "github.com/aquasecurity/defsec/pkg/severity" + "io/fs" "path" "path/filepath" "strings" + "github.com/aquasecurity/defsec/pkg/severity" "github.com/aquasecurity/trivy-operator/pkg/configauditreport" "github.com/aquasecurity/trivy-operator/pkg/plugins/trivy" + "github.com/aquasecurity/trivy/pkg/mapfs" "github.com/go-logr/logr" "github.com/liamg/memoryfs" @@ -44,16 +46,18 @@ const ( ) type Policies struct { - data map[string]string - log logr.Logger - cac configauditreport.ConfigAuditConfig + data map[string]string + log logr.Logger + cac configauditreport.ConfigAuditConfig + clusterVersion string } -func NewPolicies(data map[string]string, cac configauditreport.ConfigAuditConfig, log logr.Logger) *Policies { +func NewPolicies(data map[string]string, cac configauditreport.ConfigAuditConfig, log logr.Logger, serverVersion string) *Policies { return &Policies{ - data: data, - log: log, - cac: cac, + data: data, + log: log, + cac: cac, + clusterVersion: serverVersion, } } @@ -207,9 +211,13 @@ func (p *Policies) Eval(ctx context.Context, resource client.Object, inputs ...[ if len(inputs) > 0 { inputResource = inputs[0] } else { - inputResource, err = json.Marshal(resource) - if err != nil { - return nil, err + if jsonManifest, ok := resource.GetAnnotations()["kubectl.kubernetes.io/last-applied-configuration"]; ok { + inputResource = []byte(jsonManifest) // required for outdated-api when k8s convert resources + } else { + inputResource, err = json.Marshal(resource) + if err != nil { + return nil, err + } } } // add input files @@ -217,7 +225,16 @@ func (p *Policies) Eval(ctx context.Context, resource client.Object, inputs ...[ if err != nil { return nil, err } - scanner := kubernetes.NewScanner(getScannerOptions(hasExternalPolicies, p.cac.GetUseBuiltinRegoPolicies(), policiesFolder)...) + + dataFS, dataPaths, err := createDataFS([]string{}, p.clusterVersion) + if err != nil { + return nil, err + } + scanner := kubernetes.NewScanner(getScannerOptions(hasExternalPolicies, + p.cac.GetUseBuiltinRegoPolicies(), + policiesFolder, + dataPaths, + dataFS)...) scanResult, err := scanner.ScanFS(ctx, memfs, inputFolder) if err != nil { return nil, err @@ -246,12 +263,14 @@ func (r *Policies) HasSeverity(resultSeverity severity.Severity) bool { return strings.Contains(defaultSeverity, string(resultSeverity)) } -func getScannerOptions(hasExternalPolicies bool, useDefaultPolicies bool, policiesFolder string) []options.ScannerOption { +func getScannerOptions(hasExternalPolicies bool, useDefaultPolicies bool, policiesFolder string, dataPaths []string, dataFS fs.FS) []options.ScannerOption { optionsArray := []options.ScannerOption{options.ScannerWithEmbeddedPolicies(useDefaultPolicies)} if hasExternalPolicies { optionsArray = append(optionsArray, options.ScannerWithPolicyDirs(policiesFolder)) optionsArray = append(optionsArray, options.ScannerWithPolicyNamespaces(externalPoliciesNamespace)) } + optionsArray = append(optionsArray, options.ScannerWithDataDirs(dataPaths...)) + optionsArray = append(optionsArray, options.ScannerWithDataFilesystem(dataFS)) return optionsArray } @@ -269,3 +288,28 @@ func createPolicyInputFS(memfs *memoryfs.FS, folderName string, fileData []strin } return nil } + +func createDataFS(dataPaths []string, k8sVersion string) (fs.FS, []string, error) { + fsys := mapfs.New() + + // Create a virtual file for Kubernetes scanning + if k8sVersion != "" { + if err := fsys.MkdirAll("system", 0700); err != nil { + return nil, nil, err + } + data := []byte(fmt.Sprintf(`{"k8s": {"version": "%s"}}`, k8sVersion)) + if err := fsys.WriteVirtualFile("system/k8s-version.json", data, 0600); err != nil { + return nil, nil, err + } + } + for _, path := range dataPaths { + if err := fsys.CopyFilesUnder(path); err != nil { + return nil, nil, err + } + } + + // data paths are no longer needed as fs.FS contains only needed files now. + dataPaths = []string{"."} + + return fsys, dataPaths, nil +} diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 2247ba9f0..69b227ecd 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -32,7 +32,7 @@ func TestPolicies_PoliciesByKind(t *testing.T) { "library.kubernetes.rego": "", "library.utils.rego": "", "policy.access_to_host_pid.rego": "", - }, testConfig{}, ctrl.Log.WithName("policy logger")) + }, testConfig{}, ctrl.Log.WithName("policy logger"), "1.27.1") _, err := config.PoliciesByKind("Pod") g.Expect(err).To(MatchError("kinds not defined for policy: policy.access_to_host_pid.rego")) }) @@ -41,7 +41,7 @@ func TestPolicies_PoliciesByKind(t *testing.T) { g := NewGomegaWithT(t) config := policy.NewPolicies(map[string]string{ "policy.access_to_host_pid.kinds": "Workload", - }, testConfig{}, ctrl.Log.WithName("policy logger")) + }, testConfig{}, ctrl.Log.WithName("policy logger"), "1.27.1") _, err := config.PoliciesByKind("Pod") g.Expect(err).To(MatchError("expected policy not found: policy.access_to_host_pid.rego")) }) @@ -68,7 +68,7 @@ func TestPolicies_PoliciesByKind(t *testing.T) { "policy.privileged": "", // This one should be skipped (no policy. prefix) "foo": "bar", - }, testConfig{}, ctrl.Log.WithName("policy logger")) + }, testConfig{}, ctrl.Log.WithName("policy logger"), "1.27.1") g.Expect(config.PoliciesByKind("Pod")).To(Equal(map[string]string{ "policy.access_to_host_pid.rego": "", "policy.cpu_not_limited.rego": "", @@ -142,7 +142,7 @@ func TestPolicies_Supported(t *testing.T) { t.Run(tc.name, func(t *testing.T) { g := NewGomegaWithT(t) log := ctrl.Log.WithName("resourcecontroller") - ready, err := policy.NewPolicies(tc.data, testConfig{}, log).SupportedKind(tc.resource, tc.rbacEnable) + ready, err := policy.NewPolicies(tc.data, testConfig{}, log, "1.27.1").SupportedKind(tc.resource, tc.rbacEnable) g.Expect(err).ToNot(HaveOccurred()) g.Expect(ready).To(Equal(tc.expected)) }) @@ -217,7 +217,7 @@ deny[res] { t.Run(tc.name, func(t *testing.T) { g := NewGomegaWithT(t) log := ctrl.Log.WithName("resourcecontroller") - ready, _, err := policy.NewPolicies(tc.data, testConfig{builtInPolicies: false}, log).Applicable(tc.resource.GetObjectKind().GroupVersionKind().Kind) + ready, _, err := policy.NewPolicies(tc.data, testConfig{builtInPolicies: false}, log, "1.27.1").Applicable(tc.resource.GetObjectKind().GroupVersionKind().Kind) g.Expect(err).ToNot(HaveOccurred()) g.Expect(ready).To(Equal(tc.expected)) }) @@ -743,7 +743,7 @@ deny[res] { t.Run(tc.name, func(t *testing.T) { g := NewGomegaWithT(t) log := ctrl.Log.WithName("resourcecontroller") - checks, err := policy.NewPolicies(tc.policies, newTestConfig(tc.useBuiltInPolicies), log).Eval(context.TODO(), tc.resource) + checks, err := policy.NewPolicies(tc.policies, newTestConfig(tc.useBuiltInPolicies), log, "1.27.1").Eval(context.TODO(), tc.resource) if tc.expectedError != "" { g.Expect(err).To(MatchError(tc.expectedError)) } else {