diff --git a/pkg/k8s/scanner/io.go b/pkg/k8s/scanner/io.go index 9c32699ddd63..65f102a4b08c 100644 --- a/pkg/k8s/scanner/io.go +++ b/pkg/k8s/scanner/io.go @@ -3,6 +3,7 @@ package scanner import ( "fmt" "os" + "path/filepath" "regexp" "runtime" @@ -13,36 +14,63 @@ import ( "github.com/aquasecurity/trivy/pkg/log" ) -var r = regexp.MustCompile("\\\\|/|:|\\*|\\?|<|>") +var r = regexp.MustCompile("[\\\\/:*?<>]") -func createTempFile(artifact *artifacts.Artifact) (string, error) { +func generateTempFileByArtifact(artifact *artifacts.Artifact, tempDir string) (string, error) { filename := fmt.Sprintf("%s-%s-%s-*.yaml", artifact.Namespace, artifact.Kind, artifact.Name) - if runtime.GOOS == "windows" { // removes characters not permitted in file/directory names on Windows filename = filenameWindowsFriendly(filename) } - file, err := os.CreateTemp("", filename) + file, err := os.CreateTemp(tempDir, filename) if err != nil { - return "", xerrors.Errorf("creating tmp file error: %w", err) + return "", xerrors.Errorf("failed to create temporary file: %w", err) } + shouldRemove := false defer func() { if err := file.Close(); err != nil { - log.Error("Failed to close temp file", log.String("path", file.Name()), log.Err(err)) + log.Error("Failed to close temp file", log.FilePath(file.Name()), log.Err(err)) + } + if shouldRemove { + removeFile(file.Name()) } }() - if err := yaml.NewEncoder(file).Encode(artifact.RawResource); err != nil { - removeFile(filename) - return "", xerrors.Errorf("marshaling resource error: %w", err) + shouldRemove = true + return "", xerrors.Errorf("failed to encode artifact: %w", err) + } + return filepath.Base(file.Name()), nil +} + +// generateTempDir creates a directory with yaml files generated from kubernetes artifacts +// returns a directory name, a map for mapping a temp target file to k8s artifact and error +func generateTempDir(arts []*artifacts.Artifact) (string, map[string]*artifacts.Artifact, error) { + tempDir, err := os.MkdirTemp("", "trivyk8s*") + if err != nil { + return "", nil, xerrors.Errorf("failed to create temp directory: %w", err) + } + + m := make(map[string]*artifacts.Artifact) + for _, artifact := range arts { + filename, err := generateTempFileByArtifact(artifact, tempDir) + if err != nil { + log.Error("Failed to create temp file", log.FilePath(filename), log.Err(err)) + continue + } + m[filename] = artifact } + return tempDir, m, nil +} - return file.Name(), nil +func removeDir(dirname string) { + if err := os.RemoveAll(dirname); err != nil { + log.Error("Failed to remove temp directory", log.FilePath(dirname), log.Err(err)) + } } func removeFile(filename string) { if err := os.Remove(filename); err != nil { - log.Error("Failed to remove temp file", log.String("path", filename), log.Err(err)) + log.Error("Failed to remove temp file", log.FilePath(filename), log.Err(err)) } } diff --git a/pkg/k8s/scanner/io_test.go b/pkg/k8s/scanner/io_test.go index 7587d1bb8282..9e256b39f2b3 100644 --- a/pkg/k8s/scanner/io_test.go +++ b/pkg/k8s/scanner/io_test.go @@ -23,6 +23,11 @@ func Test_FilenameWindowsFriendly(t *testing.T) { fileName: `kube-system-Role-system-controller-bootstrap-signer-2934213283.yaml`, want: `kube-system-Role-system-controller-bootstrap-signer-2934213283.yaml`, }, + { + name: "name with no invalid - slash", + fileName: "-ClusterRoleBinding-system\\basic-user-725844313.yaml", + want: `-ClusterRoleBinding-system_basic-user-725844313.yaml`, + }, } for _, test := range tests { diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index 67d06b4c54bd..70debfe6a85f 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -82,14 +82,18 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) var resources []report.Resource - type scanResult struct { - vulns []report.Resource - misconfig report.Resource + // scans kubernetes artifacts as a scope of yaml files + if local.ShouldScanMisconfigOrRbac(s.opts.Scanners) { + misconfigs, err := s.scanMisconfigs(ctx, resourceArtifacts) + if err != nil { + return report.Report{}, xerrors.Errorf("scanning misconfigurations error: %w", err) + } + resources = append(resources, misconfigs...) } - onItem := func(ctx context.Context, artifact *artifacts.Artifact) (scanResult, error) { - scanResults := scanResult{} - if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner, types.SecretScanner) && !s.opts.SkipImages { + // scan images from kubernetes cluster in parallel + if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner, types.SecretScanner) && !s.opts.SkipImages { + onItem := func(ctx context.Context, artifact *artifacts.Artifact) ([]report.Resource, error) { opts := s.opts opts.Credentials = make([]ftypes.Credential, len(s.opts.Credentials)) copy(opts.Credentials, s.opts.Credentials) @@ -106,33 +110,22 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) } vulns, err := s.scanVulns(ctx, artifact, opts) if err != nil { - return scanResult{}, xerrors.Errorf("scanning vulnerabilities error: %w", err) + return nil, xerrors.Errorf("scanning vulnerabilities error: %w", err) } - scanResults.vulns = vulns + return vulns, nil } - if local.ShouldScanMisconfigOrRbac(s.opts.Scanners) { - misconfig, err := s.scanMisconfigs(ctx, artifact) - if err != nil { - return scanResult{}, xerrors.Errorf("scanning misconfigurations error: %w", err) - } - scanResults.misconfig = misconfig + + onResult := func(result []report.Resource) error { + resources = append(resources, result...) + return nil } - return scanResults, nil - } - onResult := func(result scanResult) error { - resources = append(resources, result.vulns...) - // don't add empty misconfig results to resources slice to avoid an empty resource - if result.misconfig.Results != nil { - resources = append(resources, result.misconfig) + p := parallel.NewPipeline(s.opts.Parallel, !s.opts.Quiet, resourceArtifacts, onItem, onResult) + if err := p.Do(ctx); err != nil { + return report.Report{}, err } - return nil } - p := parallel.NewPipeline(s.opts.Parallel, !s.opts.Quiet, resourceArtifacts, onItem, onResult) - if err := p.Do(ctx); err != nil { - return report.Report{}, err - } if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner) { k8sResource, err := s.scanK8sVulns(ctx, k8sCoreArtifacts) if err != nil { @@ -173,22 +166,43 @@ func (s *Scanner) scanVulns(ctx context.Context, artifact *artifacts.Artifact, o return resources, nil } -func (s *Scanner) scanMisconfigs(ctx context.Context, artifact *artifacts.Artifact) (report.Resource, error) { - configFile, err := createTempFile(artifact) +func (s *Scanner) scanMisconfigs(ctx context.Context, k8sArtifacts []*artifacts.Artifact) ([]report.Resource, error) { + dir, artifactsByFilename, err := generateTempDir(k8sArtifacts) if err != nil { - return report.Resource{}, xerrors.Errorf("scan error: %w", err) + return nil, xerrors.Errorf("failed to generate temp dir: %w", err) } - s.opts.Target = configFile + s.opts.Target = dir configReport, err := s.runner.ScanFilesystem(ctx, s.opts) - // remove config file after scanning - removeFile(configFile) + // remove config files after scanning + removeDir(dir) + if err != nil { - return report.CreateResource(artifact, configReport, err), err + return nil, xerrors.Errorf("failed to scan filesystem: %w", err) + } + resources := make([]report.Resource, 0, len(k8sArtifacts)) + + for _, res := range configReport.Results { + artifact := artifactsByFilename[res.Target] + + singleReport := types.Report{ + SchemaVersion: configReport.SchemaVersion, + CreatedAt: configReport.CreatedAt, + ArtifactName: res.Target, + ArtifactType: configReport.ArtifactType, + Metadata: configReport.Metadata, + Results: types.Results{res}, + } + + resource, err := s.filter(ctx, singleReport, artifact) + if err != nil { + resource = report.CreateResource(artifact, singleReport, err) + } + resources = append(resources, resource) } - return s.filter(ctx, configReport, artifact) + return resources, nil } func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifacts.Artifact) (report.Resource, error) { var err error