From 71b68df9587c4c32a7f044995589803b24190e46 Mon Sep 17 00:00:00 2001 From: chenk Date: Sat, 28 Oct 2023 20:01:33 +0300 Subject: [PATCH] refactor: break plugin pod spec builder (#1594) * refactor: break plugin pod spec builder Signed-off-by: chenk * refactor: break plugin pod spec builder Signed-off-by: chenk * refactor: break plugin pod spec builder Signed-off-by: chenk --------- Signed-off-by: chenk --- pkg/operator/envtest/suite_test.go | 3 +- pkg/operator/operator.go | 9 - pkg/plugins/trivy/config.go | 487 ++++++++++ pkg/plugins/trivy/config_test.go | 811 ++++++++++++++++ pkg/plugins/trivy/filesystem.go | 467 ++++++++++ pkg/plugins/trivy/image.go | 483 ++++++++++ pkg/plugins/trivy/jobspec.go | 62 ++ pkg/plugins/trivy/plugin.go | 1391 +--------------------------- pkg/plugins/trivy/plugin_test.go | 791 +--------------- 9 files changed, 2325 insertions(+), 2179 deletions(-) create mode 100644 pkg/plugins/trivy/config.go create mode 100644 pkg/plugins/trivy/config_test.go create mode 100644 pkg/plugins/trivy/filesystem.go create mode 100644 pkg/plugins/trivy/image.go create mode 100644 pkg/plugins/trivy/jobspec.go diff --git a/pkg/operator/envtest/suite_test.go b/pkg/operator/envtest/suite_test.go index cfdf8cc46..b04371001 100644 --- a/pkg/operator/envtest/suite_test.go +++ b/pkg/operator/envtest/suite_test.go @@ -123,7 +123,8 @@ var _ = BeforeSuite(func() { }, }) Expect(err).ToNot(HaveOccurred()) - + err = plugin.Init(pluginContext) + Expect(err).ToNot(HaveOccurred()) err = (&controller.WorkloadController{ Logger: ctrl.Log.WithName("reconciler").WithName("vulnerabilityreport"), Config: config, diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index f83631e33..ab86c8d08 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -165,11 +165,6 @@ func Start(ctx context.Context, buildInfo trivyoperator.BuildInfo, operatorConfi return err } - err = plugin.Init(pluginContext) - if err != nil { - return fmt.Errorf("initializing %s plugin: %w", pluginContext.GetName(), err) - } - if err = (&vcontroller.WorkloadController{ Logger: ctrl.Log.WithName("reconciler").WithName("vulnerabilityreport"), Config: operatorConfig, @@ -268,10 +263,6 @@ func Start(ctx context.Context, buildInfo trivyoperator.BuildInfo, operatorConfi if err != nil { return fmt.Errorf("initializing %s plugin: %w", pluginContext.GetName(), err) } - err = plugin.Init(pluginContext) - 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") diff --git a/pkg/plugins/trivy/config.go b/pkg/plugins/trivy/config.go new file mode 100644 index 000000000..f047ae900 --- /dev/null +++ b/pkg/plugins/trivy/config.go @@ -0,0 +1,487 @@ +package trivy + +import ( + "fmt" + + "path/filepath" + + "strconv" + "strings" + + "github.com/aquasecurity/trivy-operator/pkg/utils" + + "github.com/aquasecurity/trivy-operator/pkg/trivyoperator" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + keyTrivyImageRepository = "trivy.repository" + keyTrivyImageTag = "trivy.tag" + //nolint:gosec + keyTrivyImagePullSecret = "trivy.imagePullSecret" + keyTrivyImagePullPolicy = "trivy.imagePullPolicy" + keyTrivyMode = "trivy.mode" + keyTrivyAdditionalVulnerabilityReportFields = "trivy.additionalVulnerabilityReportFields" + keyTrivyCommand = "trivy.command" + KeyTrivySeverity = "trivy.severity" + keyTrivySlow = "trivy.slow" + keyTrivyVulnType = "trivy.vulnType" + keyTrivyIgnoreUnfixed = "trivy.ignoreUnfixed" + keyTrivyOfflineScan = "trivy.offlineScan" + keyTrivyTimeout = "trivy.timeout" + keyTrivyIgnoreFile = "trivy.ignoreFile" + keyTrivyIgnorePolicy = "trivy.ignorePolicy" + keyTrivyInsecureRegistryPrefix = "trivy.insecureRegistry." + keyTrivyNonSslRegistryPrefix = "trivy.nonSslRegistry." + keyTrivyMirrorPrefix = "trivy.registry.mirror." + keyTrivyHTTPProxy = "trivy.httpProxy" + keyTrivyHTTPSProxy = "trivy.httpsProxy" + keyTrivyNoProxy = "trivy.noProxy" + keyTrivySslCertDir = "trivy.sslCertDir" + // nolint:gosec // This is not a secret, but a configuration value. + keyTrivyGitHubToken = "trivy.githubToken" + keyTrivySkipFiles = "trivy.skipFiles" + keyTrivySkipDirs = "trivy.skipDirs" + keyTrivyDBRepository = "trivy.dbRepository" + keyTrivyJavaDBRepository = "trivy.javaDbRepository" + keyTrivyDBRepositoryInsecure = "trivy.dbRepositoryInsecure" + + keyTrivyUseBuiltinRegoPolicies = "trivy.useBuiltinRegoPolicies" + keyTrivySupportedConfigAuditKinds = "trivy.supportedConfigAuditKinds" + + keyTrivyServerURL = "trivy.serverURL" + keyTrivyClientServerSkipUpdate = "trivy.clientServerSkipUpdate" + keyTrivySkipJavaDBUpdate = "trivy.skipJavaDBUpdate" + // nolint:gosec // This is not a secret, but a configuration value. + keyTrivyServerTokenHeader = "trivy.serverTokenHeader" + keyTrivyServerInsecure = "trivy.serverInsecure" + // nolint:gosec // This is not a secret, but a configuration value. + keyTrivyServerToken = "trivy.serverToken" + keyTrivyServerCustomHeaders = "trivy.serverCustomHeaders" + + keyResourcesRequestsCPU = "trivy.resources.requests.cpu" + keyResourcesRequestsMemory = "trivy.resources.requests.memory" + keyResourcesLimitsCPU = "trivy.resources.limits.cpu" + keyResourcesLimitsMemory = "trivy.resources.limits.memory" + keyResourcesRequestEphemeralStorage = "trivy.resources.requests.ephemeral-storage" + keyResourcesLimitEphemeralStorage = "trivy.resources.limits.ephemeral-storage" +) + +// Config defines configuration params for this plugin. +type Config struct { + trivyoperator.PluginConfig +} + +func (c Config) GetAdditionalVulnerabilityReportFields() AdditionalFields { + addFields := AdditionalFields{} + + fields, ok := c.Data[keyTrivyAdditionalVulnerabilityReportFields] + if !ok { + return addFields + } + for _, field := range strings.Split(fields, ",") { + switch strings.TrimSpace(field) { + case "Description": + addFields.Description = true + case "Links": + addFields.Links = true + case "CVSS": + addFields.CVSS = true + case "Target": + addFields.Target = true + case "Class": + addFields.Class = true + case "PackageType": + addFields.PackageType = true + case "PackagePath": + addFields.PkgPath = true + } + } + return addFields +} + +// GetImageRef returns upstream Trivy container image reference. +func (c Config) GetImageRef() (string, error) { + repository, err := c.GetRequiredData(keyTrivyImageRepository) + if err != nil { + return "", err + } + tag, err := c.GetImageTag() + if err != nil { + return "", err + } + + return fmt.Sprintf("%s:%s", repository, tag), nil +} + +// GetImageTag returns upstream Trivy container image tag. +func (c Config) GetImageTag() (string, error) { + tag, err := c.GetRequiredData(keyTrivyImageTag) + if err != nil { + return "", err + } + return tag, nil +} + +func (c Config) GetImagePullSecret() []corev1.LocalObjectReference { + ips, ok := c.Data[keyTrivyImagePullSecret] + if !ok { + return []corev1.LocalObjectReference{} + } + return []corev1.LocalObjectReference{{Name: ips}} +} + +func (c Config) GetImagePullPolicy() string { + ipp, ok := c.Data[keyTrivyImagePullPolicy] + if !ok { + return "IfNotPresent" + } + return ipp +} + +func (c Config) GetMode() (Mode, error) { + var ok bool + var value string + if value, ok = c.Data[keyTrivyMode]; !ok { + return "", fmt.Errorf("property %s not set", keyTrivyMode) + } + + switch Mode(value) { + case Standalone: + return Standalone, nil + case ClientServer: + return ClientServer, nil + } + + return "", fmt.Errorf("invalid value (%s) of %s; allowed values (%s, %s)", + value, keyTrivyMode, Standalone, ClientServer) +} + +func (c Config) GetCommand() (Command, error) { + var ok bool + var value string + if value, ok = c.Data[keyTrivyCommand]; !ok { + // for backward compatibility, fallback to ImageScan + return Image, nil + } + switch Command(value) { + case Image: + return Image, nil + case Filesystem: + return Filesystem, nil + case Rootfs: + return Rootfs, nil + } + return "", fmt.Errorf("invalid value (%s) of %s; allowed values (%s, %s, %s)", + value, keyTrivyCommand, Image, Filesystem, Rootfs) +} + +func (c Config) GetServerURL() (string, error) { + return c.GetRequiredData(keyTrivyServerURL) +} + +func (c Config) GetClientServerSkipUpdate() bool { + val, ok := c.Data[keyTrivyClientServerSkipUpdate] + if !ok { + return false + } + boolVal, err := strconv.ParseBool(val) + if err != nil { + return false + } + return boolVal +} + +func (c Config) GetSkipJavaDBUpdate() bool { + val, ok := c.Data[keyTrivySkipJavaDBUpdate] + if !ok { + return false + } + boolVal, err := strconv.ParseBool(val) + if err != nil { + return false + } + return boolVal +} + +func (c Config) GetServerInsecure() bool { + _, ok := c.Data[keyTrivyServerInsecure] + return ok +} + +func (c Config) GetDBRepositoryInsecure() bool { + val, ok := c.Data[keyTrivyDBRepositoryInsecure] + if !ok { + return false + } + boolVal, _ := strconv.ParseBool(val) + return boolVal +} +func (c Config) GetUseBuiltinRegoPolicies() bool { + val, ok := c.Data[keyTrivyUseBuiltinRegoPolicies] + if !ok { + return true + } + boolVal, err := strconv.ParseBool(val) + if err != nil { + return true + } + return boolVal +} +func (c Config) GetSslCertDir() string { + val, ok := c.Data[keyTrivySslCertDir] + if !ok { + return "" + } + return val +} + +func (c Config) GetSeverity() string { + val, ok := c.Data[KeyTrivySeverity] + if !ok { + return "" + } + return val +} + +func (c Config) GetSlow() bool { + val, ok := c.Data[keyTrivySlow] + if !ok { + return true + } + boolVal, err := strconv.ParseBool(val) + if err != nil { + return true + } + return boolVal +} + +func (c Config) GetVulnType() string { + val, ok := c.Data[keyTrivyVulnType] + if !ok { + return "" + } + trimmedVulnType := strings.TrimSpace(val) + if !(trimmedVulnType == "os" || trimmedVulnType == "library") { + return "" + } + return trimmedVulnType +} + +func (c Config) GetSupportedConfigAuditKinds() []string { + val, ok := c.Data[keyTrivySupportedConfigAuditKinds] + if !ok { + return utils.MapKinds(strings.Split(SupportedConfigAuditKinds, ",")) + } + return utils.MapKinds(strings.Split(val, ",")) +} + +func (c Config) IgnoreFileExists() bool { + _, ok := c.Data[keyTrivyIgnoreFile] + return ok +} + +func (c Config) FindIgnorePolicyKey(workload client.Object) string { + keysByPrecedence := []string{ + keyTrivyIgnorePolicy + "." + workload.GetNamespace() + "." + workload.GetName(), + keyTrivyIgnorePolicy + "." + workload.GetNamespace(), + keyTrivyIgnorePolicy, + } + for _, key := range keysByPrecedence { + for key2 := range c.Data { + if key2 == keyTrivyIgnorePolicy || strings.HasPrefix(key2, keyTrivyIgnorePolicy) { + tempKey := key2 + if key2 != keyTrivyIgnorePolicy { + // replace dot with astrix for regex matching + tempKey = fmt.Sprintf("%s%s", keyTrivyIgnorePolicy, strings.ReplaceAll(tempKey[len(keyTrivyIgnorePolicy):], ".", "*")) + } + matched, err := filepath.Match(tempKey, key) + if err == nil && matched { + return key2 + } + } + } + } + return "" +} + +func (c Config) GenerateIgnoreFileVolumeIfAvailable(trivyConfigName string) (*corev1.Volume, *corev1.VolumeMount) { + if !c.IgnoreFileExists() { + return nil, nil + } + volume := corev1.Volume{ + Name: ignoreFileVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: trivyConfigName, + }, + Items: []corev1.KeyToPath{ + { + Key: keyTrivyIgnoreFile, + Path: ignoreFileName, + }, + }, + }, + }, + } + volumeMount := corev1.VolumeMount{ + Name: ignoreFileVolumeName, + MountPath: ignoreFileMountPath, + SubPath: ignoreFileName, + } + return &volume, &volumeMount +} + +func (c Config) GenerateSslCertDirVolumeIfAvailable(trivyConfigName string) (*corev1.Volume, *corev1.VolumeMount) { + var sslCertDirHost string + if sslCertDirHost = c.GetSslCertDir(); len(sslCertDirHost) == 0 { + return nil, nil + } + volume := corev1.Volume{ + Name: sslCertDirVolumeName, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: sslCertDirHost, + }, + }, + } + volumeMount := corev1.VolumeMount{ + Name: sslCertDirVolumeName, + MountPath: SslCertDir, + ReadOnly: true, + } + return &volume, &volumeMount +} + +func (c Config) GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName string, workload client.Object) (*corev1.Volume, *corev1.VolumeMount) { + ignorePolicyKey := c.FindIgnorePolicyKey(workload) + if ignorePolicyKey == "" { + return nil, nil + } + volume := corev1.Volume{ + Name: ignorePolicyVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: trivyConfigName, + }, + Items: []corev1.KeyToPath{ + { + Key: c.FindIgnorePolicyKey(workload), + Path: ignorePolicyName, + }, + }, + }, + }, + } + volumeMounts := corev1.VolumeMount{ + Name: ignorePolicyVolumeName, + MountPath: ignorePolicyMountPath, + SubPath: ignorePolicyName, + } + return &volume, &volumeMounts +} + +func (c Config) IgnoreUnfixed() bool { + _, ok := c.Data[keyTrivyIgnoreUnfixed] + return ok +} + +func (c Config) OfflineScan() bool { + _, ok := c.Data[keyTrivyOfflineScan] + return ok +} + +func (c Config) GetInsecureRegistries() map[string]bool { + insecureRegistries := make(map[string]bool) + for key, val := range c.Data { + if strings.HasPrefix(key, keyTrivyInsecureRegistryPrefix) { + insecureRegistries[val] = true + } + } + + return insecureRegistries +} + +func (c Config) GetNonSSLRegistries() map[string]bool { + nonSSLRegistries := make(map[string]bool) + for key, val := range c.Data { + if strings.HasPrefix(key, keyTrivyNonSslRegistryPrefix) { + nonSSLRegistries[val] = true + } + } + + return nonSSLRegistries +} + +func (c Config) GetMirrors() map[string]string { + res := make(map[string]string) + for registryKey, mirror := range c.Data { + if !strings.HasPrefix(registryKey, keyTrivyMirrorPrefix) { + continue + } + res[strings.TrimPrefix(registryKey, keyTrivyMirrorPrefix)] = mirror + } + return res +} + +// GetResourceRequirements creates ResourceRequirements from the Config. +func (c Config) GetResourceRequirements() (corev1.ResourceRequirements, error) { + requirements := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{}, + Limits: corev1.ResourceList{}, + } + + err := c.setResourceLimit(keyResourcesRequestsCPU, &requirements.Requests, corev1.ResourceCPU) + if err != nil { + return requirements, err + } + + err = c.setResourceLimit(keyResourcesRequestsMemory, &requirements.Requests, corev1.ResourceMemory) + if err != nil { + return requirements, err + } + + err = c.setResourceLimit(keyResourcesRequestEphemeralStorage, &requirements.Requests, corev1.ResourceEphemeralStorage) + if err != nil { + return requirements, err + } + + err = c.setResourceLimit(keyResourcesLimitsCPU, &requirements.Limits, corev1.ResourceCPU) + if err != nil { + return requirements, err + } + + err = c.setResourceLimit(keyResourcesLimitsMemory, &requirements.Limits, corev1.ResourceMemory) + if err != nil { + return requirements, err + } + + err = c.setResourceLimit(keyResourcesLimitEphemeralStorage, &requirements.Limits, corev1.ResourceEphemeralStorage) + if err != nil { + return requirements, err + } + + return requirements, nil +} + +func (c Config) setResourceLimit(configKey string, k8sResourceList *corev1.ResourceList, k8sResourceName corev1.ResourceName) error { + if value, found := c.Data[configKey]; found { + quantity, err := resource.ParseQuantity(value) + if err != nil { + return fmt.Errorf("parsing resource definition %s: %s %w", configKey, value, err) + } + + (*k8sResourceList)[k8sResourceName] = quantity + } + return nil +} + +func (c Config) GetDBRepository() (string, error) { + return c.GetRequiredData(keyTrivyDBRepository) +} diff --git a/pkg/plugins/trivy/config_test.go b/pkg/plugins/trivy/config_test.go new file mode 100644 index 000000000..c2e192eb1 --- /dev/null +++ b/pkg/plugins/trivy/config_test.go @@ -0,0 +1,811 @@ +package trivy + +import ( + "context" + "time" + + "testing" + + "github.com/aquasecurity/trivy-operator/pkg/ext" + "github.com/aquasecurity/trivy-operator/pkg/kube" + + "github.com/aquasecurity/trivy-operator/pkg/trivyoperator" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + fixedTime = time.Now() + fixedClock = ext.NewFixedClock(fixedTime) +) + +func TestConfig_GetImageRef(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedError string + expectedImageRef string + }{ + { + name: "Should return error", + configData: Config{PluginConfig: trivyoperator.PluginConfig{}}, + expectedError: "property trivy.repository not set", + }, + { + name: "Should return error", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "tag": "0.8.0", + }, + }}, + expectedError: "property trivy.repository not set", + }, + { + name: "Should return error", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.repository": "gcr.io/aquasecurity/trivy", + }, + }}, + expectedError: "property trivy.tag not set", + }, + { + name: "Should return image reference from config data", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.repository": "gcr.io/aquasecurity/trivy", + "trivy.tag": "0.8.0", + }, + }}, + expectedImageRef: "gcr.io/aquasecurity/trivy:0.8.0", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + imageRef, err := tc.configData.GetImageRef() + if tc.expectedError != "" { + require.EqualError(t, err, tc.expectedError) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expectedImageRef, imageRef) + } + }) + } +} + +func TestConfig_GetAdditionalVulnerabilityReportFields(t *testing.T) { + testCases := []struct { + name string + configData Config + additionalFields AdditionalFields + }{ + { + name: "no additional fields are set", + configData: Config{PluginConfig: trivyoperator.PluginConfig{}}, + additionalFields: AdditionalFields{}, + }, + { + name: "all additional fields are set", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.additionalVulnerabilityReportFields": "PackageType,PkgPath,Class,Target,Links,Description,CVSS", + }, + }}, + additionalFields: AdditionalFields{Description: true, Links: true, CVSS: true, Class: true, PackageType: true, PkgPath: true, Target: true}, + }, + { + name: "some additional fields are set", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.additionalVulnerabilityReportFields": "PackageType,Target,Links,CVSS", + }, + }}, + additionalFields: AdditionalFields{Links: true, CVSS: true, PackageType: true, Target: true}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + addFields := tc.configData.GetAdditionalVulnerabilityReportFields() + assert.True(t, addFields.Description == tc.additionalFields.Description) + assert.True(t, addFields.CVSS == tc.additionalFields.CVSS) + assert.True(t, addFields.Target == tc.additionalFields.Target) + assert.True(t, addFields.PackageType == tc.additionalFields.PackageType) + assert.True(t, addFields.Class == tc.additionalFields.Class) + assert.True(t, addFields.Links == tc.additionalFields.Links) + }) + } +} + +func TestConfig_GetMode(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedError string + expectedMode Mode + }{ + { + name: "Should return Standalone", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.mode": string(Standalone), + }, + }}, + expectedMode: Standalone, + }, + { + name: "Should return ClientServer", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.mode": string(ClientServer), + }, + }}, + expectedMode: ClientServer, + }, + { + name: "Should return error when value is not set", + configData: Config{PluginConfig: trivyoperator.PluginConfig{}}, + expectedError: "property trivy.mode not set", + }, + { + name: "Should return error when value is not allowed", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.mode": "P2P", + }, + }}, + expectedError: "invalid value (P2P) of trivy.mode; allowed values (Standalone, ClientServer)", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mode, err := tc.configData.GetMode() + if tc.expectedError != "" { + require.EqualError(t, err, tc.expectedError) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expectedMode, mode) + } + }) + } +} + +func TestGetSlow(t *testing.T) { + testCases := []struct { + name string + configData Config + want bool + }{ + { + name: "slow param set to true", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.slow": "true", + }, + }}, + want: true, + }, + { + name: "slow param set to false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.slow": "false", + }, + }}, + want: false, + }, + { + name: "slow param set to no valid value", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.slow": "false2", + }, + }}, + want: true, + }, + { + name: "slow param set to no value", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{}, + }}, + want: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := tc.configData.GetSlow() + assert.Equal(t, got, tc.want) + + }) + } +} +func TestConfig_GetCommand(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedError string + expectedCommand Command + }{ + { + name: "Should return image", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.command": "image", + }, + }}, + expectedCommand: Image, + }, + { + name: "Should return image when value is not set", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{}, + }}, + expectedCommand: Image, + }, + { + name: "Should return filesystem", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.command": "filesystem", + }, + }}, + expectedCommand: Filesystem, + }, + { + name: "Should return rootfs", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.command": "rootfs", + }, + }}, + expectedCommand: Rootfs, + }, + { + name: "Should return error when value is not allowed", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.command": "ls", + }, + }}, + expectedError: "invalid value (ls) of trivy.command; allowed values (image, filesystem, rootfs)", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + command, err := tc.configData.GetCommand() + if tc.expectedError != "" { + require.EqualError(t, err, tc.expectedError) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expectedCommand, command) + } + }) + } +} + +func TestVulnType(t *testing.T) { + testCases := []struct { + name string + configData Config + want string + }{ + { + name: "valid vuln type os", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.vulnType": "os", + }, + }}, + want: "os", + }, + { + name: "valid vuln type library", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.vulnType": "library", + }, + }}, + want: "library", + }, + { + name: "empty vuln type", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.vulnType": "", + }, + }}, + want: "", + }, + { + name: "non valid vuln type", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.vulnType": "aaa", + }, + }}, + want: "", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := tc.configData.GetVulnType() + assert.Equal(t, got, tc.want) + + }) + } +} + +func TestConfig_GetResourceRequirements(t *testing.T) { + testCases := []struct { + name string + config Config + expectedError string + expectedRequirements corev1.ResourceRequirements + }{ + { + name: "Should return empty requirements by default", + config: Config{ + PluginConfig: trivyoperator.PluginConfig{}, + }, + expectedError: "", + expectedRequirements: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{}, + Limits: corev1.ResourceList{}, + }, + }, + { + name: "Should return configured resource requirement", + config: Config{ + PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.dbRepository": DefaultDBRepository, + "trivy.javaDbRepository": DefaultJavaDBRepository, + "trivy.resources.requests.cpu": "800m", + "trivy.resources.requests.memory": "200M", + "trivy.resources.limits.cpu": "600m", + "trivy.resources.limits.memory": "700M", + }, + }, + }, + expectedError: "", + expectedRequirements: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("800m"), + corev1.ResourceMemory: resource.MustParse("200M"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("600m"), + corev1.ResourceMemory: resource.MustParse("700M"), + }, + }, + }, + { + name: "Should return error if resource is not parseable", + config: Config{ + PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.resources.requests.cpu": "roughly 100", + }, + }, + }, + expectedError: "parsing resource definition trivy.resources.requests.cpu: roughly 100 quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resourceRequirement, err := tc.config.GetResourceRequirements() + if tc.expectedError != "" { + require.EqualError(t, err, tc.expectedError) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expectedRequirements, resourceRequirement, tc.name) + } + }) + } +} + +func TestConfig_IgnoreFileExists(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedOutput bool + }{ + { + name: "Should return false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + }, + }}, + expectedOutput: false, + }, + { + name: "Should return true", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + "trivy.ignoreFile": `# Accept the risk +CVE-2018-14618 + +# No impact in our settings +CVE-2019-1543`, + }, + }}, + expectedOutput: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + exists := tc.configData.IgnoreFileExists() + assert.Equal(t, tc.expectedOutput, exists) + }) + } +} + +func TestConfig_IgnoreUnfixed(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedOutput bool + }{ + { + name: "Should return false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + }, + }}, + expectedOutput: false, + }, + { + name: "Should return true", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + "trivy.ignoreUnfixed": "true", + }, + }}, + expectedOutput: true, + }, + { + name: "Should return false when set it as false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + "trivy.ignoreUnfixed": "false", + }, + }}, + expectedOutput: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + exists := tc.configData.IgnoreUnfixed() + assert.Equal(t, tc.expectedOutput, exists) + }) + } +} + +func TestConfig_OfflineScan(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedOutput bool + }{ + { + name: "Should return false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + }, + }}, + expectedOutput: false, + }, + { + name: "Should return true", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + "trivy.offlineScan": "true", + }, + }}, + expectedOutput: true, + }, + { + name: "Should return false when set it as false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + "trivy.offlineScan": "false", + }, + }}, + expectedOutput: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + exists := tc.configData.OfflineScan() + assert.Equal(t, tc.expectedOutput, exists) + }) + } +} + +func TestConfig_dbRepositoryInsecure(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedOutput bool + }{ + { + name: "good value Should return false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.dbRepositoryInsecure": "false", + }, + }}, + expectedOutput: false, + }, + { + name: "good value Should return true", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.dbRepositoryInsecure": "true", + }, + }}, + expectedOutput: true, + }, + { + name: "bad value Should return false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.dbRepositoryInsecure": "true1", + }, + }}, + expectedOutput: false, + }, + { + name: "no value Should return false", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{}, + }}, + expectedOutput: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + exists := tc.configData.GetDBRepositoryInsecure() + assert.Equal(t, tc.expectedOutput, exists) + }) + } +} + +func TestConfig_GetInsecureRegistries(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedOutput map[string]bool + }{ + { + name: "Should return nil map when there is no key with insecureRegistry. prefix", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + }, + }}, + expectedOutput: make(map[string]bool), + }, + { + name: "Should return insecure registries in map", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + "trivy.insecureRegistry.pocRegistry": "poc.myregistry.harbor.com.pl", + "trivy.insecureRegistry.qaRegistry": "qa.registry.aquasec.com", + }, + }}, + expectedOutput: map[string]bool{ + "poc.myregistry.harbor.com.pl": true, + "qa.registry.aquasec.com": true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + insecureRegistries := tc.configData.GetInsecureRegistries() + assert.Equal(t, tc.expectedOutput, insecureRegistries) + }) + } +} + +func TestConfig_GetNonSSLRegistries(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedOutput map[string]bool + }{ + { + name: "Should return nil map when there is no key with nonSslRegistry. prefix", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + }, + }}, + expectedOutput: make(map[string]bool), + }, + { + name: "Should return insecure registries in map", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + "trivy.nonSslRegistry.pocRegistry": "poc.myregistry.harbor.com.pl", + "trivy.nonSslRegistry.qaRegistry": "qa.registry.aquasec.com", + }, + }}, + expectedOutput: map[string]bool{ + "poc.myregistry.harbor.com.pl": true, + "qa.registry.aquasec.com": true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + nonSslRegistries := tc.configData.GetNonSSLRegistries() + assert.Equal(t, tc.expectedOutput, nonSslRegistries) + }) + } +} + +func TestConfig_GetMirrors(t *testing.T) { + testCases := []struct { + name string + configData Config + expectedOutput map[string]string + }{ + { + name: "Should return empty map when there is no key with mirrors.registry. prefix", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "foo": "bar", + }, + }}, + expectedOutput: make(map[string]string), + }, + { + name: "Should return mirrors in a map", + configData: Config{PluginConfig: trivyoperator.PluginConfig{ + Data: map[string]string{ + "trivy.registry.mirror.docker.io": "mirror.io", + }, + }}, + expectedOutput: map[string]string{"docker.io": "mirror.io"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expectedOutput, tc.configData.GetMirrors()) + }) + } +} + +func TestPlugin_Init(t *testing.T) { + + t.Run("Should create the default config", func(t *testing.T) { + testClient := fake.NewClientBuilder().WithObjects().Build() + or := kube.NewObjectResolver(testClient, &kube.CompatibleObjectMapper{}) + + pluginContext := trivyoperator.NewPluginContext(). + WithName(Plugin). + WithNamespace("trivyoperator-ns"). + WithServiceAccountName("trivyoperator-sa"). + WithClient(testClient). + Get() + p := NewPlugin(fixedClock, ext.NewSimpleIDGenerator(), &or) + err := p.Init(pluginContext) + assert.NoError(t, err) + var cm corev1.ConfigMap + err = testClient.Get(context.Background(), types.NamespacedName{ + Namespace: "trivyoperator-ns", + Name: "trivy-operator-trivy-config", + }, &cm) + require.NoError(t, err) + assert.Equal(t, corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "trivy-operator-trivy-config", + Namespace: "trivyoperator-ns", + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "trivyoperator", + }, + ResourceVersion: "1", + }, + Data: map[string]string{ + "trivy.repository": DefaultImageRepository, + "trivy.tag": "0.45.1", + "trivy.severity": DefaultSeverity, + "trivy.slow": "true", + "trivy.mode": string(Standalone), + "trivy.timeout": "5m0s", + "trivy.dbRepository": DefaultDBRepository, + "trivy.javaDbRepository": DefaultJavaDBRepository, + "trivy.useBuiltinRegoPolicies": "true", + "trivy.supportedConfigAuditKinds": SupportedConfigAuditKinds, + "trivy.resources.requests.cpu": "100m", + "trivy.resources.requests.memory": "100M", + "trivy.resources.limits.cpu": "500m", + "trivy.resources.limits.memory": "500M", + }, + }, cm) + }) + + t.Run("Should not overwrite existing config", func(t *testing.T) { + testClient := fake.NewClientBuilder().WithObjects( + &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "trivy-operator-trivy-config", + Namespace: "trivyoperator-ns", + ResourceVersion: "1", + }, + Data: map[string]string{ + "trivy.repository": "gcr.io/aquasecurity/trivy", + "trivy.tag": "0.35.0", + "trivy.severity": DefaultSeverity, + "trivy.mode": string(Standalone), + }, + }).Build() + resolver := kube.NewObjectResolver(testClient, &kube.CompatibleObjectMapper{}) + + pluginContext := trivyoperator.NewPluginContext(). + WithName(Plugin). + WithNamespace("trivyoperator-ns"). + WithServiceAccountName("trivyoperator-sa"). + WithClient(testClient). + Get() + + p := NewPlugin(fixedClock, ext.NewSimpleIDGenerator(), &resolver) + err := p.Init(pluginContext) + assert.NoError(t, err) + var cm corev1.ConfigMap + err = testClient.Get(context.Background(), types.NamespacedName{ + Namespace: "trivyoperator-ns", + Name: "trivy-operator-trivy-config", + }, &cm) + require.NoError(t, err) + assert.Equal(t, corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "trivy-operator-trivy-config", + Namespace: "trivyoperator-ns", + ResourceVersion: "1", + }, + Data: map[string]string{ + "trivy.repository": "gcr.io/aquasecurity/trivy", + "trivy.tag": "0.35.0", + "trivy.severity": DefaultSeverity, + "trivy.mode": string(Standalone), + }, + }, cm) + }) +} diff --git a/pkg/plugins/trivy/filesystem.go b/pkg/plugins/trivy/filesystem.go new file mode 100644 index 000000000..071d1d425 --- /dev/null +++ b/pkg/plugins/trivy/filesystem.go @@ -0,0 +1,467 @@ +package trivy + +import ( + "context" + "fmt" + "net/url" + + "github.com/aquasecurity/trivy-operator/pkg/docker" + "github.com/aquasecurity/trivy-operator/pkg/kube" + "github.com/aquasecurity/trivy-operator/pkg/trivyoperator" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type FileSystemJobSpecMgr struct { + getPodSpecFunc GetPodSpecFunc +} + +func NewFileSystemJobSpecMgr() PodSpecMgr { + return &FileSystemJobSpecMgr{} +} + +func (j *FileSystemJobSpecMgr) GetPodSpec(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) { + return j.getPodSpecFunc(ctx, config, workload, credentials, securityContext, p) +} + +// FileSystem scan option with standalone mode. +// The only difference is that instead of scanning the resource by name, +// We are scanning the resource place on a specific file system location using the following command. +// +// trivy --quiet fs --format json --ignore-unfixed file/system/location +func GetPodSpecForStandaloneFSMode(ctx trivyoperator.PluginContext, config Config, workload client.Object, _ map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) { + var secrets []*corev1.Secret + spec, err := kube.GetPodSpec(workload) + if err != nil { + return corev1.PodSpec{}, nil, err + } + pullPolicy := corev1.PullIfNotPresent + // nodeName to schedule scan job explicitly on specific node. + var nodeName string + if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { + // get nodeName from running pods. + nodeName, err = p.objectResolver.GetNodeName(context.Background(), workload) + if err != nil { + return corev1.PodSpec{}, nil, fmt.Errorf("failed resolving node name for workload %q: %w", + workload.GetNamespace()+"/"+workload.GetName(), err) + } + pullPolicy = corev1.PullNever + } + + trivyImageRef, err := config.GetImageRef() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) + + dbRepository, err := config.GetDBRepository() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + requirements, err := config.GetResourceRequirements() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + volumeMounts := []corev1.VolumeMount{ + { + Name: FsSharedVolumeName, + ReadOnly: false, + MountPath: "/var/trivyoperator", + }, + { + Name: tmpVolumeName, + MountPath: "/tmp", + ReadOnly: false, + }, + } + + initContainerCopyBinary := corev1.Container{ + Name: p.idGenerator.GenerateID(), + Image: trivyImageRef, + ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Command: []string{ + "cp", + "-v", + "/usr/local/bin/trivy", + SharedVolumeLocationOfTrivy, + }, + Resources: requirements, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + } + + initContainerDB := corev1.Container{ + Name: p.idGenerator.GenerateID(), + Image: trivyImageRef, + ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Env: p.initContainerFSEnvVar(trivyConfigName, config), + Command: []string{ + "trivy", + }, + Args: []string{ + "--cache-dir", + "/var/trivyoperator/trivy-db", + "image", + "--download-db-only", + "--db-repository", + dbRepository, + }, + Resources: requirements, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + } + + var containers []corev1.Container + + volumes := []corev1.Volume{ + { + Name: FsSharedVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumDefault, + }, + }, + }, + { + Name: tmpVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumDefault, + }, + }, + }, + } + + volumeMounts = append(volumeMounts, getScanResultVolumeMount()) + volumes = append(volumes, getScanResultVolume()) + + if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + + for _, c := range getContainers(spec) { + env := []corev1.EnvVar{ + constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), + ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), + ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), + constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), + constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), + constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), + constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), + constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), + } + if len(config.GetSslCertDir()) > 0 { + env = append(env, corev1.EnvVar{ + Name: "SSL_CERT_DIR", + Value: SslCertDir, + }) + } + if config.IgnoreFileExists() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNOREFILE", + Value: ignoreFileMountPath, + }) + } + if config.FindIgnorePolicyKey(workload) != "" { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNORE_POLICY", + Value: ignorePolicyMountPath, + }) + } + if config.IgnoreUnfixed() { + env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", + trivyConfigName, keyTrivyIgnoreUnfixed)) + } + if config.GetDBRepositoryInsecure() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_INSECURE", + Value: "true", + }) + } + + if config.OfflineScan() { + env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", + trivyConfigName, keyTrivyOfflineScan)) + } + + env, err = p.appendTrivyInsecureEnv(config, c.Image, env) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + resourceRequirements, err := config.GetResourceRequirements() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + config, err := p.newConfigFrom(ctx) + if err != nil { + return corev1.PodSpec{}, nil, err + } + command, err := config.GetCommand() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + containers = append(containers, corev1.Container{ + Name: c.Name, + Image: c.Image, + ImagePullPolicy: pullPolicy, + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Env: env, + Command: []string{ + SharedVolumeLocationOfTrivy, + }, + Args: p.getFSScanningArgs(ctx, command, Standalone, ""), + Resources: resourceRequirements, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + }) + } + + podSpec := corev1.PodSpec{ + Affinity: trivyoperator.LinuxNodeAffinity(), + RestartPolicy: corev1.RestartPolicyNever, + ServiceAccountName: ctx.GetServiceAccountName(), + AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), + Volumes: volumes, + InitContainers: []corev1.Container{initContainerCopyBinary, initContainerDB}, + Containers: containers, + SecurityContext: &corev1.PodSecurityContext{}, + } + + if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { + // schedule scan job explicitly on specific node. + podSpec.NodeName = nodeName + } + + return podSpec, secrets, nil +} + +// FileSystem scan option with ClientServer mode. +// The only difference is that instead of scanning the resource by name, +// We scanning the resource place on a specific file system location using the following command. +// +// trivy --quiet fs --server TRIVY_SERVER --format json --ignore-unfixed file/system/location +func GetPodSpecForClientServerFSMode(ctx trivyoperator.PluginContext, config Config, workload client.Object, _ map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) { + var secrets []*corev1.Secret + spec, err := kube.GetPodSpec(workload) + if err != nil { + return corev1.PodSpec{}, nil, err + } + pullPolicy := corev1.PullIfNotPresent + // nodeName to schedule scan job explicitly on specific node. + var nodeName string + if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { + // get nodeName from running pods. + nodeName, err = p.objectResolver.GetNodeName(context.Background(), workload) + if err != nil { + return corev1.PodSpec{}, nil, fmt.Errorf("failed resolving node name for workload %q: %w", + workload.GetNamespace()+"/"+workload.GetName(), err) + } + pullPolicy = corev1.PullNever + } + + trivyImageRef, err := config.GetImageRef() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + trivyServerURL, err := config.GetServerURL() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + encodedTrivyServerURL, err := url.Parse(trivyServerURL) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) + + requirements, err := config.GetResourceRequirements() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + volumeMounts := []corev1.VolumeMount{ + { + Name: FsSharedVolumeName, + ReadOnly: false, + MountPath: "/var/trivyoperator", + }, + { + Name: tmpVolumeName, + MountPath: "/tmp", + ReadOnly: false, + }, + } + + initContainerCopyBinary := corev1.Container{ + Name: p.idGenerator.GenerateID(), + Image: trivyImageRef, + ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Command: []string{ + "cp", + "-v", + "/usr/local/bin/trivy", + SharedVolumeLocationOfTrivy, + }, + Resources: requirements, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + } + + var containers []corev1.Container + + volumes := []corev1.Volume{ + { + Name: FsSharedVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumDefault, + }, + }, + }, + { + Name: tmpVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumDefault, + }, + }, + }, + } + volumeMounts = append(volumeMounts, getScanResultVolumeMount()) + volumes = append(volumes, getScanResultVolume()) + + if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + + for _, c := range getContainers(spec) { + env := []corev1.EnvVar{ + constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), + ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), + ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), + constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), + constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), + constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), + constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), + constructEnvVarSourceFromConfigMap("TRIVY_TOKEN_HEADER", trivyConfigName, keyTrivyServerTokenHeader), + constructEnvVarSourceFromSecret("TRIVY_TOKEN", trivyConfigName, keyTrivyServerToken), + constructEnvVarSourceFromSecret("TRIVY_CUSTOM_HEADERS", trivyConfigName, keyTrivyServerCustomHeaders), + constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), + } + if len(config.GetSslCertDir()) > 0 { + env = append(env, corev1.EnvVar{ + Name: "SSL_CERT_DIR", + Value: SslCertDir, + }) + } + if config.IgnoreFileExists() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNOREFILE", + Value: ignoreFileMountPath, + }) + } + if config.FindIgnorePolicyKey(workload) != "" { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNORE_POLICY", + Value: ignorePolicyMountPath, + }) + } + if config.IgnoreUnfixed() { + env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", + trivyConfigName, keyTrivyIgnoreUnfixed)) + } + + if config.OfflineScan() { + env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", + trivyConfigName, keyTrivyOfflineScan)) + } + + env, err = p.appendTrivyInsecureEnv(config, c.Image, env) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + if config.GetServerInsecure() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_INSECURE", + Value: "true", + }) + } + + resourceRequirements, err := config.GetResourceRequirements() + if err != nil { + return corev1.PodSpec{}, nil, err + } + config, err := p.newConfigFrom(ctx) + if err != nil { + return corev1.PodSpec{}, nil, err + } + command, err := config.GetCommand() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + containers = append(containers, corev1.Container{ + Name: c.Name, + Image: c.Image, + ImagePullPolicy: pullPolicy, + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Env: env, + Command: []string{ + SharedVolumeLocationOfTrivy, + }, + Args: p.getFSScanningArgs(ctx, command, ClientServer, encodedTrivyServerURL.String()), + Resources: resourceRequirements, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + }) + } + + podSpec := corev1.PodSpec{ + Affinity: trivyoperator.LinuxNodeAffinity(), + RestartPolicy: corev1.RestartPolicyNever, + ServiceAccountName: ctx.GetServiceAccountName(), + AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), + Volumes: volumes, + InitContainers: []corev1.Container{initContainerCopyBinary}, + Containers: containers, + SecurityContext: &corev1.PodSecurityContext{}, + } + + if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { + // schedule scan job explicitly on specific node. + podSpec.NodeName = nodeName + } + + return podSpec, secrets, nil +} diff --git a/pkg/plugins/trivy/image.go b/pkg/plugins/trivy/image.go new file mode 100644 index 000000000..372ed71a0 --- /dev/null +++ b/pkg/plugins/trivy/image.go @@ -0,0 +1,483 @@ +package trivy + +import ( + "fmt" + "net/url" + + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" + + "github.com/aquasecurity/trivy-operator/pkg/docker" + "github.com/aquasecurity/trivy-operator/pkg/kube" + "github.com/aquasecurity/trivy-operator/pkg/trivyoperator" + containerimage "github.com/google/go-containerregistry/pkg/name" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ImageJobSpecMgr struct { + getPodSpecFunc GetPodSpecFunc +} + +func NewImageJobSpecMgr() PodSpecMgr { + return &ImageJobSpecMgr{} +} + +func (j *ImageJobSpecMgr) GetPodSpec(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) { + return j.getPodSpecFunc(ctx, config, workload, credentials, securityContext, p) +} + +// In the Standalone mode there is the init container responsible for +// downloading the latest Trivy DB file from GitHub and storing it to the +// emptyDir volume shared with main containers. In other words, the init +// container runs the following Trivy command: +// +// trivy --cache-dir /tmp/trivy/.cache image --download-db-only +// +// The number of main containers correspond to the number of containers +// defined for the scanned workload. Each container runs the Trivy image scan +// command and skips the database download: +// +// trivy --cache-dir /tmp/trivy/.cache image --skip-update \ +// --format json +func GetPodSpecForStandaloneMode(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) { + var secret *corev1.Secret + var secrets []*corev1.Secret + var containersSpec []corev1.Container + + spec, err := kube.GetPodSpec(workload) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + for _, c := range getContainers(spec) { + optionalMirroredImage, err := GetMirroredImage(c.Image, config.GetMirrors()) + if err != nil { + return corev1.PodSpec{}, nil, err + } + c.Image = optionalMirroredImage + containersSpec = append(containersSpec, c) + } + + containerImages := kube.GetContainerImagesFromContainersList(containersSpec) + containersCredentials, err := kube.MapContainerNamesToDockerAuths(containerImages, credentials) + if err != nil { + return corev1.PodSpec{}, nil, err + } + if len(containersCredentials) > 0 { + secret = p.newSecretWithAggregateImagePullCredentials(workload, containerImages, containersCredentials) + secrets = append(secrets, secret) + } + + trivyImageRef, err := config.GetImageRef() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) + + dbRepository, err := config.GetDBRepository() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + requirements, err := config.GetResourceRequirements() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + initContainer := corev1.Container{ + Name: p.idGenerator.GenerateID(), + Image: trivyImageRef, + ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Env: p.initContainerEnvVar(trivyConfigName, config), + Command: []string{ + "trivy", + }, + Args: []string{ + "--cache-dir", + "/tmp/trivy/.cache", + "image", + "--download-db-only", + "--db-repository", + dbRepository, + }, + Resources: requirements, + SecurityContext: securityContext, + VolumeMounts: []corev1.VolumeMount{ + { + Name: tmpVolumeName, + MountPath: "/tmp", + ReadOnly: false, + }, + }, + } + + var containers []corev1.Container + + volumeMounts := []corev1.VolumeMount{ + { + Name: tmpVolumeName, + ReadOnly: false, + MountPath: "/tmp", + }, + } + volumes := []corev1.Volume{ + { + Name: tmpVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumDefault, + }, + }, + }, + } + volumeMounts = append(volumeMounts, getScanResultVolumeMount()) + volumes = append(volumes, getScanResultVolume()) + + if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + + for _, c := range containersSpec { + env := []corev1.EnvVar{ + constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), + constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", trivyConfigName, keyTrivyIgnoreUnfixed), + constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", trivyConfigName, keyTrivyOfflineScan), + constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), + constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), + ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), + ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), + constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), + constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), + constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), + } + + if len(config.GetSslCertDir()) > 0 { + env = append(env, corev1.EnvVar{ + Name: "SSL_CERT_DIR", + Value: SslCertDir, + }) + } + if config.IgnoreFileExists() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNOREFILE", + Value: ignoreFileMountPath, + }) + } + if config.FindIgnorePolicyKey(workload) != "" { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNORE_POLICY", + Value: ignorePolicyMountPath, + }) + } + + region := CheckAwsEcrPrivateRegistry(c.Image) + if region != "" { + env = append(env, corev1.EnvVar{ + Name: "AWS_REGION", + Value: region, + }) + } + if config.GetDBRepositoryInsecure() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_INSECURE", + Value: "true", + }) + } + gcrImage := checkGcpCrOrPivateRegistry(c.Image) + if _, ok := containersCredentials[c.Name]; ok && secret != nil { + registryUsernameKey := fmt.Sprintf("%s.username", c.Name) + registryPasswordKey := fmt.Sprintf("%s.password", c.Name) + secretName := secret.Name + if gcrImage { + createEnvandVolumeForGcr(&env, &volumeMounts, &volumes, ®istryPasswordKey, &secretName) + } else { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secret.Name, + }, + Key: registryUsernameKey, + }, + }, + }, corev1.EnvVar{ + Name: "TRIVY_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secret.Name, + }, + Key: registryPasswordKey, + }, + }, + }) + } + + } + + env, err = p.appendTrivyInsecureEnv(config, c.Image, env) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + env, err = p.appendTrivyNonSSLEnv(config, c.Image, env) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + resourceRequirements, err := config.GetResourceRequirements() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + imageRef, err := containerimage.ParseReference(c.Image) + if err != nil { + return corev1.PodSpec{}, nil, err + } + resultFileName := getUniqueScanResultFileName(c.Name) + cmd, args := p.getCommandAndArgs(ctx, Standalone, imageRef.String(), "", resultFileName) + containers = append(containers, corev1.Container{ + Name: c.Name, + Image: trivyImageRef, + ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Env: env, + Command: cmd, + Args: args, + Resources: resourceRequirements, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + }) + } + + return corev1.PodSpec{ + Affinity: trivyoperator.LinuxNodeAffinity(), + RestartPolicy: corev1.RestartPolicyNever, + ServiceAccountName: ctx.GetServiceAccountName(), + AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), + Volumes: volumes, + InitContainers: []corev1.Container{initContainer}, + Containers: containers, + SecurityContext: &corev1.PodSecurityContext{}, + }, secrets, nil +} + +// In the ClientServer mode the number of containers of the pod created by the +// scan job equals the number of containers defined for the scanned workload. +// Each container runs Trivy image scan command and refers to Trivy server URL +// returned by Config.GetServerURL: +// +// trivy image --server \ +// --format json +func GetPodSpecForClientServerMode(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) { + var secret *corev1.Secret + var secrets []*corev1.Secret + var containersSpec []corev1.Container + spec, err := kube.GetPodSpec(workload) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + trivyImageRef, err := config.GetImageRef() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + trivyServerURL, err := config.GetServerURL() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + for _, c := range getContainers(spec) { + optionalMirroredImage, err := GetMirroredImage(c.Image, config.GetMirrors()) + if err != nil { + return corev1.PodSpec{}, nil, err + } + c.Image = optionalMirroredImage + containersSpec = append(containersSpec, c) + } + + containerImages := kube.GetContainerImagesFromContainersList(containersSpec) + containersCredentials, err := kube.MapContainerNamesToDockerAuths(containerImages, credentials) + if err != nil { + return corev1.PodSpec{}, nil, err + } + if len(containersCredentials) > 0 { + secret = p.newSecretWithAggregateImagePullCredentials(workload, containerImages, containersCredentials) + secrets = append(secrets, secret) + } + + var containers []corev1.Container + + trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) + // add tmp volume mount + volumeMounts := []corev1.VolumeMount{ + { + Name: tmpVolumeName, + ReadOnly: false, + MountPath: "/tmp", + }, + } + + // add tmp volume + volumes := []corev1.Volume{ + { + Name: tmpVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumDefault, + }, + }, + }, + } + + volumeMounts = append(volumeMounts, getScanResultVolumeMount()) + volumes = append(volumes, getScanResultVolume()) + + if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + + if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { + volumes = append(volumes, *volume) + volumeMounts = append(volumeMounts, *volumeMount) + } + + for _, container := range containersSpec { + env := []corev1.EnvVar{ + constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), + constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), + constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), + constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), + constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", trivyConfigName, keyTrivyIgnoreUnfixed), + constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", trivyConfigName, keyTrivyOfflineScan), + constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), + constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), + ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), + ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), + constructEnvVarSourceFromConfigMap("TRIVY_TOKEN_HEADER", trivyConfigName, keyTrivyServerTokenHeader), + constructEnvVarSourceFromSecret("TRIVY_TOKEN", trivyConfigName, keyTrivyServerToken), + constructEnvVarSourceFromSecret("TRIVY_CUSTOM_HEADERS", trivyConfigName, keyTrivyServerCustomHeaders), + } + if len(config.GetSslCertDir()) > 0 { + env = append(env, corev1.EnvVar{ + Name: "SSL_CERT_DIR", + Value: SslCertDir, + }) + } + if config.IgnoreFileExists() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNOREFILE", + Value: ignoreFileMountPath, + }) + } + if config.FindIgnorePolicyKey(workload) != "" { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_IGNORE_POLICY", + Value: ignorePolicyMountPath, + }) + } + + if auth, ok := containersCredentials[container.Name]; ok && secret != nil { + if checkGcpCrOrPivateRegistry(container.Image) && auth.Username == "_json_key" { + registryServiceAccountAuthKey := fmt.Sprintf("%s.password", container.Name) + createEnvandVolumeForGcr(&env, &volumeMounts, &volumes, ®istryServiceAccountAuthKey, &secret.Name) + } else { + registryUsernameKey := fmt.Sprintf("%s.username", container.Name) + registryPasswordKey := fmt.Sprintf("%s.password", container.Name) + env = append(env, corev1.EnvVar{ + Name: "TRIVY_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secret.Name, + }, + Key: registryUsernameKey, + }, + }, + }, corev1.EnvVar{ + Name: "TRIVY_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secret.Name, + }, + Key: registryPasswordKey, + }, + }, + }) + } + } + + env, err = p.appendTrivyInsecureEnv(config, container.Image, env) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + env, err = p.appendTrivyNonSSLEnv(config, container.Image, env) + if err != nil { + return corev1.PodSpec{}, nil, err + } + + if config.GetServerInsecure() { + env = append(env, corev1.EnvVar{ + Name: "TRIVY_INSECURE", + Value: "true", + }) + } + + requirements, err := config.GetResourceRequirements() + if err != nil { + return corev1.PodSpec{}, nil, err + } + + encodedTrivyServerURL, err := url.Parse(trivyServerURL) + if err != nil { + return corev1.PodSpec{}, nil, err + } + imageRef, err := containerimage.ParseReference(container.Image) + if err != nil { + return corev1.PodSpec{}, nil, err + } + resultFileName := getUniqueScanResultFileName(container.Name) + cmd, args := p.getCommandAndArgs(ctx, ClientServer, imageRef.String(), encodedTrivyServerURL.String(), resultFileName) + containers = append(containers, corev1.Container{ + Name: container.Name, + Image: trivyImageRef, + ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + Env: env, + Command: cmd, + Args: args, + Resources: requirements, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + }) + } + + return corev1.PodSpec{ + Affinity: trivyoperator.LinuxNodeAffinity(), + RestartPolicy: corev1.RestartPolicyNever, + ServiceAccountName: ctx.GetServiceAccountName(), + AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), + Containers: containers, + Volumes: volumes, + }, secrets, nil +} diff --git a/pkg/plugins/trivy/jobspec.go b/pkg/plugins/trivy/jobspec.go new file mode 100644 index 000000000..2f80e5b3d --- /dev/null +++ b/pkg/plugins/trivy/jobspec.go @@ -0,0 +1,62 @@ +package trivy + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + + "github.com/aquasecurity/trivy-operator/pkg/docker" + "github.com/aquasecurity/trivy-operator/pkg/trivyoperator" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type GetPodSpecFunc func(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) + +type PodSpecMgr interface { + GetPodSpec(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext, p *plugin) (corev1.PodSpec, []*corev1.Secret, error) +} + +func NewPodSpecMgr(ctx trivyoperator.PluginContext) (PodSpecMgr, error) { + pluginConfig, err := ctx.GetConfig() + if err != nil { + return nil, err + } + config := Config{PluginConfig: pluginConfig} + + mode, err := config.GetMode() + if err != nil { + return nil, err + } + command, err := config.GetCommand() + if err != nil { + return nil, err + } + + if command == Image { + switch mode { + case Standalone: + return &ImageJobSpecMgr{ + getPodSpecFunc: GetPodSpecForStandaloneMode, + }, nil + case ClientServer: + return &ImageJobSpecMgr{ + getPodSpecFunc: GetPodSpecForClientServerMode, + }, nil + default: + } + } + + if command == Filesystem || command == Rootfs { + switch mode { + case Standalone: + return &ImageJobSpecMgr{ + getPodSpecFunc: GetPodSpecForStandaloneFSMode, + }, nil + case ClientServer: + return &ImageJobSpecMgr{ + getPodSpecFunc: GetPodSpecForClientServerFSMode, + }, nil + } + } + return nil, fmt.Errorf("unrecognized trivy mode %q for command %q", mode, command) +} diff --git a/pkg/plugins/trivy/plugin.go b/pkg/plugins/trivy/plugin.go index fa2eec328..bb469b036 100644 --- a/pkg/plugins/trivy/plugin.go +++ b/pkg/plugins/trivy/plugin.go @@ -1,14 +1,12 @@ package trivy import ( - "context" "encoding/json" "fmt" "io" - "net/url" - "path/filepath" + "regexp" - "strconv" + "strings" "time" @@ -29,7 +27,7 @@ import ( "github.com/aquasecurity/trivy-operator/pkg/trivyoperator" "github.com/aquasecurity/trivy-operator/pkg/vulnerabilityreport" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" @@ -50,59 +48,6 @@ const ( SkipFilesAnnotation = "trivy-operator.aquasecurity.github.io/skip-files" ) -const ( - keyTrivyImageRepository = "trivy.repository" - keyTrivyImageTag = "trivy.tag" - //nolint:gosec - keyTrivyImagePullSecret = "trivy.imagePullSecret" - keyTrivyImagePullPolicy = "trivy.imagePullPolicy" - keyTrivyMode = "trivy.mode" - keyTrivyAdditionalVulnerabilityReportFields = "trivy.additionalVulnerabilityReportFields" - keyTrivyCommand = "trivy.command" - KeyTrivySeverity = "trivy.severity" - keyTrivySlow = "trivy.slow" - keyTrivyVulnType = "trivy.vulnType" - keyTrivyIgnoreUnfixed = "trivy.ignoreUnfixed" - keyTrivyOfflineScan = "trivy.offlineScan" - keyTrivyTimeout = "trivy.timeout" - keyTrivyIgnoreFile = "trivy.ignoreFile" - keyTrivyIgnorePolicy = "trivy.ignorePolicy" - keyTrivyInsecureRegistryPrefix = "trivy.insecureRegistry." - keyTrivyNonSslRegistryPrefix = "trivy.nonSslRegistry." - keyTrivyMirrorPrefix = "trivy.registry.mirror." - keyTrivyHTTPProxy = "trivy.httpProxy" - keyTrivyHTTPSProxy = "trivy.httpsProxy" - keyTrivyNoProxy = "trivy.noProxy" - keyTrivySslCertDir = "trivy.sslCertDir" - // nolint:gosec // This is not a secret, but a configuration value. - keyTrivyGitHubToken = "trivy.githubToken" - keyTrivySkipFiles = "trivy.skipFiles" - keyTrivySkipDirs = "trivy.skipDirs" - keyTrivyDBRepository = "trivy.dbRepository" - keyTrivyJavaDBRepository = "trivy.javaDbRepository" - keyTrivyDBRepositoryInsecure = "trivy.dbRepositoryInsecure" - - keyTrivyUseBuiltinRegoPolicies = "trivy.useBuiltinRegoPolicies" - keyTrivySupportedConfigAuditKinds = "trivy.supportedConfigAuditKinds" - - keyTrivyServerURL = "trivy.serverURL" - keyTrivyClientServerSkipUpdate = "trivy.clientServerSkipUpdate" - keyTrivySkipJavaDBUpdate = "trivy.skipJavaDBUpdate" - // nolint:gosec // This is not a secret, but a configuration value. - keyTrivyServerTokenHeader = "trivy.serverTokenHeader" - keyTrivyServerInsecure = "trivy.serverInsecure" - // nolint:gosec // This is not a secret, but a configuration value. - keyTrivyServerToken = "trivy.serverToken" - keyTrivyServerCustomHeaders = "trivy.serverCustomHeaders" - - keyResourcesRequestsCPU = "trivy.resources.requests.cpu" - keyResourcesRequestsMemory = "trivy.resources.requests.memory" - keyResourcesLimitsCPU = "trivy.resources.limits.cpu" - keyResourcesLimitsMemory = "trivy.resources.limits.memory" - keyResourcesRequestEphemeralStorage = "trivy.resources.requests.ephemeral-storage" - keyResourcesLimitEphemeralStorage = "trivy.resources.limits.ephemeral-storage" -) - const ( DefaultImageRepository = "ghcr.io/aquasecurity/trivy" DefaultDBRepository = "ghcr.io/aquasecurity/trivy-db" @@ -137,421 +82,6 @@ type AdditionalFields struct { PkgPath bool } -// Config defines configuration params for this plugin. -type Config struct { - trivyoperator.PluginConfig -} - -func (c Config) GetAdditionalVulnerabilityReportFields() AdditionalFields { - addFields := AdditionalFields{} - - fields, ok := c.Data[keyTrivyAdditionalVulnerabilityReportFields] - if !ok { - return addFields - } - for _, field := range strings.Split(fields, ",") { - switch strings.TrimSpace(field) { - case "Description": - addFields.Description = true - case "Links": - addFields.Links = true - case "CVSS": - addFields.CVSS = true - case "Target": - addFields.Target = true - case "Class": - addFields.Class = true - case "PackageType": - addFields.PackageType = true - case "PackagePath": - addFields.PkgPath = true - } - } - return addFields -} - -// GetImageRef returns upstream Trivy container image reference. -func (c Config) GetImageRef() (string, error) { - repository, err := c.GetRequiredData(keyTrivyImageRepository) - if err != nil { - return "", err - } - tag, err := c.GetImageTag() - if err != nil { - return "", err - } - - return fmt.Sprintf("%s:%s", repository, tag), nil -} - -// GetImageTag returns upstream Trivy container image tag. -func (c Config) GetImageTag() (string, error) { - tag, err := c.GetRequiredData(keyTrivyImageTag) - if err != nil { - return "", err - } - return tag, nil -} - -func (c Config) GetImagePullSecret() []corev1.LocalObjectReference { - ips, ok := c.Data[keyTrivyImagePullSecret] - if !ok { - return []corev1.LocalObjectReference{} - } - return []corev1.LocalObjectReference{{Name: ips}} -} - -func (c Config) GetImagePullPolicy() string { - ipp, ok := c.Data[keyTrivyImagePullPolicy] - if !ok { - return "IfNotPresent" - } - return ipp -} - -func (c Config) GetMode() (Mode, error) { - var ok bool - var value string - if value, ok = c.Data[keyTrivyMode]; !ok { - return "", fmt.Errorf("property %s not set", keyTrivyMode) - } - - switch Mode(value) { - case Standalone: - return Standalone, nil - case ClientServer: - return ClientServer, nil - } - - return "", fmt.Errorf("invalid value (%s) of %s; allowed values (%s, %s)", - value, keyTrivyMode, Standalone, ClientServer) -} - -func (c Config) GetCommand() (Command, error) { - var ok bool - var value string - if value, ok = c.Data[keyTrivyCommand]; !ok { - // for backward compatibility, fallback to ImageScan - return Image, nil - } - switch Command(value) { - case Image: - return Image, nil - case Filesystem: - return Filesystem, nil - case Rootfs: - return Rootfs, nil - } - return "", fmt.Errorf("invalid value (%s) of %s; allowed values (%s, %s, %s)", - value, keyTrivyCommand, Image, Filesystem, Rootfs) -} - -func (c Config) GetServerURL() (string, error) { - return c.GetRequiredData(keyTrivyServerURL) -} - -func (c Config) GetClientServerSkipUpdate() bool { - val, ok := c.Data[keyTrivyClientServerSkipUpdate] - if !ok { - return false - } - boolVal, err := strconv.ParseBool(val) - if err != nil { - return false - } - return boolVal -} - -func (c Config) GetSkipJavaDBUpdate() bool { - val, ok := c.Data[keyTrivySkipJavaDBUpdate] - if !ok { - return false - } - boolVal, err := strconv.ParseBool(val) - if err != nil { - return false - } - return boolVal -} - -func (c Config) GetServerInsecure() bool { - _, ok := c.Data[keyTrivyServerInsecure] - return ok -} - -func (c Config) GetDBRepositoryInsecure() bool { - val, ok := c.Data[keyTrivyDBRepositoryInsecure] - if !ok { - return false - } - boolVal, _ := strconv.ParseBool(val) - return boolVal -} -func (c Config) GetUseBuiltinRegoPolicies() bool { - val, ok := c.Data[keyTrivyUseBuiltinRegoPolicies] - if !ok { - return true - } - boolVal, err := strconv.ParseBool(val) - if err != nil { - return true - } - return boolVal -} -func (c Config) GetSslCertDir() string { - val, ok := c.Data[keyTrivySslCertDir] - if !ok { - return "" - } - return val -} - -func (c Config) GetSeverity() string { - val, ok := c.Data[KeyTrivySeverity] - if !ok { - return "" - } - return val -} - -func (c Config) GetSlow() bool { - val, ok := c.Data[keyTrivySlow] - if !ok { - return true - } - boolVal, err := strconv.ParseBool(val) - if err != nil { - return true - } - return boolVal -} - -func (c Config) GetVulnType() string { - val, ok := c.Data[keyTrivyVulnType] - if !ok { - return "" - } - trimmedVulnType := strings.TrimSpace(val) - if !(trimmedVulnType == "os" || trimmedVulnType == "library") { - return "" - } - return trimmedVulnType -} - -func (c Config) GetSupportedConfigAuditKinds() []string { - val, ok := c.Data[keyTrivySupportedConfigAuditKinds] - if !ok { - return utils.MapKinds(strings.Split(SupportedConfigAuditKinds, ",")) - } - return utils.MapKinds(strings.Split(val, ",")) -} - -func (c Config) IgnoreFileExists() bool { - _, ok := c.Data[keyTrivyIgnoreFile] - return ok -} - -func (c Config) FindIgnorePolicyKey(workload client.Object) string { - keysByPrecedence := []string{ - keyTrivyIgnorePolicy + "." + workload.GetNamespace() + "." + workload.GetName(), - keyTrivyIgnorePolicy + "." + workload.GetNamespace(), - keyTrivyIgnorePolicy, - } - for _, key := range keysByPrecedence { - for key2 := range c.Data { - if key2 == keyTrivyIgnorePolicy || strings.HasPrefix(key2, keyTrivyIgnorePolicy) { - tempKey := key2 - if key2 != keyTrivyIgnorePolicy { - // replace dot with astrix for regex matching - tempKey = fmt.Sprintf("%s%s", keyTrivyIgnorePolicy, strings.ReplaceAll(tempKey[len(keyTrivyIgnorePolicy):], ".", "*")) - } - matched, err := filepath.Match(tempKey, key) - if err == nil && matched { - return key2 - } - } - } - } - return "" -} - -func (c Config) GenerateIgnoreFileVolumeIfAvailable(trivyConfigName string) (*corev1.Volume, *corev1.VolumeMount) { - if !c.IgnoreFileExists() { - return nil, nil - } - volume := corev1.Volume{ - Name: ignoreFileVolumeName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: trivyConfigName, - }, - Items: []corev1.KeyToPath{ - { - Key: keyTrivyIgnoreFile, - Path: ignoreFileName, - }, - }, - }, - }, - } - volumeMount := corev1.VolumeMount{ - Name: ignoreFileVolumeName, - MountPath: ignoreFileMountPath, - SubPath: ignoreFileName, - } - return &volume, &volumeMount -} - -func (c Config) GenerateSslCertDirVolumeIfAvailable(trivyConfigName string) (*corev1.Volume, *corev1.VolumeMount) { - var sslCertDirHost string - if sslCertDirHost = c.GetSslCertDir(); len(sslCertDirHost) == 0 { - return nil, nil - } - volume := corev1.Volume{ - Name: sslCertDirVolumeName, - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: sslCertDirHost, - }, - }, - } - volumeMount := corev1.VolumeMount{ - Name: sslCertDirVolumeName, - MountPath: SslCertDir, - ReadOnly: true, - } - return &volume, &volumeMount -} - -func (c Config) GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName string, workload client.Object) (*corev1.Volume, *corev1.VolumeMount) { - ignorePolicyKey := c.FindIgnorePolicyKey(workload) - if ignorePolicyKey == "" { - return nil, nil - } - volume := corev1.Volume{ - Name: ignorePolicyVolumeName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: trivyConfigName, - }, - Items: []corev1.KeyToPath{ - { - Key: c.FindIgnorePolicyKey(workload), - Path: ignorePolicyName, - }, - }, - }, - }, - } - volumeMounts := corev1.VolumeMount{ - Name: ignorePolicyVolumeName, - MountPath: ignorePolicyMountPath, - SubPath: ignorePolicyName, - } - return &volume, &volumeMounts -} - -func (c Config) IgnoreUnfixed() bool { - _, ok := c.Data[keyTrivyIgnoreUnfixed] - return ok -} - -func (c Config) OfflineScan() bool { - _, ok := c.Data[keyTrivyOfflineScan] - return ok -} - -func (c Config) GetInsecureRegistries() map[string]bool { - insecureRegistries := make(map[string]bool) - for key, val := range c.Data { - if strings.HasPrefix(key, keyTrivyInsecureRegistryPrefix) { - insecureRegistries[val] = true - } - } - - return insecureRegistries -} - -func (c Config) GetNonSSLRegistries() map[string]bool { - nonSSLRegistries := make(map[string]bool) - for key, val := range c.Data { - if strings.HasPrefix(key, keyTrivyNonSslRegistryPrefix) { - nonSSLRegistries[val] = true - } - } - - return nonSSLRegistries -} - -func (c Config) GetMirrors() map[string]string { - res := make(map[string]string) - for registryKey, mirror := range c.Data { - if !strings.HasPrefix(registryKey, keyTrivyMirrorPrefix) { - continue - } - res[strings.TrimPrefix(registryKey, keyTrivyMirrorPrefix)] = mirror - } - return res -} - -// GetResourceRequirements creates ResourceRequirements from the Config. -func (c Config) GetResourceRequirements() (corev1.ResourceRequirements, error) { - requirements := corev1.ResourceRequirements{ - Requests: corev1.ResourceList{}, - Limits: corev1.ResourceList{}, - } - - err := c.setResourceLimit(keyResourcesRequestsCPU, &requirements.Requests, corev1.ResourceCPU) - if err != nil { - return requirements, err - } - - err = c.setResourceLimit(keyResourcesRequestsMemory, &requirements.Requests, corev1.ResourceMemory) - if err != nil { - return requirements, err - } - - err = c.setResourceLimit(keyResourcesRequestEphemeralStorage, &requirements.Requests, corev1.ResourceEphemeralStorage) - if err != nil { - return requirements, err - } - - err = c.setResourceLimit(keyResourcesLimitsCPU, &requirements.Limits, corev1.ResourceCPU) - if err != nil { - return requirements, err - } - - err = c.setResourceLimit(keyResourcesLimitsMemory, &requirements.Limits, corev1.ResourceMemory) - if err != nil { - return requirements, err - } - - err = c.setResourceLimit(keyResourcesLimitEphemeralStorage, &requirements.Limits, corev1.ResourceEphemeralStorage) - if err != nil { - return requirements, err - } - - return requirements, nil -} - -func (c Config) setResourceLimit(configKey string, k8sResourceList *corev1.ResourceList, k8sResourceName corev1.ResourceName) error { - if value, found := c.Data[configKey]; found { - quantity, err := resource.ParseQuantity(value) - if err != nil { - return fmt.Errorf("parsing resource definition %s: %s %w", configKey, value, err) - } - - (*k8sResourceList)[k8sResourceName] = quantity - } - return nil -} - -func (c Config) GetDBRepository() (string, error) { - return c.GetRequiredData(keyTrivyDBRepository) -} - type plugin struct { clock ext.Clock idGenerator ext.IDGenerator @@ -570,11 +100,12 @@ type plugin struct { // more performant, however it requires a Trivy server accessible at the // configurable Config.GetServerURL. func NewPlugin(clock ext.Clock, idGenerator ext.IDGenerator, objectResolver *kube.ObjectResolver) vulnerabilityreport.Plugin { - return &plugin{ + plugin := &plugin{ clock: clock, idGenerator: idGenerator, objectResolver: objectResolver, } + return plugin } // NewTrivyConfigAuditPlugin constructs a new configAudit.Plugin, which is using an @@ -614,38 +145,14 @@ func (p *plugin) GetScanJobSpec(ctx trivyoperator.PluginContext, workload client if err != nil { return corev1.PodSpec{}, nil, err } - - mode, err := config.GetMode() - if err != nil { - return corev1.PodSpec{}, nil, err - } - command, err := config.GetCommand() + var podSpec corev1.PodSpec + var secrets []*corev1.Secret + psm, err := NewPodSpecMgr(ctx) if err != nil { return corev1.PodSpec{}, nil, err } + podSpec, secrets, err = psm.GetPodSpec(ctx, config, workload, credentials, securityContext, p) - var podSpec corev1.PodSpec - var secrets []*corev1.Secret - if command == Image { - switch mode { - case Standalone: - podSpec, secrets, err = p.getPodSpecForStandaloneMode(ctx, config, workload, credentials, securityContext) - case ClientServer: - podSpec, secrets, err = p.getPodSpecForClientServerMode(ctx, config, workload, credentials, securityContext) - default: - return corev1.PodSpec{}, nil, fmt.Errorf("unrecognized trivy mode %q for command %q", mode, command) - } - } - if command == Filesystem || command == Rootfs { - switch mode { - case Standalone: - podSpec, secrets, err = p.getPodSpecForStandaloneFSMode(ctx, command, config, workload, securityContext) - case ClientServer: - podSpec, secrets, err = p.getPodSpecForClientServerFSMode(ctx, command, config, workload, securityContext) - default: - return corev1.PodSpec{}, nil, fmt.Errorf("unrecognized trivy mode %q for command %q", mode, command) - } - } // add image pull secret to be used when pulling trivy image fom private registry podSpec.ImagePullSecrets = config.GetImagePullSecret() return podSpec, secrets, err @@ -677,254 +184,6 @@ const ( SslCertDir = "/var/ssl-cert" ) -// In the Standalone mode there is the init container responsible for -// downloading the latest Trivy DB file from GitHub and storing it to the -// emptyDir volume shared with main containers. In other words, the init -// container runs the following Trivy command: -// -// trivy --cache-dir /tmp/trivy/.cache image --download-db-only -// -// The number of main containers correspond to the number of containers -// defined for the scanned workload. Each container runs the Trivy image scan -// command and skips the database download: -// -// trivy --cache-dir /tmp/trivy/.cache image --skip-update \ -// --format json -func (p *plugin) getPodSpecForStandaloneMode(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext) (corev1.PodSpec, []*corev1.Secret, error) { - var secret *corev1.Secret - var secrets []*corev1.Secret - var containersSpec []corev1.Container - - spec, err := kube.GetPodSpec(workload) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - for _, c := range getContainers(spec) { - optionalMirroredImage, err := GetMirroredImage(c.Image, config.GetMirrors()) - if err != nil { - return corev1.PodSpec{}, nil, err - } - c.Image = optionalMirroredImage - containersSpec = append(containersSpec, c) - } - - containerImages := kube.GetContainerImagesFromContainersList(containersSpec) - containersCredentials, err := kube.MapContainerNamesToDockerAuths(containerImages, credentials) - if err != nil { - return corev1.PodSpec{}, nil, err - } - if len(containersCredentials) > 0 { - secret = p.newSecretWithAggregateImagePullCredentials(workload, containerImages, containersCredentials) - secrets = append(secrets, secret) - } - - trivyImageRef, err := config.GetImageRef() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) - - dbRepository, err := config.GetDBRepository() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - requirements, err := config.GetResourceRequirements() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - initContainer := corev1.Container{ - Name: p.idGenerator.GenerateID(), - Image: trivyImageRef, - ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Env: p.initContainerEnvVar(trivyConfigName, config), - Command: []string{ - "trivy", - }, - Args: []string{ - "--cache-dir", - "/tmp/trivy/.cache", - "image", - "--download-db-only", - "--db-repository", - dbRepository, - }, - Resources: requirements, - SecurityContext: securityContext, - VolumeMounts: []corev1.VolumeMount{ - { - Name: tmpVolumeName, - MountPath: "/tmp", - ReadOnly: false, - }, - }, - } - - var containers []corev1.Container - - volumeMounts := []corev1.VolumeMount{ - { - Name: tmpVolumeName, - ReadOnly: false, - MountPath: "/tmp", - }, - } - volumes := []corev1.Volume{ - { - Name: tmpVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumDefault, - }, - }, - }, - } - volumeMounts = append(volumeMounts, getScanResultVolumeMount()) - volumes = append(volumes, getScanResultVolume()) - - if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - - for _, c := range containersSpec { - env := []corev1.EnvVar{ - constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), - constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", trivyConfigName, keyTrivyIgnoreUnfixed), - constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", trivyConfigName, keyTrivyOfflineScan), - constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), - constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), - ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), - ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), - constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), - constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), - constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), - } - - if len(config.GetSslCertDir()) > 0 { - env = append(env, corev1.EnvVar{ - Name: "SSL_CERT_DIR", - Value: SslCertDir, - }) - } - if config.IgnoreFileExists() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNOREFILE", - Value: ignoreFileMountPath, - }) - } - if config.FindIgnorePolicyKey(workload) != "" { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNORE_POLICY", - Value: ignorePolicyMountPath, - }) - } - - region := CheckAwsEcrPrivateRegistry(c.Image) - if region != "" { - env = append(env, corev1.EnvVar{ - Name: "AWS_REGION", - Value: region, - }) - } - if config.GetDBRepositoryInsecure() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_INSECURE", - Value: "true", - }) - } - gcrImage := checkGcpCrOrPivateRegistry(c.Image) - if _, ok := containersCredentials[c.Name]; ok && secret != nil { - registryUsernameKey := fmt.Sprintf("%s.username", c.Name) - registryPasswordKey := fmt.Sprintf("%s.password", c.Name) - secretName := secret.Name - if gcrImage { - createEnvandVolumeForGcr(&env, &volumeMounts, &volumes, ®istryPasswordKey, &secretName) - } else { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_USERNAME", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: secret.Name, - }, - Key: registryUsernameKey, - }, - }, - }, corev1.EnvVar{ - Name: "TRIVY_PASSWORD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: secret.Name, - }, - Key: registryPasswordKey, - }, - }, - }) - } - - } - - env, err = p.appendTrivyInsecureEnv(config, c.Image, env) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - env, err = p.appendTrivyNonSSLEnv(config, c.Image, env) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - resourceRequirements, err := config.GetResourceRequirements() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - imageRef, err := containerimage.ParseReference(c.Image) - if err != nil { - return corev1.PodSpec{}, nil, err - } - resultFileName := getUniqueScanResultFileName(c.Name) - cmd, args := p.getCommandAndArgs(ctx, Standalone, imageRef.String(), "", resultFileName) - containers = append(containers, corev1.Container{ - Name: c.Name, - Image: trivyImageRef, - ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Env: env, - Command: cmd, - Args: args, - Resources: resourceRequirements, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - }) - } - - return corev1.PodSpec{ - Affinity: trivyoperator.LinuxNodeAffinity(), - RestartPolicy: corev1.RestartPolicyNever, - ServiceAccountName: ctx.GetServiceAccountName(), - AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), - Volumes: volumes, - InitContainers: []corev1.Container{initContainer}, - Containers: containers, - SecurityContext: &corev1.PodSecurityContext{}, - }, secrets, nil -} - func checkGcpCrOrPivateRegistry(imageUrl string) bool { imageRegex := regexp.MustCompile(GCPCR_Inage_Regex) return imageRegex.MatchString(imageUrl) @@ -947,214 +206,6 @@ func (p *plugin) initContainerEnvVar(trivyConfigName string, config Config) []co return envs } -// In the ClientServer mode the number of containers of the pod created by the -// scan job equals the number of containers defined for the scanned workload. -// Each container runs Trivy image scan command and refers to Trivy server URL -// returned by Config.GetServerURL: -// -// trivy image --server \ -// --format json -func (p *plugin) getPodSpecForClientServerMode(ctx trivyoperator.PluginContext, config Config, workload client.Object, credentials map[string]docker.Auth, securityContext *corev1.SecurityContext) (corev1.PodSpec, []*corev1.Secret, error) { - var secret *corev1.Secret - var secrets []*corev1.Secret - var containersSpec []corev1.Container - spec, err := kube.GetPodSpec(workload) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - trivyImageRef, err := config.GetImageRef() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - trivyServerURL, err := config.GetServerURL() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - for _, c := range getContainers(spec) { - optionalMirroredImage, err := GetMirroredImage(c.Image, config.GetMirrors()) - if err != nil { - return corev1.PodSpec{}, nil, err - } - c.Image = optionalMirroredImage - containersSpec = append(containersSpec, c) - } - - containerImages := kube.GetContainerImagesFromContainersList(containersSpec) - containersCredentials, err := kube.MapContainerNamesToDockerAuths(containerImages, credentials) - if err != nil { - return corev1.PodSpec{}, nil, err - } - if len(containersCredentials) > 0 { - secret = p.newSecretWithAggregateImagePullCredentials(workload, containerImages, containersCredentials) - secrets = append(secrets, secret) - } - - var containers []corev1.Container - - trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) - // add tmp volume mount - volumeMounts := []corev1.VolumeMount{ - { - Name: tmpVolumeName, - ReadOnly: false, - MountPath: "/tmp", - }, - } - - // add tmp volume - volumes := []corev1.Volume{ - { - Name: tmpVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumDefault, - }, - }, - }, - } - - volumeMounts = append(volumeMounts, getScanResultVolumeMount()) - volumes = append(volumes, getScanResultVolume()) - - if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - - if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - - for _, container := range containersSpec { - env := []corev1.EnvVar{ - constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), - constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), - constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), - constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), - constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", trivyConfigName, keyTrivyIgnoreUnfixed), - constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", trivyConfigName, keyTrivyOfflineScan), - constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), - constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), - ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), - ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), - constructEnvVarSourceFromConfigMap("TRIVY_TOKEN_HEADER", trivyConfigName, keyTrivyServerTokenHeader), - constructEnvVarSourceFromSecret("TRIVY_TOKEN", trivyConfigName, keyTrivyServerToken), - constructEnvVarSourceFromSecret("TRIVY_CUSTOM_HEADERS", trivyConfigName, keyTrivyServerCustomHeaders), - } - if len(config.GetSslCertDir()) > 0 { - env = append(env, corev1.EnvVar{ - Name: "SSL_CERT_DIR", - Value: SslCertDir, - }) - } - if config.IgnoreFileExists() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNOREFILE", - Value: ignoreFileMountPath, - }) - } - if config.FindIgnorePolicyKey(workload) != "" { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNORE_POLICY", - Value: ignorePolicyMountPath, - }) - } - - if auth, ok := containersCredentials[container.Name]; ok && secret != nil { - if checkGcpCrOrPivateRegistry(container.Image) && auth.Username == "_json_key" { - registryServiceAccountAuthKey := fmt.Sprintf("%s.password", container.Name) - createEnvandVolumeForGcr(&env, &volumeMounts, &volumes, ®istryServiceAccountAuthKey, &secret.Name) - } else { - registryUsernameKey := fmt.Sprintf("%s.username", container.Name) - registryPasswordKey := fmt.Sprintf("%s.password", container.Name) - env = append(env, corev1.EnvVar{ - Name: "TRIVY_USERNAME", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: secret.Name, - }, - Key: registryUsernameKey, - }, - }, - }, corev1.EnvVar{ - Name: "TRIVY_PASSWORD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: secret.Name, - }, - Key: registryPasswordKey, - }, - }, - }) - } - } - - env, err = p.appendTrivyInsecureEnv(config, container.Image, env) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - env, err = p.appendTrivyNonSSLEnv(config, container.Image, env) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - if config.GetServerInsecure() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_INSECURE", - Value: "true", - }) - } - - requirements, err := config.GetResourceRequirements() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - encodedTrivyServerURL, err := url.Parse(trivyServerURL) - if err != nil { - return corev1.PodSpec{}, nil, err - } - imageRef, err := containerimage.ParseReference(container.Image) - if err != nil { - return corev1.PodSpec{}, nil, err - } - resultFileName := getUniqueScanResultFileName(container.Name) - cmd, args := p.getCommandAndArgs(ctx, ClientServer, imageRef.String(), encodedTrivyServerURL.String(), resultFileName) - containers = append(containers, corev1.Container{ - Name: container.Name, - Image: trivyImageRef, - ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Env: env, - Command: cmd, - Args: args, - Resources: requirements, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - }) - } - - return corev1.PodSpec{ - Affinity: trivyoperator.LinuxNodeAffinity(), - RestartPolicy: corev1.RestartPolicyNever, - ServiceAccountName: ctx.GetServiceAccountName(), - AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), - Containers: containers, - Volumes: volumes, - }, secrets, nil -} - func (p *plugin) getCommandAndArgs(ctx trivyoperator.PluginContext, mode Mode, imageRef string, trivyServerURL string, resultFileName string) ([]string, []string) { command := []string{ "trivy", @@ -1336,430 +387,6 @@ func createEnvandVolumeForGcr(env *[]corev1.EnvVar, volumeMounts *[]corev1.Volum *volumeMounts = append(*volumeMounts, googlecredMount) } -// FileSystem scan option with standalone mode. -// The only difference is that instead of scanning the resource by name, -// We are scanning the resource place on a specific file system location using the following command. -// -// trivy --quiet fs --format json --ignore-unfixed file/system/location -func (p *plugin) getPodSpecForStandaloneFSMode(ctx trivyoperator.PluginContext, command Command, config Config, - workload client.Object, securityContext *corev1.SecurityContext) (corev1.PodSpec, []*corev1.Secret, error) { - var secrets []*corev1.Secret - spec, err := kube.GetPodSpec(workload) - if err != nil { - return corev1.PodSpec{}, nil, err - } - pullPolicy := corev1.PullIfNotPresent - // nodeName to schedule scan job explicitly on specific node. - var nodeName string - if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { - // get nodeName from running pods. - nodeName, err = p.objectResolver.GetNodeName(context.Background(), workload) - if err != nil { - return corev1.PodSpec{}, nil, fmt.Errorf("failed resolving node name for workload %q: %w", - workload.GetNamespace()+"/"+workload.GetName(), err) - } - pullPolicy = corev1.PullNever - } - - trivyImageRef, err := config.GetImageRef() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) - - dbRepository, err := config.GetDBRepository() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - requirements, err := config.GetResourceRequirements() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - volumeMounts := []corev1.VolumeMount{ - { - Name: FsSharedVolumeName, - ReadOnly: false, - MountPath: "/var/trivyoperator", - }, - { - Name: tmpVolumeName, - MountPath: "/tmp", - ReadOnly: false, - }, - } - - initContainerCopyBinary := corev1.Container{ - Name: p.idGenerator.GenerateID(), - Image: trivyImageRef, - ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Command: []string{ - "cp", - "-v", - "/usr/local/bin/trivy", - SharedVolumeLocationOfTrivy, - }, - Resources: requirements, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - } - - initContainerDB := corev1.Container{ - Name: p.idGenerator.GenerateID(), - Image: trivyImageRef, - ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Env: p.initContainerFSEnvVar(trivyConfigName, config), - Command: []string{ - "trivy", - }, - Args: []string{ - "--cache-dir", - "/var/trivyoperator/trivy-db", - "image", - "--download-db-only", - "--db-repository", - dbRepository, - }, - Resources: requirements, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - } - - var containers []corev1.Container - - volumes := []corev1.Volume{ - { - Name: FsSharedVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumDefault, - }, - }, - }, - { - Name: tmpVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumDefault, - }, - }, - }, - } - - volumeMounts = append(volumeMounts, getScanResultVolumeMount()) - volumes = append(volumes, getScanResultVolume()) - - if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - - for _, c := range getContainers(spec) { - env := []corev1.EnvVar{ - constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), - ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), - ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), - constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), - constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), - constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), - constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), - constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), - } - if len(config.GetSslCertDir()) > 0 { - env = append(env, corev1.EnvVar{ - Name: "SSL_CERT_DIR", - Value: SslCertDir, - }) - } - if config.IgnoreFileExists() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNOREFILE", - Value: ignoreFileMountPath, - }) - } - if config.FindIgnorePolicyKey(workload) != "" { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNORE_POLICY", - Value: ignorePolicyMountPath, - }) - } - if config.IgnoreUnfixed() { - env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", - trivyConfigName, keyTrivyIgnoreUnfixed)) - } - if config.GetDBRepositoryInsecure() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_INSECURE", - Value: "true", - }) - } - - if config.OfflineScan() { - env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", - trivyConfigName, keyTrivyOfflineScan)) - } - - env, err = p.appendTrivyInsecureEnv(config, c.Image, env) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - resourceRequirements, err := config.GetResourceRequirements() - if err != nil { - return corev1.PodSpec{}, nil, err - } - containers = append(containers, corev1.Container{ - Name: c.Name, - Image: c.Image, - ImagePullPolicy: pullPolicy, - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Env: env, - Command: []string{ - SharedVolumeLocationOfTrivy, - }, - Args: p.getFSScanningArgs(ctx, command, Standalone, ""), - Resources: resourceRequirements, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - }) - } - - podSpec := corev1.PodSpec{ - Affinity: trivyoperator.LinuxNodeAffinity(), - RestartPolicy: corev1.RestartPolicyNever, - ServiceAccountName: ctx.GetServiceAccountName(), - AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), - Volumes: volumes, - InitContainers: []corev1.Container{initContainerCopyBinary, initContainerDB}, - Containers: containers, - SecurityContext: &corev1.PodSecurityContext{}, - } - - if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { - // schedule scan job explicitly on specific node. - podSpec.NodeName = nodeName - } - - return podSpec, secrets, nil -} - -// FileSystem scan option with ClientServer mode. -// The only difference is that instead of scanning the resource by name, -// We scanning the resource place on a specific file system location using the following command. -// -// trivy --quiet fs --server TRIVY_SERVER --format json --ignore-unfixed file/system/location -func (p *plugin) getPodSpecForClientServerFSMode(ctx trivyoperator.PluginContext, command Command, config Config, - workload client.Object, securityContext *corev1.SecurityContext) (corev1.PodSpec, []*corev1.Secret, error) { - var secrets []*corev1.Secret - spec, err := kube.GetPodSpec(workload) - if err != nil { - return corev1.PodSpec{}, nil, err - } - pullPolicy := corev1.PullIfNotPresent - // nodeName to schedule scan job explicitly on specific node. - var nodeName string - if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { - // get nodeName from running pods. - nodeName, err = p.objectResolver.GetNodeName(context.Background(), workload) - if err != nil { - return corev1.PodSpec{}, nil, fmt.Errorf("failed resolving node name for workload %q: %w", - workload.GetNamespace()+"/"+workload.GetName(), err) - } - pullPolicy = corev1.PullNever - } - - trivyImageRef, err := config.GetImageRef() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - trivyServerURL, err := config.GetServerURL() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - encodedTrivyServerURL, err := url.Parse(trivyServerURL) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - trivyConfigName := trivyoperator.GetPluginConfigMapName(Plugin) - - requirements, err := config.GetResourceRequirements() - if err != nil { - return corev1.PodSpec{}, nil, err - } - - volumeMounts := []corev1.VolumeMount{ - { - Name: FsSharedVolumeName, - ReadOnly: false, - MountPath: "/var/trivyoperator", - }, - { - Name: tmpVolumeName, - MountPath: "/tmp", - ReadOnly: false, - }, - } - - initContainerCopyBinary := corev1.Container{ - Name: p.idGenerator.GenerateID(), - Image: trivyImageRef, - ImagePullPolicy: corev1.PullPolicy(config.GetImagePullPolicy()), - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Command: []string{ - "cp", - "-v", - "/usr/local/bin/trivy", - SharedVolumeLocationOfTrivy, - }, - Resources: requirements, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - } - - var containers []corev1.Container - - volumes := []corev1.Volume{ - { - Name: FsSharedVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumDefault, - }, - }, - }, - { - Name: tmpVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumDefault, - }, - }, - }, - } - volumeMounts = append(volumeMounts, getScanResultVolumeMount()) - volumes = append(volumes, getScanResultVolume()) - - if volume, volumeMount := config.GenerateIgnoreFileVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - if volume, volumeMount := config.GenerateIgnorePolicyVolumeIfAvailable(trivyConfigName, workload); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - if volume, volumeMount := config.GenerateSslCertDirVolumeIfAvailable(trivyConfigName); volume != nil && volumeMount != nil { - volumes = append(volumes, *volume) - volumeMounts = append(volumeMounts, *volumeMount) - } - - for _, c := range getContainers(spec) { - env := []corev1.EnvVar{ - constructEnvVarSourceFromConfigMap("TRIVY_SEVERITY", trivyConfigName, KeyTrivySeverity), - ConfigWorkloadAnnotationEnvVars(workload, SkipFilesAnnotation, "TRIVY_SKIP_FILES", trivyConfigName, keyTrivySkipFiles), - ConfigWorkloadAnnotationEnvVars(workload, SkipDirsAnnotation, "TRIVY_SKIP_DIRS", trivyConfigName, keyTrivySkipDirs), - constructEnvVarSourceFromConfigMap("HTTP_PROXY", trivyConfigName, keyTrivyHTTPProxy), - constructEnvVarSourceFromConfigMap("TRIVY_TIMEOUT", trivyConfigName, keyTrivyTimeout), - constructEnvVarSourceFromConfigMap("HTTPS_PROXY", trivyConfigName, keyTrivyHTTPSProxy), - constructEnvVarSourceFromConfigMap("NO_PROXY", trivyConfigName, keyTrivyNoProxy), - constructEnvVarSourceFromConfigMap("TRIVY_TOKEN_HEADER", trivyConfigName, keyTrivyServerTokenHeader), - constructEnvVarSourceFromSecret("TRIVY_TOKEN", trivyConfigName, keyTrivyServerToken), - constructEnvVarSourceFromSecret("TRIVY_CUSTOM_HEADERS", trivyConfigName, keyTrivyServerCustomHeaders), - constructEnvVarSourceFromConfigMap("TRIVY_JAVA_DB_REPOSITORY", trivyConfigName, keyTrivyJavaDBRepository), - } - if len(config.GetSslCertDir()) > 0 { - env = append(env, corev1.EnvVar{ - Name: "SSL_CERT_DIR", - Value: SslCertDir, - }) - } - if config.IgnoreFileExists() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNOREFILE", - Value: ignoreFileMountPath, - }) - } - if config.FindIgnorePolicyKey(workload) != "" { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_IGNORE_POLICY", - Value: ignorePolicyMountPath, - }) - } - if config.IgnoreUnfixed() { - env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_IGNORE_UNFIXED", - trivyConfigName, keyTrivyIgnoreUnfixed)) - } - - if config.OfflineScan() { - env = append(env, constructEnvVarSourceFromConfigMap("TRIVY_OFFLINE_SCAN", - trivyConfigName, keyTrivyOfflineScan)) - } - - env, err = p.appendTrivyInsecureEnv(config, c.Image, env) - if err != nil { - return corev1.PodSpec{}, nil, err - } - - if config.GetServerInsecure() { - env = append(env, corev1.EnvVar{ - Name: "TRIVY_INSECURE", - Value: "true", - }) - } - - resourceRequirements, err := config.GetResourceRequirements() - if err != nil { - return corev1.PodSpec{}, nil, err - } - containers = append(containers, corev1.Container{ - Name: c.Name, - Image: c.Image, - ImagePullPolicy: pullPolicy, - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - Env: env, - Command: []string{ - SharedVolumeLocationOfTrivy, - }, - Args: p.getFSScanningArgs(ctx, command, ClientServer, encodedTrivyServerURL.String()), - Resources: resourceRequirements, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - }) - } - - podSpec := corev1.PodSpec{ - Affinity: trivyoperator.LinuxNodeAffinity(), - RestartPolicy: corev1.RestartPolicyNever, - ServiceAccountName: ctx.GetServiceAccountName(), - AutomountServiceAccountToken: pointer.Bool(getAutomountServiceAccountToken(ctx)), - Volumes: volumes, - InitContainers: []corev1.Container{initContainerCopyBinary}, - Containers: containers, - SecurityContext: &corev1.PodSecurityContext{}, - } - - if !ctx.GetTrivyOperatorConfig().VulnerabilityScanJobsInSameNamespace() { - // schedule scan job explicitly on specific node. - podSpec.NodeName = nodeName - } - - return podSpec, secrets, nil -} - func (p *plugin) getFSScanningArgs(ctx trivyoperator.PluginContext, command Command, mode Mode, trivyServerURL string) []string { c, err := p.getConfig(ctx) if err != nil { diff --git a/pkg/plugins/trivy/plugin_test.go b/pkg/plugins/trivy/plugin_test.go index 6296c5c4d..147a648e2 100644 --- a/pkg/plugins/trivy/plugin_test.go +++ b/pkg/plugins/trivy/plugin_test.go @@ -2,7 +2,7 @@ package trivy_test import ( "bytes" - "context" + "encoding/base64" "errors" "fmt" @@ -32,7 +32,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -43,790 +43,6 @@ var ( fixedClock = ext.NewFixedClock(fixedTime) ) -func TestConfig_GetImageRef(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedError string - expectedImageRef string - }{ - { - name: "Should return error", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{}}, - expectedError: "property trivy.repository not set", - }, - { - name: "Should return error", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.tag": "0.8.0", - }, - }}, - expectedError: "property trivy.repository not set", - }, - { - name: "Should return error", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.repository": "gcr.io/aquasecurity/trivy", - }, - }}, - expectedError: "property trivy.tag not set", - }, - { - name: "Should return image reference from config data", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.repository": "gcr.io/aquasecurity/trivy", - "trivy.tag": "0.8.0", - }, - }}, - expectedImageRef: "gcr.io/aquasecurity/trivy:0.8.0", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - imageRef, err := tc.configData.GetImageRef() - if tc.expectedError != "" { - require.EqualError(t, err, tc.expectedError) - } else { - require.NoError(t, err) - assert.Equal(t, tc.expectedImageRef, imageRef) - } - }) - } -} - -func TestConfig_GetAdditionalVulnerabilityReportFields(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - additionalFields trivy.AdditionalFields - }{ - { - name: "no additional fields are set", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{}}, - additionalFields: trivy.AdditionalFields{}, - }, - { - name: "all additional fields are set", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.additionalVulnerabilityReportFields": "PackageType,PkgPath,Class,Target,Links,Description,CVSS", - }, - }}, - additionalFields: trivy.AdditionalFields{Description: true, Links: true, CVSS: true, Class: true, PackageType: true, PkgPath: true, Target: true}, - }, - { - name: "some additional fields are set", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.additionalVulnerabilityReportFields": "PackageType,Target,Links,CVSS", - }, - }}, - additionalFields: trivy.AdditionalFields{Links: true, CVSS: true, PackageType: true, Target: true}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - addFields := tc.configData.GetAdditionalVulnerabilityReportFields() - assert.True(t, addFields.Description == tc.additionalFields.Description) - assert.True(t, addFields.CVSS == tc.additionalFields.CVSS) - assert.True(t, addFields.Target == tc.additionalFields.Target) - assert.True(t, addFields.PackageType == tc.additionalFields.PackageType) - assert.True(t, addFields.Class == tc.additionalFields.Class) - assert.True(t, addFields.Links == tc.additionalFields.Links) - }) - } -} - -func TestConfig_GetMode(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedError string - expectedMode trivy.Mode - }{ - { - name: "Should return Standalone", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.mode": string(trivy.Standalone), - }, - }}, - expectedMode: trivy.Standalone, - }, - { - name: "Should return ClientServer", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.mode": string(trivy.ClientServer), - }, - }}, - expectedMode: trivy.ClientServer, - }, - { - name: "Should return error when value is not set", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{}}, - expectedError: "property trivy.mode not set", - }, - { - name: "Should return error when value is not allowed", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.mode": "P2P", - }, - }}, - expectedError: "invalid value (P2P) of trivy.mode; allowed values (Standalone, ClientServer)", - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - mode, err := tc.configData.GetMode() - if tc.expectedError != "" { - require.EqualError(t, err, tc.expectedError) - } else { - require.NoError(t, err) - assert.Equal(t, tc.expectedMode, mode) - } - }) - } -} - -func TestGetSlow(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - want bool - }{ - { - name: "slow param set to true", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.slow": "true", - }, - }}, - want: true, - }, - { - name: "slow param set to false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.slow": "false", - }, - }}, - want: false, - }, - { - name: "slow param set to no valid value", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.slow": "false2", - }, - }}, - want: true, - }, - { - name: "slow param set to no value", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{}, - }}, - want: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got := tc.configData.GetSlow() - assert.Equal(t, got, tc.want) - - }) - } -} -func TestConfig_GetCommand(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedError string - expectedCommand trivy.Command - }{ - { - name: "Should return image", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.command": "image", - }, - }}, - expectedCommand: trivy.Image, - }, - { - name: "Should return image when value is not set", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{}, - }}, - expectedCommand: trivy.Image, - }, - { - name: "Should return filesystem", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.command": "filesystem", - }, - }}, - expectedCommand: trivy.Filesystem, - }, - { - name: "Should return rootfs", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.command": "rootfs", - }, - }}, - expectedCommand: trivy.Rootfs, - }, - { - name: "Should return error when value is not allowed", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.command": "ls", - }, - }}, - expectedError: "invalid value (ls) of trivy.command; allowed values (image, filesystem, rootfs)", - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - command, err := tc.configData.GetCommand() - if tc.expectedError != "" { - require.EqualError(t, err, tc.expectedError) - } else { - require.NoError(t, err) - assert.Equal(t, tc.expectedCommand, command) - } - }) - } -} - -func TestVulnType(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - want string - }{ - { - name: "valid vuln type os", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.vulnType": "os", - }, - }}, - want: "os", - }, - { - name: "valid vuln type library", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.vulnType": "library", - }, - }}, - want: "library", - }, - { - name: "empty vuln type", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.vulnType": "", - }, - }}, - want: "", - }, - { - name: "non valid vuln type", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.vulnType": "aaa", - }, - }}, - want: "", - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got := tc.configData.GetVulnType() - assert.Equal(t, got, tc.want) - - }) - } -} - -func TestConfig_GetResourceRequirements(t *testing.T) { - testCases := []struct { - name string - config trivy.Config - expectedError string - expectedRequirements corev1.ResourceRequirements - }{ - { - name: "Should return empty requirements by default", - config: trivy.Config{ - PluginConfig: trivyoperator.PluginConfig{}, - }, - expectedError: "", - expectedRequirements: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{}, - Limits: corev1.ResourceList{}, - }, - }, - { - name: "Should return configured resource requirement", - config: trivy.Config{ - PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.dbRepository": trivy.DefaultDBRepository, - "trivy.javaDbRepository": trivy.DefaultJavaDBRepository, - "trivy.resources.requests.cpu": "800m", - "trivy.resources.requests.memory": "200M", - "trivy.resources.limits.cpu": "600m", - "trivy.resources.limits.memory": "700M", - }, - }, - }, - expectedError: "", - expectedRequirements: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("800m"), - corev1.ResourceMemory: resource.MustParse("200M"), - }, - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("600m"), - corev1.ResourceMemory: resource.MustParse("700M"), - }, - }, - }, - { - name: "Should return error if resource is not parseable", - config: trivy.Config{ - PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.resources.requests.cpu": "roughly 100", - }, - }, - }, - expectedError: "parsing resource definition trivy.resources.requests.cpu: roughly 100 quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'", - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - resourceRequirement, err := tc.config.GetResourceRequirements() - if tc.expectedError != "" { - require.EqualError(t, err, tc.expectedError) - } else { - require.NoError(t, err) - assert.Equal(t, tc.expectedRequirements, resourceRequirement, tc.name) - } - }) - } -} - -func TestConfig_IgnoreFileExists(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedOutput bool - }{ - { - name: "Should return false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - }, - }}, - expectedOutput: false, - }, - { - name: "Should return true", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - "trivy.ignoreFile": `# Accept the risk -CVE-2018-14618 - -# No impact in our settings -CVE-2019-1543`, - }, - }}, - expectedOutput: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - exists := tc.configData.IgnoreFileExists() - assert.Equal(t, tc.expectedOutput, exists) - }) - } -} - -func TestConfig_IgnoreUnfixed(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedOutput bool - }{ - { - name: "Should return false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - }, - }}, - expectedOutput: false, - }, - { - name: "Should return true", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - "trivy.ignoreUnfixed": "true", - }, - }}, - expectedOutput: true, - }, - { - name: "Should return false when set it as false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - "trivy.ignoreUnfixed": "false", - }, - }}, - expectedOutput: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - exists := tc.configData.IgnoreUnfixed() - assert.Equal(t, tc.expectedOutput, exists) - }) - } -} - -func TestConfig_OfflineScan(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedOutput bool - }{ - { - name: "Should return false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - }, - }}, - expectedOutput: false, - }, - { - name: "Should return true", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - "trivy.offlineScan": "true", - }, - }}, - expectedOutput: true, - }, - { - name: "Should return false when set it as false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - "trivy.offlineScan": "false", - }, - }}, - expectedOutput: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - exists := tc.configData.OfflineScan() - assert.Equal(t, tc.expectedOutput, exists) - }) - } -} - -func TestConfig_dbRepositoryInsecure(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedOutput bool - }{ - { - name: "good value Should return false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.dbRepositoryInsecure": "false", - }, - }}, - expectedOutput: false, - }, - { - name: "good value Should return true", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.dbRepositoryInsecure": "true", - }, - }}, - expectedOutput: true, - }, - { - name: "bad value Should return false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.dbRepositoryInsecure": "true1", - }, - }}, - expectedOutput: false, - }, - { - name: "no value Should return false", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{}, - }}, - expectedOutput: false, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - exists := tc.configData.GetDBRepositoryInsecure() - assert.Equal(t, tc.expectedOutput, exists) - }) - } -} - -func TestConfig_GetInsecureRegistries(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedOutput map[string]bool - }{ - { - name: "Should return nil map when there is no key with trivy.insecureRegistry. prefix", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - }, - }}, - expectedOutput: make(map[string]bool), - }, - { - name: "Should return insecure registries in map", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - "trivy.insecureRegistry.pocRegistry": "poc.myregistry.harbor.com.pl", - "trivy.insecureRegistry.qaRegistry": "qa.registry.aquasec.com", - }, - }}, - expectedOutput: map[string]bool{ - "poc.myregistry.harbor.com.pl": true, - "qa.registry.aquasec.com": true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - insecureRegistries := tc.configData.GetInsecureRegistries() - assert.Equal(t, tc.expectedOutput, insecureRegistries) - }) - } -} - -func TestConfig_GetNonSSLRegistries(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedOutput map[string]bool - }{ - { - name: "Should return nil map when there is no key with trivy.nonSslRegistry. prefix", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - }, - }}, - expectedOutput: make(map[string]bool), - }, - { - name: "Should return insecure registries in map", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - "trivy.nonSslRegistry.pocRegistry": "poc.myregistry.harbor.com.pl", - "trivy.nonSslRegistry.qaRegistry": "qa.registry.aquasec.com", - }, - }}, - expectedOutput: map[string]bool{ - "poc.myregistry.harbor.com.pl": true, - "qa.registry.aquasec.com": true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - nonSslRegistries := tc.configData.GetNonSSLRegistries() - assert.Equal(t, tc.expectedOutput, nonSslRegistries) - }) - } -} - -func TestConfig_GetMirrors(t *testing.T) { - testCases := []struct { - name string - configData trivy.Config - expectedOutput map[string]string - }{ - { - name: "Should return empty map when there is no key with trivy.mirrors.registry. prefix", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "foo": "bar", - }, - }}, - expectedOutput: make(map[string]string), - }, - { - name: "Should return mirrors in a map", - configData: trivy.Config{PluginConfig: trivyoperator.PluginConfig{ - Data: map[string]string{ - "trivy.registry.mirror.docker.io": "mirror.io", - }, - }}, - expectedOutput: map[string]string{"docker.io": "mirror.io"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectedOutput, tc.configData.GetMirrors()) - }) - } -} - -func TestPlugin_Init(t *testing.T) { - - t.Run("Should create the default config", func(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects().Build() - or := kube.NewObjectResolver(testClient, &kube.CompatibleObjectMapper{}) - instance := trivy.NewPlugin(fixedClock, ext.NewSimpleIDGenerator(), &or) - - pluginContext := trivyoperator.NewPluginContext(). - WithName(trivy.Plugin). - WithNamespace("trivyoperator-ns"). - WithServiceAccountName("trivyoperator-sa"). - WithClient(testClient). - Get() - err := instance.Init(pluginContext) - require.NoError(t, err) - - var cm corev1.ConfigMap - err = testClient.Get(context.Background(), types.NamespacedName{ - Namespace: "trivyoperator-ns", - Name: "trivy-operator-trivy-config", - }, &cm) - require.NoError(t, err) - assert.Equal(t, corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ConfigMap", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "trivy-operator-trivy-config", - Namespace: "trivyoperator-ns", - Labels: map[string]string{ - "app.kubernetes.io/managed-by": "trivyoperator", - }, - ResourceVersion: "1", - }, - Data: map[string]string{ - "trivy.repository": trivy.DefaultImageRepository, - "trivy.tag": "0.45.1", - "trivy.severity": trivy.DefaultSeverity, - "trivy.slow": "true", - "trivy.mode": string(trivy.Standalone), - "trivy.timeout": "5m0s", - "trivy.dbRepository": trivy.DefaultDBRepository, - "trivy.javaDbRepository": trivy.DefaultJavaDBRepository, - "trivy.useBuiltinRegoPolicies": "true", - "trivy.supportedConfigAuditKinds": trivy.SupportedConfigAuditKinds, - "trivy.resources.requests.cpu": "100m", - "trivy.resources.requests.memory": "100M", - "trivy.resources.limits.cpu": "500m", - "trivy.resources.limits.memory": "500M", - }, - }, cm) - }) - - t.Run("Should not overwrite existing config", func(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects( - &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ConfigMap", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "trivy-operator-trivy-config", - Namespace: "trivyoperator-ns", - ResourceVersion: "1", - }, - Data: map[string]string{ - "trivy.repository": "gcr.io/aquasecurity/trivy", - "trivy.tag": "0.35.0", - "trivy.severity": trivy.DefaultSeverity, - "trivy.mode": string(trivy.Standalone), - }, - }).Build() - resolver := kube.NewObjectResolver(testClient, &kube.CompatibleObjectMapper{}) - instance := trivy.NewPlugin(fixedClock, ext.NewSimpleIDGenerator(), &resolver) - - pluginContext := trivyoperator.NewPluginContext(). - WithName(trivy.Plugin). - WithNamespace("trivyoperator-ns"). - WithServiceAccountName("trivyoperator-sa"). - WithClient(testClient). - Get() - err := instance.Init(pluginContext) - require.NoError(t, err) - - var cm corev1.ConfigMap - err = testClient.Get(context.Background(), types.NamespacedName{ - Namespace: "trivyoperator-ns", - Name: "trivy-operator-trivy-config", - }, &cm) - require.NoError(t, err) - assert.Equal(t, corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ConfigMap", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "trivy-operator-trivy-config", - Namespace: "trivyoperator-ns", - ResourceVersion: "1", - }, - Data: map[string]string{ - "trivy.repository": "gcr.io/aquasecurity/trivy", - "trivy.tag": "0.35.0", - "trivy.severity": trivy.DefaultSeverity, - "trivy.mode": string(trivy.Standalone), - }, - }, cm) - }) -} - func TestPlugin_GetScanJobSpec(t *testing.T) { tmpVolume := corev1.Volume{ @@ -7471,6 +6687,8 @@ func TestPlugin_ParseReportData(t *testing.T) { Data: map[string]string{ "trivy.repository": "aquasec/trivy", "trivy.tag": "0.9.1", + "trivy.mode": string(trivy.Standalone), + "trivy.command": string(trivy.Image), }, } @@ -7550,7 +6768,6 @@ func TestPlugin_ParseReportData(t *testing.T) { "generateSbomEnabled": "false", }). Get() - resolver := kube.NewObjectResolver(fakeClient, &kube.CompatibleObjectMapper{}) instance := trivy.NewPlugin(fixedClock, ext.NewSimpleIDGenerator(), &resolver) vulnReport, secretReport, _, err := instance.ParseReportData(ctx, tc.imageRef, io.NopCloser(strings.NewReader(tc.input)))