Skip to content

Commit

Permalink
Refresh sample test data via new refresh command (chainguard-dev#634)
Browse files Browse the repository at this point in the history
* Refresh test data via new refresh command

Signed-off-by: egibs <[email protected]>

* Export CachedRules to help with refresh scans

Signed-off-by: egibs <[email protected]>

---------

Signed-off-by: egibs <[email protected]>
  • Loading branch information
egibs authored Nov 17, 2024
1 parent 6cd4c4b commit 8fead21
Show file tree
Hide file tree
Showing 9 changed files with 624 additions and 233 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ update-third-party:

.PHONY: refresh-sample-testdata
refresh-sample-testdata: out/$(SAMPLES_REPO)/.decompressed-$(SAMPLES_COMMIT) out/mal
./tests/refresh-testdata.sh ./out/mal out/$(SAMPLES_REPO)
./out/mal refresh

ARCH ?= $(shell uname -m)
CRANE_VERSION=v0.20.2
Expand Down
17 changes: 17 additions & 0 deletions cmd/mal/mal.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/chainguard-dev/clog"
"github.com/chainguard-dev/malcontent/pkg/action"
"github.com/chainguard-dev/malcontent/pkg/profile"
"github.com/chainguard-dev/malcontent/pkg/refresh"
"github.com/chainguard-dev/malcontent/pkg/render"
"github.com/chainguard-dev/malcontent/pkg/version"
"github.com/chainguard-dev/malcontent/rules"
Expand Down Expand Up @@ -474,6 +475,22 @@ func main() {
return nil
},
},
{
Name: "refresh",
Usage: "Refresh test data",
Action: func(_ *cli.Context) error {
cfg := refresh.Config{
Concurrency: runtime.NumCPU(),
SamplesPath: "./out/chainguard-dev/malcontent-samples",
TestDataPath: "./tests",
}
if err := refresh.Refresh(ctx, cfg); err != nil {
returnCode = ExitInputOutput
return err
}
return nil
},
},
{
Name: "scan",
Usage: "tersely scan a path and return findings of the highest severity",
Expand Down
37 changes: 33 additions & 4 deletions pkg/action/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,16 @@ func scanSinglePath(ctx context.Context, c malcontent.Config, path string, ruleF
defer f.Close()
fd := f.Fd()

yrs, err := cachedRules(ctx, ruleFS)
if err != nil {
return nil, fmt.Errorf("rules: %w", err)
// For non-refresh scans, c.Rules will be nil
// For refreshes, the rules _should_ be compiled by the time we get here
var yrs *yara.Rules
if c.Rules == nil {
yrs, err = CachedRules(ctx, ruleFS)
if err != nil {
return nil, fmt.Errorf("rules: %w", err)
}
} else {
yrs = c.Rules
}
if err := yrs.ScanFileDescriptor(fd, 0, 0, &mrs); err != nil {
logger.Info("skipping", slog.Any("error", err))
Expand All @@ -147,10 +154,20 @@ func scanSinglePath(ctx context.Context, c malcontent.Config, path string, ruleF
// If absPath is provided, use it instead of the path if they are different.
// This is useful when scanning images and archives.
if absPath != "" && absPath != path && isArchive {
if len(c.TrimPrefixes) > 0 {
absPath = report.TrimPrefixes(absPath, c.TrimPrefixes)
}
fr.Path = fmt.Sprintf("%s ∴ %s", absPath, clean)
}

if len(fr.Behaviors) == 0 {
if len(c.TrimPrefixes) > 0 {
if isArchive {
absPath = report.TrimPrefixes(absPath, c.TrimPrefixes)
} else {
path = report.TrimPrefixes(absPath, c.TrimPrefixes)
}
}
// Ensure that files within archives with no behaviors are formatted consistently
if isArchive {
return &malcontent.FileReport{Path: fmt.Sprintf("%s ∴ %s", absPath, clean)}, nil
Expand Down Expand Up @@ -223,7 +240,7 @@ func exitIfHitOrMiss(frs *sync.Map, scanPath string, errIfHit bool, errIfMiss bo
return nil, nil
}

func cachedRules(ctx context.Context, fss []fs.FS) (*yara.Rules, error) {
func CachedRules(ctx context.Context, fss []fs.FS) (*yara.Rules, error) {
if compiledRuleCache != nil {
return compiledRuleCache, nil
}
Expand Down Expand Up @@ -325,6 +342,9 @@ func recursiveScan(ctx context.Context, c malcontent.Config) (*malcontent.Report
}
if k, ok := key.(string); ok {
if fr, ok := value.(*malcontent.FileReport); ok {
if len(c.TrimPrefixes) > 0 {
path = report.TrimPrefixes(k, c.TrimPrefixes)
}
r.Files.Store(k, fr)
if c.Renderer != nil && r.Diff == nil && fr.RiskScore >= c.MinFileRisk {
if err := c.Renderer.File(ctx, fr); err != nil {
Expand All @@ -348,6 +368,9 @@ func recursiveScan(ctx context.Context, c malcontent.Config) (*malcontent.Report

fr, err := processFile(ctx, c, c.RuleFS, path, scanPath, trimPath, logger)
if err != nil {
if len(c.TrimPrefixes) > 0 {
path = report.TrimPrefixes(path, c.TrimPrefixes)
}
r.Files.Store(path, &malcontent.FileReport{})
return fmt.Errorf("process: %w", err)
}
Expand All @@ -367,6 +390,9 @@ func recursiveScan(ctx context.Context, c malcontent.Config) (*malcontent.Report
}
}

if len(c.TrimPrefixes) > 0 {
path = report.TrimPrefixes(path, c.TrimPrefixes)
}
r.Files.Store(path, fr)
if c.Renderer != nil && r.Diff == nil && fr.RiskScore >= c.MinFileRisk {
if err := c.Renderer.File(ctx, fr); err != nil {
Expand Down Expand Up @@ -422,6 +448,9 @@ func recursiveScan(ctx context.Context, c malcontent.Config) (*malcontent.Report
Files: sync.Map{},
}
if match.fr != nil {
if len(c.TrimPrefixes) > 0 {
match.fr.Path = report.TrimPrefixes(match.fr.Path, c.TrimPrefixes)
}
r.Files.Store(match.fr.Path, match.fr)
}
return r, match.err
Expand Down
3 changes: 3 additions & 0 deletions pkg/malcontent/malcontent.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io/fs"
"sync"

"github.com/hillu/go-yara/v4"
orderedmap "github.com/wk8/go-ordered-map/v2"
)

Expand Down Expand Up @@ -36,9 +37,11 @@ type Config struct {
QuantityIncreasesRisk bool
Renderer Renderer
RuleFS []fs.FS
Rules *yara.Rules
Scan bool
ScanPaths []string
Stats bool
TrimPrefixes []string
}

type Behavior struct {
Expand Down
87 changes: 87 additions & 0 deletions pkg/refresh/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package refresh

import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"

"github.com/chainguard-dev/malcontent/pkg/action"
"github.com/chainguard-dev/malcontent/pkg/malcontent"
"github.com/chainguard-dev/malcontent/pkg/render"
"github.com/chainguard-dev/malcontent/rules"
thirdparty "github.com/chainguard-dev/malcontent/third_party"
)

type actionData struct {
format string
outputPath string
scanPath string
}

// actionSampleData contains paths and formats for test data in pkg/action.
var actionTestData = []actionData{
{
format: "json",
scanPath: "pkg/action/testdata/static.tar.xz",
outputPath: "pkg/action/testdata/scan_oci",
},
{
format: "json",
scanPath: "pkg/action/testdata/apko_nested.tar.gz",
outputPath: "pkg/action/testdata/scan_archive",
},
}

func actionRefresh(ctx context.Context) ([]TestData, error) {
testData := make([]TestData, 0, len(actionTestData))

for _, td := range actionTestData {
output := td.outputPath
scan := td.scanPath

if _, err := os.Stat(scan); err != nil {
return nil, fmt.Errorf("special case input file not found: %s: %w", scan, err)
}

if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil {
return nil, fmt.Errorf("create output directory: %w", err)
}

outFile, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
if err != nil {
return nil, fmt.Errorf("create output file %s: %w", output, err)
}

r, err := render.New(td.format, outFile)
if err != nil {
return nil, fmt.Errorf("create renderer for %s: %w", output, err)
}

rfs := []fs.FS{rules.FS, thirdparty.FS}
yrs, err := action.CachedRules(ctx, rfs)
if err != nil {
return nil, err
}

c := &malcontent.Config{
IgnoreSelf: false,
MinFileRisk: 0,
MinRisk: 0,
OCI: false,
QuantityIncreasesRisk: true,
Renderer: r,
RuleFS: rfs,
Rules: yrs,
ScanPaths: []string{scan},
TrimPrefixes: []string{"pkg/action/"},
}

testData = append(testData, TestData{
Config: c,
OutputPath: output,
})
}
return testData, nil
}
Loading

0 comments on commit 8fead21

Please sign in to comment.