mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
fix(misconf): handle unsupported experimental flags in Dockerfile (#9769)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
@@ -2,12 +2,13 @@ package parser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfile, error) {
|
||||
parsed, err := parser.Parse(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dockerfile parse error: %w", err)
|
||||
return nil, xerrors.Errorf("dockerfile parse error: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -28,9 +29,9 @@ func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfil
|
||||
for _, child := range parsed.AST.Children {
|
||||
child.Value = strings.ToLower(child.Value)
|
||||
|
||||
instr, err := instructions.ParseInstruction(child)
|
||||
instr, err := parseInstruction(child)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse dockerfile instruction: %w", err)
|
||||
return nil, xerrors.Errorf("parse dockerfile instruction: %w", err)
|
||||
}
|
||||
|
||||
if _, ok := instr.(*instructions.Stage); ok {
|
||||
@@ -89,6 +90,39 @@ func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfil
|
||||
return []*dockerfile.Dockerfile{&parsedFile}, nil
|
||||
}
|
||||
|
||||
func parseInstruction(child *parser.Node) (any, error) {
|
||||
for {
|
||||
instr, err := instructions.ParseInstruction(child)
|
||||
if err == nil {
|
||||
return instr, nil
|
||||
}
|
||||
|
||||
flagName := extractUnknownFlag(err.Error())
|
||||
if flagName == "" {
|
||||
return nil, xerrors.Errorf("parse instruction %q: %w", child.Value, err)
|
||||
}
|
||||
|
||||
filtered := slices.DeleteFunc(child.Flags, func(flag string) bool {
|
||||
return strings.HasPrefix(flag, flagName)
|
||||
})
|
||||
|
||||
if len(filtered) == len(child.Flags) {
|
||||
return nil, xerrors.Errorf("cannot remove unknown flag %q from flags %v", flagName, child.Flags)
|
||||
}
|
||||
child.Flags = filtered
|
||||
}
|
||||
}
|
||||
|
||||
func extractUnknownFlag(errMsg string) string {
|
||||
after, ok := strings.CutPrefix(errMsg, "unknown flag: ")
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
flagName, _, _ := strings.Cut(after, " ")
|
||||
return flagName
|
||||
}
|
||||
|
||||
func originalFromHeredoc(node *parser.Node) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(node.Original)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -57,6 +58,75 @@ CMD python /app/app.py
|
||||
assert.Equal(t, 4, commands[3].EndLine)
|
||||
}
|
||||
|
||||
func TestParserUnknownFlags(t *testing.T) {
|
||||
input := `FROM ubuntu:18.04
|
||||
COPY --foo --chown=1 --bar=./test . /app
|
||||
RUN --baz --baz --network=host make /app
|
||||
ONBUILD RUN --foo make /app
|
||||
CMD python /app/app.py
|
||||
`
|
||||
dfs, err := parser.Parse(t.Context(), strings.NewReader(input), "Dockerfile")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dfs, 1)
|
||||
|
||||
expected := &dockerfile.Dockerfile{
|
||||
Stages: []dockerfile.Stage{
|
||||
{
|
||||
Name: "ubuntu:18.04",
|
||||
Commands: []dockerfile.Command{
|
||||
{
|
||||
Cmd: "from",
|
||||
Value: []string{"ubuntu:18.04"},
|
||||
Flags: []string{},
|
||||
Original: "FROM ubuntu:18.04",
|
||||
Path: "Dockerfile",
|
||||
StartLine: 1,
|
||||
EndLine: 1,
|
||||
},
|
||||
{
|
||||
Cmd: "copy",
|
||||
Value: []string{".", "/app"},
|
||||
Flags: []string{"--chown=1"},
|
||||
Original: "COPY --foo --chown=1 --bar=./test . /app",
|
||||
Path: "Dockerfile",
|
||||
StartLine: 2,
|
||||
EndLine: 2,
|
||||
},
|
||||
{
|
||||
Cmd: "run",
|
||||
Value: []string{"make /app"},
|
||||
Flags: []string{"--network=host"},
|
||||
Original: "RUN --baz --baz --network=host make /app",
|
||||
Path: "Dockerfile",
|
||||
StartLine: 3,
|
||||
EndLine: 3,
|
||||
},
|
||||
{
|
||||
Cmd: "onbuild",
|
||||
SubCmd: "RUN",
|
||||
Value: []string{"make /app"},
|
||||
Flags: []string{},
|
||||
Original: "ONBUILD RUN --foo make /app",
|
||||
Path: "Dockerfile",
|
||||
StartLine: 4,
|
||||
EndLine: 4,
|
||||
},
|
||||
{
|
||||
Cmd: "cmd",
|
||||
Value: []string{"python /app/app.py"},
|
||||
Flags: []string{},
|
||||
Original: "CMD python /app/app.py",
|
||||
Path: "Dockerfile",
|
||||
StartLine: 5,
|
||||
EndLine: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, dfs[0])
|
||||
}
|
||||
|
||||
func Test_ParseHeredocs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user