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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
|
"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) {
|
func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfile, error) {
|
||||||
parsed, err := parser.Parse(r)
|
parsed, err := parser.Parse(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dockerfile parse error: %w", err)
|
return nil, xerrors.Errorf("dockerfile parse error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -28,9 +29,9 @@ func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfil
|
|||||||
for _, child := range parsed.AST.Children {
|
for _, child := range parsed.AST.Children {
|
||||||
child.Value = strings.ToLower(child.Value)
|
child.Value = strings.ToLower(child.Value)
|
||||||
|
|
||||||
instr, err := instructions.ParseInstruction(child)
|
instr, err := parseInstruction(child)
|
||||||
if err != nil {
|
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 {
|
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
|
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 {
|
func originalFromHeredoc(node *parser.Node) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString(node.Original)
|
sb.WriteString(node.Original)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
|
||||||
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser"
|
"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)
|
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) {
|
func Test_ParseHeredocs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Reference in New Issue
Block a user