|
6 | 6 | "fmt" |
7 | 7 | "strings" |
8 | 8 |
|
| 9 | + v1 "github.com/google/go-containerregistry/pkg/v1" |
9 | 10 | "golang.org/x/xerrors" |
10 | 11 |
|
11 | 12 | "github.com/aquasecurity/trivy/pkg/fanal/analyzer" |
@@ -49,76 +50,98 @@ func (a *historyAnalyzer) Analyze(ctx context.Context, input analyzer.ConfigAnal |
49 | 50 | if input.Config == nil { |
50 | 51 | return nil, nil |
51 | 52 | } |
| 53 | + |
| 54 | + fsys := mapfs.New() |
| 55 | + if err := fsys.WriteVirtualFile( |
| 56 | + "Dockerfile", imageConfigToDockerfile(input.Config), 0600); err != nil { |
| 57 | + return nil, xerrors.Errorf("mapfs write error: %w", err) |
| 58 | + } |
| 59 | + |
| 60 | + misconfs, err := a.scanner.Scan(ctx, fsys) |
| 61 | + if err != nil { |
| 62 | + return nil, xerrors.Errorf("history scan error: %w", err) |
| 63 | + } |
| 64 | + // The result should be a single element as it passes one Dockerfile. |
| 65 | + if len(misconfs) != 1 { |
| 66 | + return nil, nil |
| 67 | + } |
| 68 | + |
| 69 | + return &analyzer.ConfigAnalysisResult{ |
| 70 | + Misconfiguration: &misconfs[0], |
| 71 | + }, nil |
| 72 | +} |
| 73 | + |
| 74 | +func imageConfigToDockerfile(cfg *v1.ConfigFile) []byte { |
52 | 75 | dockerfile := new(bytes.Buffer) |
53 | 76 | var userFound bool |
54 | | - baseLayerIndex := image.GuessBaseImageIndex(input.Config.History) |
55 | | - for i := baseLayerIndex + 1; i < len(input.Config.History); i++ { |
56 | | - h := input.Config.History[i] |
| 77 | + baseLayerIndex := image.GuessBaseImageIndex(cfg.History) |
| 78 | + for i := baseLayerIndex + 1; i < len(cfg.History); i++ { |
| 79 | + h := cfg.History[i] |
57 | 80 | var createdBy string |
58 | 81 | switch { |
59 | 82 | case strings.HasPrefix(h.CreatedBy, "/bin/sh -c #(nop)"): |
60 | 83 | // Instruction other than RUN |
61 | 84 | createdBy = strings.TrimPrefix(h.CreatedBy, "/bin/sh -c #(nop)") |
62 | 85 | case strings.HasPrefix(h.CreatedBy, "/bin/sh -c"): |
63 | 86 | // RUN instruction |
64 | | - createdBy = strings.ReplaceAll(h.CreatedBy, "/bin/sh -c", "RUN") |
| 87 | + createdBy = buildRunInstruction(createdBy) |
65 | 88 | case strings.HasSuffix(h.CreatedBy, "# buildkit"): |
66 | 89 | // buildkit instructions |
67 | 90 | // COPY ./foo /foo # buildkit |
68 | 91 | // ADD ./foo.txt /foo.txt # buildkit |
69 | 92 | // RUN /bin/sh -c ls -hl /foo # buildkit |
70 | 93 | createdBy = strings.TrimSuffix(h.CreatedBy, "# buildkit") |
71 | | - if strings.HasPrefix(h.CreatedBy, "RUN /bin/sh -c") { |
72 | | - createdBy = strings.ReplaceAll(createdBy, "RUN /bin/sh -c", "RUN") |
73 | | - } |
| 94 | + createdBy = buildRunInstruction(createdBy) |
74 | 95 | case strings.HasPrefix(h.CreatedBy, "USER"): |
75 | 96 | // USER instruction |
76 | 97 | createdBy = h.CreatedBy |
77 | 98 | userFound = true |
78 | 99 | case strings.HasPrefix(h.CreatedBy, "HEALTHCHECK"): |
79 | 100 | // HEALTHCHECK instruction |
80 | | - var interval, timeout, startPeriod, retries, command string |
81 | | - if input.Config.Config.Healthcheck.Interval != 0 { |
82 | | - interval = fmt.Sprintf("--interval=%s ", input.Config.Config.Healthcheck.Interval) |
| 101 | + createdBy = buildHealthcheckInstruction(cfg.Config.Healthcheck) |
| 102 | + default: |
| 103 | + for _, prefix := range []string{"ARG", "ENV", "ENTRYPOINT"} { |
| 104 | + strings.HasPrefix(h.CreatedBy, prefix) |
| 105 | + createdBy = h.CreatedBy |
| 106 | + break |
83 | 107 | } |
84 | | - if input.Config.Config.Healthcheck.Timeout != 0 { |
85 | | - timeout = fmt.Sprintf("--timeout=%s ", input.Config.Config.Healthcheck.Timeout) |
86 | | - } |
87 | | - if input.Config.Config.Healthcheck.StartPeriod != 0 { |
88 | | - startPeriod = fmt.Sprintf("--startPeriod=%s ", input.Config.Config.Healthcheck.StartPeriod) |
89 | | - } |
90 | | - if input.Config.Config.Healthcheck.Retries != 0 { |
91 | | - retries = fmt.Sprintf("--retries=%d ", input.Config.Config.Healthcheck.Retries) |
92 | | - } |
93 | | - command = strings.Join(input.Config.Config.Healthcheck.Test, " ") |
94 | | - command = strings.ReplaceAll(command, "CMD-SHELL", "CMD") |
95 | | - createdBy = fmt.Sprintf("HEALTHCHECK %s%s%s%s%s", interval, timeout, startPeriod, retries, command) |
96 | 108 | } |
97 | 109 | dockerfile.WriteString(strings.TrimSpace(createdBy) + "\n") |
98 | 110 | } |
99 | 111 |
|
100 | | - if !userFound && input.Config.Config.User != "" { |
101 | | - user := fmt.Sprintf("USER %s", input.Config.Config.User) |
| 112 | + if !userFound && cfg.Config.User != "" { |
| 113 | + user := fmt.Sprintf("USER %s", cfg.Config.User) |
102 | 114 | dockerfile.WriteString(user) |
103 | 115 | } |
104 | 116 |
|
105 | | - fsys := mapfs.New() |
106 | | - if err := fsys.WriteVirtualFile("Dockerfile", dockerfile.Bytes(), 0600); err != nil { |
107 | | - return nil, xerrors.Errorf("mapfs write error: %w", err) |
| 117 | + return dockerfile.Bytes() |
| 118 | +} |
| 119 | + |
| 120 | +func buildRunInstruction(s string) string { |
| 121 | + pos := strings.Index(s, "/bin/sh -c") |
| 122 | + if pos == -1 { |
| 123 | + return s |
108 | 124 | } |
| 125 | + return "RUN" + s[pos+len("/bin/sh -c"):] |
| 126 | +} |
109 | 127 |
|
110 | | - misconfs, err := a.scanner.Scan(ctx, fsys) |
111 | | - if err != nil { |
112 | | - return nil, xerrors.Errorf("history scan error: %w", err) |
| 128 | +func buildHealthcheckInstruction(health *v1.HealthConfig) string { |
| 129 | + var interval, timeout, startPeriod, retries, command string |
| 130 | + if health.Interval != 0 { |
| 131 | + interval = fmt.Sprintf("--interval=%s ", health.Interval) |
113 | 132 | } |
114 | | - // The result should be a single element as it passes one Dockerfile. |
115 | | - if len(misconfs) != 1 { |
116 | | - return nil, nil |
| 133 | + if health.Timeout != 0 { |
| 134 | + timeout = fmt.Sprintf("--timeout=%s ", health.Timeout) |
117 | 135 | } |
118 | | - |
119 | | - return &analyzer.ConfigAnalysisResult{ |
120 | | - Misconfiguration: &misconfs[0], |
121 | | - }, nil |
| 136 | + if health.StartPeriod != 0 { |
| 137 | + startPeriod = fmt.Sprintf("--startPeriod=%s ", health.StartPeriod) |
| 138 | + } |
| 139 | + if health.Retries != 0 { |
| 140 | + retries = fmt.Sprintf("--retries=%d ", health.Retries) |
| 141 | + } |
| 142 | + command = strings.Join(health.Test, " ") |
| 143 | + command = strings.ReplaceAll(command, "CMD-SHELL", "CMD") |
| 144 | + return fmt.Sprintf("HEALTHCHECK %s%s%s%s%s", interval, timeout, startPeriod, retries, command) |
122 | 145 | } |
123 | 146 |
|
124 | 147 | func (a *historyAnalyzer) Required(_ types.OS) bool { |
|
0 commit comments