Skip to content

Commit

Permalink
refactor(misconf): introduce generic scanner (#7515)
Browse files Browse the repository at this point in the history
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
  • Loading branch information
nikpivkin authored Oct 11, 2024
1 parent 672e886 commit c78f45b
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 1,235 deletions.
64 changes: 7 additions & 57 deletions pkg/iac/scanners/dockerfile/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,26 @@ import (
"context"
"fmt"
"io"
"io/fs"
"path/filepath"
"strings"

"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"

"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
"github.com/aquasecurity/trivy/pkg/log"
)

type Parser struct {
logger *log.Logger
}

// New creates a new Dockerfile parser
func New() *Parser {
return &Parser{
logger: log.WithPrefix("dockerfile parser"),
}
}

func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]*dockerfile.Dockerfile, error) {

files := make(map[string]*dockerfile.Dockerfile)
if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if err != nil {
return err
}
if entry.IsDir() {
return nil
}

df, err := p.ParseFile(ctx, target, path)
if err != nil {
p.logger.Error("Failed to parse Dockerfile", log.FilePath(path), log.Err(err))
return nil
}
files[path] = df
return nil
}); err != nil {
return nil, err
}
return files, nil
}

// ParseFile parses Dockerfile content from the provided filesystem path.
func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (*dockerfile.Dockerfile, error) {
f, err := fsys.Open(filepath.ToSlash(path))
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
return p.parse(path, f)
}

func (p *Parser) parse(path string, r io.Reader) (*dockerfile.Dockerfile, error) {
func Parse(_ context.Context, r io.Reader, path string) (any, error) {
parsed, err := parser.Parse(r)
if err != nil {
return nil, fmt.Errorf("dockerfile parse error: %w", err)
}

var parsedFile dockerfile.Dockerfile
var stage dockerfile.Stage
var stageIndex int
var (
parsedFile dockerfile.Dockerfile
stage dockerfile.Stage
stageIndex int
)

fromValue := "args"
for _, child := range parsed.AST.Children {
child.Value = strings.ToLower(child.Value)
Expand Down
13 changes: 9 additions & 4 deletions pkg/iac/scanners/dockerfile/parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package parser
package parser_test

import (
"context"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser"
)

func Test_Parser(t *testing.T) {
Expand All @@ -15,12 +19,13 @@ RUN make /app
CMD python /app/app.py
`

df, err := New().parse("Dockerfile", strings.NewReader(input))
res, err := parser.Parse(context.TODO(), strings.NewReader(input), "Dockerfile")
require.NoError(t, err)

assert.Len(t, df.Stages, 1)
df, ok := res.(*dockerfile.Dockerfile)
require.True(t, ok)

require.Len(t, df.Stages, 1)
assert.Len(t, df.Stages, 1)

assert.Equal(t, "ubuntu:18.04", df.Stages[0].Name)
commands := df.Stages[0].Commands
Expand Down
108 changes: 3 additions & 105 deletions pkg/iac/scanners/dockerfile/scanner.go
Original file line number Diff line number Diff line change
@@ -1,114 +1,12 @@
package dockerfile

import (
"context"
"io/fs"
"sync"

"github.com/aquasecurity/trivy/pkg/iac/framework"
"github.com/aquasecurity/trivy/pkg/iac/rego"
"github.com/aquasecurity/trivy/pkg/iac/scan"
"github.com/aquasecurity/trivy/pkg/iac/scanners"
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser"
"github.com/aquasecurity/trivy/pkg/iac/scanners/generic"
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
"github.com/aquasecurity/trivy/pkg/iac/types"
"github.com/aquasecurity/trivy/pkg/log"
)

var _ scanners.FSScanner = (*Scanner)(nil)
var _ options.ConfigurableScanner = (*Scanner)(nil)

type Scanner struct {
mu sync.Mutex
logger *log.Logger
parser *parser.Parser
regoScanner *rego.Scanner
options []options.ScannerOption
}

func (s *Scanner) SetIncludeDeprecatedChecks(bool) {}
func (s *Scanner) SetRegoOnly(bool) {}
func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {}

func (s *Scanner) Name() string {
return "Dockerfile"
}

func NewScanner(opts ...options.ScannerOption) *Scanner {
s := &Scanner{
options: opts,
logger: log.WithPrefix("dockerfile scanner"),
}
for _, opt := range opts {
opt(s)
}
s.parser = parser.New()
return s
}

func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, path string) (scan.Results, error) {

files, err := s.parser.ParseFS(ctx, fsys, path)
if err != nil {
return nil, err
}

if len(files) == 0 {
return nil, nil
}

var inputs []rego.Input
for path, dfile := range files {
inputs = append(inputs, rego.Input{
Path: path,
FS: fsys,
Contents: dfile.ToRego(),
})
}

results, err := s.scanRego(ctx, fsys, inputs...)
if err != nil {
return nil, err
}
return results, nil
}

func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.Results, error) {
dockerfile, err := s.parser.ParseFile(ctx, fsys, path)
if err != nil {
return nil, err
}
s.logger.Debug("Scanning", log.FilePath(path))
return s.scanRego(ctx, fsys, rego.Input{
Path: path,
Contents: dockerfile.ToRego(),
})
}

func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.regoScanner != nil {
return s.regoScanner, nil
}

regoScanner := rego.NewScanner(types.SourceDockerfile, s.options...)
if err := regoScanner.LoadPolicies(srcFS); err != nil {
return nil, err
}
s.regoScanner = regoScanner
return regoScanner, nil
}

func (s *Scanner) scanRego(ctx context.Context, srcFS fs.FS, inputs ...rego.Input) (scan.Results, error) {
regoScanner, err := s.initRegoScanner(srcFS)
if err != nil {
return nil, err
}
results, err := regoScanner.ScanInput(ctx, inputs...)
if err != nil {
return nil, err
}
results.SetSourceAndFilesystem("", srcFS, false)
return results, nil
func NewScanner(opts ...options.ScannerOption) *generic.GenericScanner {
return generic.NewScanner("Dockerfile", types.SourceDockerfile, generic.ParseFunc(parser.Parse), opts...)
}
7 changes: 4 additions & 3 deletions pkg/iac/scanners/dockerfile/scanner_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dockerfile
package dockerfile_test

import (
"bytes"
Expand All @@ -13,6 +13,7 @@ import (
"github.com/aquasecurity/trivy/pkg/iac/rego"
"github.com/aquasecurity/trivy/pkg/iac/rego/schemas"
"github.com/aquasecurity/trivy/pkg/iac/scan"
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile"
)

const DS006PolicyWithDockerfileSchema = `# METADATA
Expand Down Expand Up @@ -219,7 +220,7 @@ USER root
"/rules/rule.rego": DS006LegacyWithOldStyleMetadata,
})

scanner := NewScanner(rego.WithPolicyDirs("rules"))
scanner := dockerfile.NewScanner(rego.WithPolicyDirs("rules"))

results, err := scanner.ScanFS(context.TODO(), fs, "code")
require.NoError(t, err)
Expand Down Expand Up @@ -563,7 +564,7 @@ COPY --from=dep /binary /`

var traceBuf bytes.Buffer

scanner := NewScanner(
scanner := dockerfile.NewScanner(
rego.WithPolicyDirs("rules"),
rego.WithEmbeddedLibraries(true),
rego.WithTrace(&traceBuf),
Expand Down
Loading

0 comments on commit c78f45b

Please sign in to comment.