podman

Форк
0
295 строк · 10.2 Кб
1
package buildah
2

3
import (
4
	"context"
5
	"fmt"
6
	"io"
7
	"os"
8
	"path/filepath"
9
	"strings"
10

11
	"github.com/containers/buildah/define"
12
	"github.com/containers/buildah/internal/sbom"
13
	"github.com/mattn/go-shellwords"
14
	rspec "github.com/opencontainers/runtime-spec/specs-go"
15
	"github.com/sirupsen/logrus"
16
	"golang.org/x/exp/slices"
17
)
18

19
func stringSliceReplaceAll(slice []string, replacements map[string]string, important []string) (built []string, replacedAnImportantValue bool) {
20
	built = make([]string, 0, len(slice))
21
	for i := range slice {
22
		element := slice[i]
23
		for from, to := range replacements {
24
			previous := element
25
			if element = strings.ReplaceAll(previous, from, to); element != previous {
26
				if len(important) == 0 || slices.Contains(important, from) {
27
					replacedAnImportantValue = true
28
				}
29
			}
30
		}
31
		built = append(built, element)
32
	}
33
	return built, replacedAnImportantValue
34
}
35

36
// sbomScan iterates through the scanning configuration settings, generating
37
// SBOM files and storing them either in the rootfs or in a local file path.
38
func (b *Builder) sbomScan(ctx context.Context, options CommitOptions) (imageFiles, localFiles map[string]string, scansDir string, err error) {
39
	// We'll use a temporary per-container directory for this one.
40
	cdir, err := b.store.ContainerDirectory(b.ContainerID)
41
	if err != nil {
42
		return nil, nil, "", err
43
	}
44
	scansDir, err = os.MkdirTemp(cdir, "buildah-scan")
45
	if err != nil {
46
		return nil, nil, "", err
47
	}
48
	defer func() {
49
		if err != nil {
50
			if err := os.RemoveAll(scansDir); err != nil {
51
				logrus.Warnf("removing temporary directory %q: %v", scansDir, err)
52
			}
53
		}
54
	}()
55

56
	// We may be producing sets of outputs using temporary containers, and
57
	// there's no need to create more than one container for any one
58
	// specific scanner image.
59
	scanners := make(map[string]*Builder)
60
	defer func() {
61
		for _, scanner := range scanners {
62
			scannerID := scanner.ContainerID
63
			if err := scanner.Delete(); err != nil {
64
				logrus.Warnf("removing temporary scanner container %q: %v", scannerID, err)
65
			}
66
		}
67
	}()
68

69
	// Just assume that every scanning method will be looking at the rootfs.
70
	rootfs, err := b.Mount(b.MountLabel)
71
	if err != nil {
72
		return nil, nil, "", err
73
	}
74
	defer func(b *Builder) {
75
		if err := b.Unmount(); err != nil {
76
			logrus.Warnf("unmounting temporary scanner container %q: %v", b.ContainerID, err)
77
		}
78
	}(b)
79

80
	// Iterate through all of the scanning strategies.
81
	for _, scanSpec := range options.SBOMScanOptions {
82
		// Pull the image and create a container we can run the scanner
83
		// in, unless we've done that already for this scanner image.
84
		scanBuilder, ok := scanners[scanSpec.Image]
85
		if !ok {
86
			builderOptions := BuilderOptions{
87
				FromImage:        scanSpec.Image,
88
				ContainerSuffix:  "scanner",
89
				PullPolicy:       scanSpec.PullPolicy,
90
				BlobDirectory:    options.BlobDirectory,
91
				Logger:           b.Logger,
92
				SystemContext:    options.SystemContext,
93
				MountLabel:       b.MountLabel,
94
				ProcessLabel:     b.ProcessLabel,
95
				IDMappingOptions: &b.IDMappingOptions,
96
			}
97
			if scanBuilder, err = NewBuilder(ctx, b.store, builderOptions); err != nil {
98
				return nil, nil, "", fmt.Errorf("creating temporary working container to run scanner: %w", err)
99
			}
100
			scanners[scanSpec.Image] = scanBuilder
101
		}
102
		// Now figure out which commands we need to run.  First, try to
103
		// parse a command ourselves, because syft's image (at least)
104
		// doesn't include a shell.  Build a slice of command slices.
105
		var commands [][]string
106
		for _, commandSpec := range scanSpec.Commands {
107
			// Start by assuming it's shell -c $whatever.
108
			parsedCommand := []string{"/bin/sh", "-c", commandSpec}
109
			if shell := scanBuilder.Shell(); len(shell) != 0 {
110
				parsedCommand = append(append([]string{}, shell...), commandSpec)
111
			}
112
			if !strings.ContainsAny(commandSpec, "<>|") { // An imperfect check for shell redirection being used.
113
				// If we can parse it ourselves, though, prefer to use that result,
114
				// in case the scanner image doesn't include a shell.
115
				if parsed, err := shellwords.Parse(commandSpec); err == nil {
116
					parsedCommand = parsed
117
				}
118
			}
119
			commands = append(commands, parsedCommand)
120
		}
121
		// Set up a list of mounts for the rootfs and whichever context
122
		// directories we're told were used.
123
		const rootfsTargetDir = "/.rootfs"
124
		const scansTargetDir = "/.scans"
125
		const contextsTargetDirPrefix = "/.context"
126
		runMounts := []rspec.Mount{
127
			// Our temporary directory, read-write.
128
			{
129
				Type:        define.TypeBind,
130
				Source:      scansDir,
131
				Destination: scansTargetDir,
132
				Options:     []string{"rw", "z"},
133
			},
134
			// The rootfs, read-only.
135
			{
136
				Type:        define.TypeBind,
137
				Source:      rootfs,
138
				Destination: rootfsTargetDir,
139
				Options:     []string{"ro"},
140
			},
141
		}
142
		// Each context directory, also read-only.
143
		for i := range scanSpec.ContextDir {
144
			contextMount := rspec.Mount{
145
				Type:        define.TypeBind,
146
				Source:      scanSpec.ContextDir[i],
147
				Destination: fmt.Sprintf("%s%d", contextsTargetDirPrefix, i),
148
				Options:     []string{"ro"},
149
			}
150
			runMounts = append(runMounts, contextMount)
151
		}
152
		// Set up run options and mounts one time, and reuse it.
153
		runOptions := RunOptions{
154
			Logger:        b.Logger,
155
			Isolation:     b.Isolation,
156
			SystemContext: options.SystemContext,
157
			Mounts:        runMounts,
158
		}
159
		// We'll have to do some text substitutions so that we run the
160
		// right commands, in the right order, pointing at the right
161
		// mount points.
162
		var resolvedCommands [][]string
163
		var resultFiles []string
164
		for _, command := range commands {
165
			// Each command gets to produce its own file that we'll
166
			// combine later if there's more than one of them.
167
			contextDirScans := 0
168
			for i := range scanSpec.ContextDir {
169
				resultFile := filepath.Join(scansTargetDir, fmt.Sprintf("scan%d.json", len(resultFiles)))
170
				// If the command mentions {CONTEXT}...
171
				resolvedCommand, scansContext := stringSliceReplaceAll(command,
172
					map[string]string{
173
						"{CONTEXT}": fmt.Sprintf("%s%d", contextsTargetDirPrefix, i),
174
						"{OUTPUT}":  resultFile,
175
					},
176
					[]string{"{CONTEXT}"},
177
				)
178
				if !scansContext {
179
					break
180
				}
181
				// ... resolve the path references and add it to the list of commands.
182
				resolvedCommands = append(resolvedCommands, resolvedCommand)
183
				resultFiles = append(resultFiles, resultFile)
184
				contextDirScans++
185
			}
186
			if contextDirScans == 0 {
187
				resultFile := filepath.Join(scansTargetDir, fmt.Sprintf("scan%d.json", len(resultFiles)))
188
				// If the command didn't mention {CONTEXT}, but does mention {ROOTFS}...
189
				resolvedCommand, scansRootfs := stringSliceReplaceAll(command,
190
					map[string]string{
191
						"{ROOTFS}": rootfsTargetDir,
192
						"{OUTPUT}": resultFile,
193
					},
194
					[]string{"{ROOTFS}"},
195
				)
196
				// ... resolve the path references and add that to the list of commands.
197
				if scansRootfs {
198
					resolvedCommands = append(resolvedCommands, resolvedCommand)
199
					resultFiles = append(resultFiles, resultFile)
200
				}
201
			}
202
		}
203
		// Run all of the commands, one after the other, producing one
204
		// or more files named "scan%d.json" in our temporary directory.
205
		for _, resolvedCommand := range resolvedCommands {
206
			logrus.Debugf("Running scan command %q", resolvedCommand)
207
			if err = scanBuilder.Run(resolvedCommand, runOptions); err != nil {
208
				return nil, nil, "", fmt.Errorf("running scanning command %v: %w", resolvedCommand, err)
209
			}
210
		}
211
		// Produce the combined output files that we need to create, if there are any.
212
		var sbomResult, purlResult string
213
		switch {
214
		case scanSpec.ImageSBOMOutput != "":
215
			sbomResult = filepath.Join(scansDir, filepath.Base(scanSpec.ImageSBOMOutput))
216
		case scanSpec.SBOMOutput != "":
217
			sbomResult = filepath.Join(scansDir, filepath.Base(scanSpec.SBOMOutput))
218
		default:
219
			sbomResult = filepath.Join(scansDir, "sbom-result")
220
		}
221
		switch {
222
		case scanSpec.ImagePURLOutput != "":
223
			purlResult = filepath.Join(scansDir, filepath.Base(scanSpec.ImagePURLOutput))
224
		case scanSpec.PURLOutput != "":
225
			purlResult = filepath.Join(scansDir, filepath.Base(scanSpec.PURLOutput))
226
		default:
227
			purlResult = filepath.Join(scansDir, "purl-result")
228
		}
229
		copyFile := func(destination, source string) error {
230
			dst, err := os.Create(destination)
231
			if err != nil {
232
				return err
233
			}
234
			defer dst.Close()
235
			src, err := os.Open(source)
236
			if err != nil {
237
				return err
238
			}
239
			defer src.Close()
240
			if _, err = io.Copy(dst, src); err != nil {
241
				return fmt.Errorf("copying %q to %q: %w", source, destination, err)
242
			}
243
			return nil
244
		}
245
		err = func() error {
246
			for i := range resultFiles {
247
				thisResultFile := filepath.Join(scansDir, filepath.Base(resultFiles[i]))
248
				switch i {
249
				case 0:
250
					// Straight-up copy to create the first version of the final output.
251
					if err = copyFile(sbomResult, thisResultFile); err != nil {
252
						return err
253
					}
254
					// This shouldn't change any contents, but lets us generate the purl file.
255
					err = sbom.Merge(scanSpec.MergeStrategy, thisResultFile, sbomResult, purlResult)
256
				default:
257
					// Hopefully we know how to merge information from the new one into the final output.
258
					err = sbom.Merge(scanSpec.MergeStrategy, sbomResult, thisResultFile, purlResult)
259
				}
260
			}
261
			return err
262
		}()
263
		if err != nil {
264
			return nil, nil, "", err
265
		}
266
		// If these files are supposed to be written to the local filesystem, add
267
		// their contents to the map of files we expect our caller to write.
268
		if scanSpec.SBOMOutput != "" || scanSpec.PURLOutput != "" {
269
			if localFiles == nil {
270
				localFiles = make(map[string]string)
271
			}
272
			if scanSpec.SBOMOutput != "" {
273
				localFiles[scanSpec.SBOMOutput] = sbomResult
274
			}
275
			if scanSpec.PURLOutput != "" {
276
				localFiles[scanSpec.PURLOutput] = purlResult
277
			}
278
		}
279
		// If these files are supposed to be written to the image, create a map of
280
		// their contents so that we can either create a layer diff for them (or
281
		// slipstream them into a squashed layer diff) later.
282
		if scanSpec.ImageSBOMOutput != "" || scanSpec.ImagePURLOutput != "" {
283
			if imageFiles == nil {
284
				imageFiles = make(map[string]string)
285
			}
286
			if scanSpec.ImageSBOMOutput != "" {
287
				imageFiles[scanSpec.ImageSBOMOutput] = sbomResult
288
			}
289
			if scanSpec.ImagePURLOutput != "" {
290
				imageFiles[scanSpec.ImagePURLOutput] = purlResult
291
			}
292
		}
293
	}
294
	return imageFiles, localFiles, scansDir, nil
295
}
296

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.