Skip to content

Commit 807bbbd

Browse files
authored
refactor(misconf): type-safe parser results in generic scanner (aquasecurity#9685)
Signed-off-by: nikpivkin <[email protected]>
1 parent a9a3031 commit 807bbbd

File tree

8 files changed

+102
-92
lines changed

8 files changed

+102
-92
lines changed

pkg/iac/scanners/dockerfile/parser/parser.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
1313
)
1414

15-
func Parse(_ context.Context, r io.Reader, path string) (any, error) {
15+
func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfile, error) {
1616
parsed, err := parser.Parse(r)
1717
if err != nil {
1818
return nil, fmt.Errorf("dockerfile parse error: %w", err)
@@ -86,7 +86,7 @@ func Parse(_ context.Context, r io.Reader, path string) (any, error) {
8686
parsedFile.Stages = append(parsedFile.Stages, stage)
8787
}
8888

89-
return &parsedFile, nil
89+
return []*dockerfile.Dockerfile{&parsedFile}, nil
9090
}
9191

9292
func originalFromHeredoc(node *parser.Node) string {

pkg/iac/scanners/dockerfile/parser/parser_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/require"
99

10-
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
1110
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser"
1211
)
1312

@@ -18,12 +17,11 @@ RUN make /app
1817
CMD python /app/app.py
1918
`
2019

21-
res, err := parser.Parse(t.Context(), strings.NewReader(input), "Dockerfile")
20+
dfs, err := parser.Parse(t.Context(), strings.NewReader(input), "Dockerfile")
2221
require.NoError(t, err)
22+
require.Len(t, dfs, 1)
2323

24-
df, ok := res.(*dockerfile.Dockerfile)
25-
require.True(t, ok)
26-
24+
df := dfs[0]
2725
assert.Len(t, df.Stages, 1)
2826

2927
assert.Equal(t, "ubuntu:18.04", df.Stages[0].Name)
@@ -102,11 +100,12 @@ EOF`,
102100

103101
for _, tt := range tests {
104102
t.Run(tt.name, func(t *testing.T) {
105-
res, err := parser.Parse(t.Context(), strings.NewReader(tt.src), "Dockerfile")
103+
dfs, err := parser.Parse(t.Context(), strings.NewReader(tt.src), "Dockerfile")
106104
require.NoError(t, err)
105+
require.Len(t, dfs, 1)
107106

108-
df, ok := res.(*dockerfile.Dockerfile)
109-
require.True(t, ok)
107+
df := dfs[0]
108+
require.Len(t, df.Stages, 1)
110109

111110
cmd := df.Stages[0].Commands[0]
112111

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package dockerfile
22

33
import (
4+
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
45
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser"
56
"github.com/aquasecurity/trivy/pkg/iac/scanners/generic"
67
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
78
"github.com/aquasecurity/trivy/pkg/iac/types"
89
)
910

10-
func NewScanner(opts ...options.ScannerOption) *generic.GenericScanner {
11-
return generic.NewScanner("Dockerfile", types.SourceDockerfile, generic.ParseFunc(parser.Parse), opts...)
11+
func NewScanner(opts ...options.ScannerOption) *generic.GenericScanner[*dockerfile.Dockerfile] {
12+
defaultOpts := []options.ScannerOption{
13+
generic.WithSupportsInlineIgnore[*dockerfile.Dockerfile](true),
14+
}
15+
p := generic.ParseFunc[*dockerfile.Dockerfile](parser.Parse)
16+
return generic.NewScanner("Dockerfile", types.SourceDockerfile, p, append(defaultOpts, opts...)...,
17+
)
1218
}

pkg/iac/scanners/generic/scanner.go

Lines changed: 73 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,67 @@ import (
2222
"github.com/aquasecurity/trivy/pkg/log"
2323
)
2424

25-
func NewJsonScanner(opts ...options.ScannerOption) *GenericScanner {
26-
return NewScanner("JSON", types.SourceJSON, ParseFunc(parseJson), opts...)
25+
func NewJsonScanner(opts ...options.ScannerOption) *GenericScanner[*identityMarshaler] {
26+
return NewScanner("JSON", types.SourceJSON, ParseFunc[*identityMarshaler](parseJson), opts...)
2727
}
2828

29-
func NewYamlScanner(opts ...options.ScannerOption) *GenericScanner {
30-
return NewScanner("YAML", types.SourceYAML, ParseFunc(parseYaml), opts...)
29+
func NewYamlScanner(opts ...options.ScannerOption) *GenericScanner[*identityMarshaler] {
30+
return NewScanner("YAML", types.SourceYAML, ParseFunc[*identityMarshaler](parseYaml), opts...)
3131
}
3232

33-
func NewTomlScanner(opts ...options.ScannerOption) *GenericScanner {
34-
return NewScanner("TOML", types.SourceTOML, ParseFunc(parseTOML), opts...)
33+
func NewTomlScanner(opts ...options.ScannerOption) *GenericScanner[*identityMarshaler] {
34+
return NewScanner("TOML", types.SourceTOML, ParseFunc[*identityMarshaler](parseTOML), opts...)
3535
}
3636

37-
type configParser interface {
38-
Parse(ctx context.Context, r io.Reader, path string) (any, error)
37+
type RegoMarshaler interface {
38+
ToRego() any
39+
}
40+
41+
type identityMarshaler struct {
42+
value any
43+
}
44+
45+
func (r identityMarshaler) ToRego() any {
46+
return r.value
47+
}
48+
49+
type configParser[T RegoMarshaler] interface {
50+
Parse(ctx context.Context, r io.Reader, path string) ([]T, error)
51+
}
52+
53+
type ParseFunc[T RegoMarshaler] func(ctx context.Context, r io.Reader, path string) ([]T, error)
54+
55+
func (f ParseFunc[T]) Parse(ctx context.Context, r io.Reader, path string) ([]T, error) {
56+
return f(ctx, r, path)
57+
}
58+
59+
func WithSupportsInlineIgnore[T RegoMarshaler](supports bool) options.ScannerOption {
60+
return func(s options.ConfigurableScanner) {
61+
if ss, ok := s.(*GenericScanner[T]); ok {
62+
ss.supportsInlineIgnore = supports
63+
}
64+
}
3965
}
4066

4167
// GenericScanner is a scanner that scans a file as is without processing it
42-
type GenericScanner struct {
68+
type GenericScanner[T RegoMarshaler] struct {
4369
*rego.RegoScannerProvider
4470
name string
4571
source types.Source
4672
logger *log.Logger
4773
options []options.ScannerOption
4874

49-
parser configParser
75+
parser configParser[T]
76+
supportsInlineIgnore bool
5077
}
5178

52-
type ParseFunc func(ctx context.Context, r io.Reader, path string) (any, error)
53-
54-
func (f ParseFunc) Parse(ctx context.Context, r io.Reader, path string) (any, error) {
55-
return f(ctx, r, path)
56-
}
57-
58-
func NewScanner(name string, source types.Source, parser configParser, opts ...options.ScannerOption) *GenericScanner {
59-
s := &GenericScanner{
79+
func NewScanner[T RegoMarshaler](
80+
name string,
81+
source types.Source,
82+
parser configParser[T],
83+
opts ...options.ScannerOption,
84+
) *GenericScanner[T] {
85+
s := &GenericScanner[T]{
6086
RegoScannerProvider: rego.NewRegoScannerProvider(opts...),
6187
name: name,
6288
options: opts,
@@ -72,41 +98,26 @@ func NewScanner(name string, source types.Source, parser configParser, opts ...o
7298
return s
7399
}
74100

75-
func (s *GenericScanner) Name() string {
101+
func (s *GenericScanner[T]) Name() string {
76102
return s.name
77103
}
78104

79-
func (s *GenericScanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) {
80-
fileset, err := s.parseFS(ctx, fsys, dir)
105+
func (s *GenericScanner[T]) ScanFS(ctx context.Context, fsys fs.FS, rootDir string) (scan.Results, error) {
106+
fileMap, err := s.parseFS(ctx, fsys, rootDir)
81107
if err != nil {
82108
return nil, err
83109
}
84110

85-
if len(fileset) == 0 {
111+
if len(fileMap) == 0 {
86112
return nil, nil
87113
}
88114

89115
var inputs []rego.Input
90-
for path, val := range fileset {
91-
switch v := val.(type) {
92-
case interface{ ToRego() any }:
116+
for filePath, parsedObjects := range fileMap {
117+
for _, parsedObj := range parsedObjects {
93118
inputs = append(inputs, rego.Input{
94-
Path: path,
95-
Contents: v.ToRego(),
96-
FS: fsys,
97-
})
98-
case []any:
99-
for _, file := range v {
100-
inputs = append(inputs, rego.Input{
101-
Path: path,
102-
Contents: file,
103-
FS: fsys,
104-
})
105-
}
106-
default:
107-
inputs = append(inputs, rego.Input{
108-
Path: path,
109-
Contents: v,
119+
Path: filePath,
120+
Contents: parsedObj.ToRego(),
110121
FS: fsys,
111122
})
112123
}
@@ -131,13 +142,9 @@ func (s *GenericScanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (sc
131142
return results, nil
132143
}
133144

134-
func (s *GenericScanner) supportsIgnoreRules() bool {
135-
return s.source == types.SourceDockerfile
136-
}
137-
138-
func (s *GenericScanner) parseFS(ctx context.Context, fsys fs.FS, path string) (map[string]any, error) {
139-
files := make(map[string]any)
140-
if err := fs.WalkDir(fsys, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error {
145+
func (s *GenericScanner[T]) parseFS(ctx context.Context, fsys fs.FS, rootDir string) (map[string][]T, error) {
146+
parsedFiles := make(map[string][]T)
147+
walkFn := func(filePath string, entry fs.DirEntry, err error) error {
141148
select {
142149
case <-ctx.Done():
143150
return ctx.Err()
@@ -150,27 +157,29 @@ func (s *GenericScanner) parseFS(ctx context.Context, fsys fs.FS, path string) (
150157
return nil
151158
}
152159

153-
f, err := fsys.Open(filepath.ToSlash(path))
160+
f, err := fsys.Open(filepath.ToSlash(filePath))
154161
if err != nil {
155162
return err
156163
}
157164
defer f.Close()
158165

159-
df, err := s.parser.Parse(ctx, f, path)
166+
parsedObjects, err := s.parser.Parse(ctx, f, filePath)
160167
if err != nil {
161-
s.logger.Error("Failed to parse file", log.FilePath(path), log.Err(err))
168+
s.logger.Error("Failed to parse file", log.FilePath(filePath), log.Err(err))
162169
return nil
163170
}
164-
files[path] = df
171+
parsedFiles[filePath] = parsedObjects
165172
return nil
166-
}); err != nil {
173+
}
174+
175+
if err := fs.WalkDir(fsys, filepath.ToSlash(rootDir), walkFn); err != nil {
167176
return nil, err
168177
}
169-
return files, nil
178+
return parsedFiles, nil
170179
}
171180

172-
func (s *GenericScanner) applyIgnoreRules(fsys fs.FS, results scan.Results) error {
173-
if !s.supportsIgnoreRules() {
181+
func (s *GenericScanner[T]) applyIgnoreRules(fsys fs.FS, results scan.Results) error {
182+
if !s.supportsInlineIgnore {
174183
return nil
175184
}
176185

@@ -190,21 +199,21 @@ func (s *GenericScanner) applyIgnoreRules(fsys fs.FS, results scan.Results) erro
190199
return nil
191200
}
192201

193-
func parseJson(_ context.Context, r io.Reader, _ string) (any, error) {
202+
func parseJson(_ context.Context, r io.Reader, _ string) ([]*identityMarshaler, error) {
194203
var target any
195204
if err := json.NewDecoder(r).Decode(&target); err != nil {
196205
return nil, err
197206
}
198-
return target, nil
207+
return []*identityMarshaler{{value: target}}, nil
199208
}
200209

201-
func parseYaml(_ context.Context, r io.Reader, _ string) (any, error) {
210+
func parseYaml(_ context.Context, r io.Reader, _ string) ([]*identityMarshaler, error) {
202211
contents, err := io.ReadAll(r)
203212
if err != nil {
204213
return nil, err
205214
}
206215

207-
var results []any
216+
var documents []*identityMarshaler
208217

209218
marker := "\n---\n"
210219
altMarker := "\r\n---\r\n"
@@ -217,16 +226,16 @@ func parseYaml(_ context.Context, r io.Reader, _ string) (any, error) {
217226
if err := yaml.Unmarshal([]byte(partial), &target); err != nil {
218227
return nil, err
219228
}
220-
results = append(results, target)
229+
documents = append(documents, &identityMarshaler{target})
221230
}
222231

223-
return results, nil
232+
return documents, nil
224233
}
225234

226-
func parseTOML(_ context.Context, r io.Reader, _ string) (any, error) {
235+
func parseTOML(_ context.Context, r io.Reader, _ string) ([]*identityMarshaler, error) {
227236
var target any
228237
if _, err := toml.NewDecoder(r).Decode(&target); err != nil {
229238
return nil, err
230239
}
231-
return target, nil
240+
return []*identityMarshaler{{value: target}}, nil
232241
}

pkg/iac/scanners/helm/scanner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func (s *Scanner) getScanResults(ctx context.Context, path string, target fs.FS)
140140
for _, manifest := range manifests {
141141
fileResults, err := rs.ScanInput(ctx, types.SourceKubernetes, rego.Input{
142142
Path: file.TemplateFilePath,
143-
Contents: manifest,
143+
Contents: manifest.ToRego(),
144144
FS: manifestFS,
145145
})
146146
if err != nil {

pkg/iac/scanners/kubernetes/parser/parser.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"strings"
99
)
1010

11-
func Parse(_ context.Context, r io.Reader, path string) ([]any, error) {
11+
func Parse(_ context.Context, r io.Reader, path string) ([]*Manifest, error) {
1212
contents, err := io.ReadAll(r)
1313
if err != nil {
1414
return nil, err
@@ -23,10 +23,10 @@ func Parse(_ context.Context, r io.Reader, path string) ([]any, error) {
2323
if err != nil {
2424
return nil, err
2525
}
26-
return []any{manifest.ToRego()}, nil
26+
return []*Manifest{manifest}, nil
2727
}
2828

29-
var manifests []any
29+
var manifests []*Manifest
3030

3131
re := regexp.MustCompile(`(?m:^---\r?\n)`)
3232
offset := 0
@@ -35,10 +35,9 @@ func Parse(_ context.Context, r io.Reader, path string) ([]any, error) {
3535
if err != nil {
3636
return nil, err
3737
}
38-
3938
if manifest.Content != nil {
4039
manifest.Content.Offset = offset
41-
manifests = append(manifests, manifest.ToRego())
40+
manifests = append(manifests, manifest)
4241
}
4342

4443
offset += countLines(partial)

pkg/iac/scanners/kubernetes/parser/parser_test.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,9 @@ kind: Pod
6565
require.NoError(t, err)
6666
require.Len(t, manifests, len(tt.expectedOffsets))
6767

68-
for i, m := range manifests {
69-
manifest := m.(map[string]any)
70-
metadata, ok := manifest["__defsec_metadata"].(map[string]any)
71-
require.True(t, ok)
72-
offset, ok := metadata["offset"].(int)
73-
require.True(t, ok)
74-
require.Equal(t, tt.expectedOffsets[i], offset)
68+
for i, manifest := range manifests {
69+
require.NotNil(t, manifest.Content)
70+
require.Equal(t, tt.expectedOffsets[i], manifest.Content.Offset)
7571
}
7672
})
7773
}

pkg/iac/scanners/kubernetes/scanner.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import (
1010
"github.com/aquasecurity/trivy/pkg/iac/types"
1111
)
1212

13-
func NewScanner(opts ...options.ScannerOption) *generic.GenericScanner {
14-
return generic.NewScanner("Kubernetes", types.SourceKubernetes, generic.ParseFunc(parse), opts...)
13+
func NewScanner(opts ...options.ScannerOption) *generic.GenericScanner[*parser.Manifest] {
14+
p := generic.ParseFunc[*parser.Manifest](parse)
15+
return generic.NewScanner("Kubernetes", types.SourceKubernetes, p, opts...)
1516
}
1617

17-
func parse(ctx context.Context, r io.Reader, path string) (any, error) {
18+
func parse(ctx context.Context, r io.Reader, path string) ([]*parser.Manifest, error) {
1819
return parser.Parse(ctx, r, path)
1920
}

0 commit comments

Comments
 (0)