aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/containers/buildah/util.go
blob: 2f923357c18b4ebbe5766b400c8ce5a90859baf3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
package buildah

import (
	"archive/tar"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/containers/buildah/util"
	"github.com/containers/image/v5/docker/reference"
	"github.com/containers/image/v5/pkg/sysregistriesv2"
	"github.com/containers/image/v5/types"
	"github.com/containers/storage"
	"github.com/containers/storage/pkg/archive"
	"github.com/containers/storage/pkg/chrootarchive"
	"github.com/containers/storage/pkg/idtools"
	"github.com/containers/storage/pkg/pools"
	"github.com/containers/storage/pkg/reexec"
	"github.com/containers/storage/pkg/system"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
	rspec "github.com/opencontainers/runtime-spec/specs-go"
	selinux "github.com/opencontainers/selinux/go-selinux"
	"github.com/opencontainers/selinux/go-selinux/label"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// InitReexec is a wrapper for reexec.Init().  It should be called at
// the start of main(), and if it returns true, main() should return
// immediately.
func InitReexec() bool {
	return reexec.Init()
}

func copyStringStringMap(m map[string]string) map[string]string {
	n := map[string]string{}
	for k, v := range m {
		n[k] = v
	}
	return n
}

func copyStringSlice(s []string) []string {
	t := make([]string, len(s))
	copy(t, s)
	return t
}

func copyHistory(history []v1.History) []v1.History {
	if len(history) == 0 {
		return nil
	}
	h := make([]v1.History, 0, len(history))
	for _, entry := range history {
		created := entry.Created
		if created != nil {
			timestamp := *created
			created = &timestamp
		}
		h = append(h, v1.History{
			Created:    created,
			CreatedBy:  entry.CreatedBy,
			Author:     entry.Author,
			Comment:    entry.Comment,
			EmptyLayer: entry.EmptyLayer,
		})
	}
	return h
}

func convertStorageIDMaps(UIDMap, GIDMap []idtools.IDMap) ([]rspec.LinuxIDMapping, []rspec.LinuxIDMapping) {
	uidmap := make([]rspec.LinuxIDMapping, 0, len(UIDMap))
	gidmap := make([]rspec.LinuxIDMapping, 0, len(GIDMap))
	for _, m := range UIDMap {
		uidmap = append(uidmap, rspec.LinuxIDMapping{
			HostID:      uint32(m.HostID),
			ContainerID: uint32(m.ContainerID),
			Size:        uint32(m.Size),
		})
	}
	for _, m := range GIDMap {
		gidmap = append(gidmap, rspec.LinuxIDMapping{
			HostID:      uint32(m.HostID),
			ContainerID: uint32(m.ContainerID),
			Size:        uint32(m.Size),
		})
	}
	return uidmap, gidmap
}

func convertRuntimeIDMaps(UIDMap, GIDMap []rspec.LinuxIDMapping) ([]idtools.IDMap, []idtools.IDMap) {
	uidmap := make([]idtools.IDMap, 0, len(UIDMap))
	gidmap := make([]idtools.IDMap, 0, len(GIDMap))
	for _, m := range UIDMap {
		uidmap = append(uidmap, idtools.IDMap{
			HostID:      int(m.HostID),
			ContainerID: int(m.ContainerID),
			Size:        int(m.Size),
		})
	}
	for _, m := range GIDMap {
		gidmap = append(gidmap, idtools.IDMap{
			HostID:      int(m.HostID),
			ContainerID: int(m.ContainerID),
			Size:        int(m.Size),
		})
	}
	return uidmap, gidmap
}

// copyFileWithTar returns a function which copies a single file from outside
// of any container, or another container, into our working container, mapping
// read permissions using the passed-in ID maps, writing using the container's
// ID mappings, possibly overridden using the passed-in chownOpts
func (b *Builder) copyFileWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(src, dest string) error {
	if tarIDMappingOptions == nil {
		tarIDMappingOptions = &IDMappingOptions{
			HostUIDMapping: true,
			HostGIDMapping: true,
		}
	}

	var hardlinkChecker util.HardlinkChecker
	return func(src, dest string) error {
		var f *os.File

		logrus.Debugf("copyFileWithTar(%s, %s)", src, dest)
		fi, err := os.Lstat(src)
		if err != nil {
			return errors.Wrapf(err, "error reading attributes of %q", src)
		}

		sysfi, err := system.Lstat(src)
		if err != nil {
			return errors.Wrapf(err, "error reading attributes of %q", src)
		}

		hostUID := sysfi.UID()
		hostGID := sysfi.GID()
		containerUID, containerGID, err := util.GetContainerIDs(tarIDMappingOptions.UIDMap, tarIDMappingOptions.GIDMap, hostUID, hostGID)
		if err != nil {
			return errors.Wrapf(err, "error mapping owner IDs of %q: %d/%d", src, hostUID, hostGID)
		}

		hdr, err := tar.FileInfoHeader(fi, filepath.Base(src))
		if err != nil {
			return errors.Wrapf(err, "error generating tar header for: %q", src)
		}
		chrootedDest, err := filepath.Rel(b.MountPoint, dest)
		if err != nil {
			return errors.Wrapf(err, "error generating relative-to-chroot target name for %q", dest)
		}
		hdr.Name = chrootedDest
		hdr.Uid = int(containerUID)
		hdr.Gid = int(containerGID)

		if fi.Mode().IsRegular() && hdr.Typeflag == tar.TypeReg {
			if linkname := hardlinkChecker.Check(fi); linkname != "" {
				hdr.Typeflag = tar.TypeLink
				hdr.Linkname = linkname
			} else {
				hardlinkChecker.Add(fi, chrootedDest)
				f, err = os.Open(src)
				if err != nil {
					return errors.Wrapf(err, "error opening %q to copy its contents", src)
				}
			}
		}

		if fi.Mode()&os.ModeSymlink == os.ModeSymlink && hdr.Typeflag == tar.TypeSymlink {
			hdr.Typeflag = tar.TypeSymlink
			linkName, err := os.Readlink(src)
			if err != nil {
				return errors.Wrapf(err, "error reading destination from symlink %q", src)
			}
			hdr.Linkname = linkName
		}

		pipeReader, pipeWriter := io.Pipe()
		writer := tar.NewWriter(pipeWriter)
		var copyErr error
		go func(srcFile *os.File) {
			err := writer.WriteHeader(hdr)
			if err != nil {
				logrus.Debugf("error writing header for %s: %v", srcFile.Name(), err)
				copyErr = err
			}
			if srcFile != nil {
				n, err := pools.Copy(writer, srcFile)
				if n != hdr.Size {
					logrus.Debugf("expected to write %d bytes for %s, wrote %d instead", hdr.Size, srcFile.Name(), n)
				}
				if err != nil {
					logrus.Debugf("error copying contents of %s: %v", fi.Name(), err)
					copyErr = err
				}
				if err = srcFile.Close(); err != nil {
					logrus.Debugf("error closing %s: %v", fi.Name(), err)
				}
			}
			if err = writer.Close(); err != nil {
				logrus.Debugf("error closing write pipe for %s: %v", hdr.Name, err)
			}
			pipeWriter.Close()
			pipeWriter = nil
		}(f)

		untar := b.untar(chownOpts, hasher, dryRun)
		err = untar(pipeReader, b.MountPoint)
		if err == nil {
			err = copyErr
		}
		if pipeWriter != nil {
			pipeWriter.Close()
		}
		return err
	}
}

// copyWithTar returns a function which copies a directory tree from outside of
// our container or from another container, into our working container, mapping
// permissions at read-time using the container's ID maps, with ownership at
// write-time possibly overridden using the passed-in chownOpts
func (b *Builder) copyWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(src, dest string) error {
	tar := b.tarPath(tarIDMappingOptions)
	return func(src, dest string) error {
		thisHasher := hasher
		if thisHasher != nil && b.ContentDigester.Hash() != nil {
			thisHasher = io.MultiWriter(thisHasher, b.ContentDigester.Hash())
		}
		if thisHasher == nil {
			thisHasher = b.ContentDigester.Hash()
		}
		untar := b.untar(chownOpts, thisHasher, dryRun)
		rc, err := tar(src)
		if err != nil {
			return errors.Wrapf(err, "error archiving %q for copy", src)
		}
		return untar(rc, dest)
	}
}

// untarPath returns a function which extracts an archive in a specified
// location into our working container, mapping permissions using the
// container's ID maps, possibly overridden using the passed-in chownOpts
func (b *Builder) untarPath(chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(src, dest string) error {
	convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap)
	if dryRun {
		return func(src, dest string) error {
			thisHasher := hasher
			if thisHasher != nil && b.ContentDigester.Hash() != nil {
				thisHasher = io.MultiWriter(thisHasher, b.ContentDigester.Hash())
			}
			if thisHasher == nil {
				thisHasher = b.ContentDigester.Hash()
			}
			f, err := os.Open(src)
			if err != nil {
				return errors.Wrapf(err, "error opening %q", src)
			}
			defer f.Close()
			_, err = io.Copy(thisHasher, f)
			return err
		}
	}
	return func(src, dest string) error {
		thisHasher := hasher
		if thisHasher != nil && b.ContentDigester.Hash() != nil {
			thisHasher = io.MultiWriter(thisHasher, b.ContentDigester.Hash())
		}
		if thisHasher == nil {
			thisHasher = b.ContentDigester.Hash()
		}
		untarPathAndChown := chrootarchive.UntarPathAndChown(chownOpts, thisHasher, convertedUIDMap, convertedGIDMap)
		return untarPathAndChown(src, dest)
	}
}

// tarPath returns a function which creates an archive of a specified location,
// which is often somewhere in the container's filesystem, mapping permissions
// using the container's ID maps, or the passed-in maps if specified
func (b *Builder) tarPath(idMappingOptions *IDMappingOptions) func(path string) (io.ReadCloser, error) {
	var uidmap, gidmap []idtools.IDMap
	if idMappingOptions == nil {
		idMappingOptions = &IDMappingOptions{
			HostUIDMapping: true,
			HostGIDMapping: true,
		}
	}
	convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(idMappingOptions.UIDMap, idMappingOptions.GIDMap)
	tarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap)
	uidmap = tarMappings.UIDs()
	gidmap = tarMappings.GIDs()
	options := &archive.TarOptions{
		Compression: archive.Uncompressed,
		UIDMaps:     uidmap,
		GIDMaps:     gidmap,
	}
	return func(path string) (io.ReadCloser, error) {
		return archive.TarWithOptions(path, options)
	}
}

// untar returns a function which extracts an archive stream to a specified
// location in the container's filesystem, mapping permissions using the
// container's ID maps, possibly overridden using the passed-in chownOpts
func (b *Builder) untar(chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(tarArchive io.ReadCloser, dest string) error {
	convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap)
	untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap)
	options := &archive.TarOptions{
		UIDMaps:   untarMappings.UIDs(),
		GIDMaps:   untarMappings.GIDs(),
		ChownOpts: chownOpts,
	}
	untar := chrootarchive.Untar
	if dryRun {
		untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
			if _, err := io.Copy(ioutil.Discard, tarArchive); err != nil {
				return errors.Wrapf(err, "error digesting tar stream")
			}
			return nil
		}
	}
	originalUntar := untar
	untarWithHasher := func(tarArchive io.Reader, dest string, options *archive.TarOptions, untarHasher io.Writer) error {
		reader := tarArchive
		if untarHasher != nil {
			reader = io.TeeReader(tarArchive, untarHasher)
		}
		return originalUntar(reader, dest, options)
	}
	return func(tarArchive io.ReadCloser, dest string) error {
		thisHasher := hasher
		if thisHasher != nil && b.ContentDigester.Hash() != nil {
			thisHasher = io.MultiWriter(thisHasher, b.ContentDigester.Hash())
		}
		if thisHasher == nil {
			thisHasher = b.ContentDigester.Hash()
		}
		err := untarWithHasher(tarArchive, dest, options, thisHasher)
		if err2 := tarArchive.Close(); err2 != nil {
			if err == nil {
				err = err2
			}
		}
		return err
	}
}

// isRegistryBlocked checks if the named registry is marked as blocked
func isRegistryBlocked(registry string, sc *types.SystemContext) (bool, error) {
	reginfo, err := sysregistriesv2.FindRegistry(sc, registry)
	if err != nil {
		return false, errors.Wrapf(err, "unable to parse the registries configuration (%s)", sysregistriesv2.ConfigPath(sc))
	}
	if reginfo != nil {
		if reginfo.Blocked {
			logrus.Debugf("registry %q is marked as blocked in registries configuration %q", registry, sysregistriesv2.ConfigPath(sc))
		} else {
			logrus.Debugf("registry %q is not marked as blocked in registries configuration %q", registry, sysregistriesv2.ConfigPath(sc))
		}
		return reginfo.Blocked, nil
	}
	logrus.Debugf("registry %q is not listed in registries configuration %q, assuming it's not blocked", registry, sysregistriesv2.ConfigPath(sc))
	return false, nil
}

// isReferenceSomething checks if the registry part of a reference is insecure or blocked
func isReferenceSomething(ref types.ImageReference, sc *types.SystemContext, what func(string, *types.SystemContext) (bool, error)) (bool, error) {
	if ref != nil && ref.DockerReference() != nil {
		if named, ok := ref.DockerReference().(reference.Named); ok {
			if domain := reference.Domain(named); domain != "" {
				return what(domain, sc)
			}
		}
	}
	return false, nil
}

// isReferenceBlocked checks if the registry part of a reference is blocked
func isReferenceBlocked(ref types.ImageReference, sc *types.SystemContext) (bool, error) {
	if ref != nil && ref.Transport() != nil {
		switch ref.Transport().Name() {
		case "docker":
			return isReferenceSomething(ref, sc, isRegistryBlocked)
		}
	}
	return false, nil
}

// ReserveSELinuxLabels reads containers storage and reserves SELinux containers
// fall all existing buildah containers
func ReserveSELinuxLabels(store storage.Store, id string) error {
	if selinux.GetEnabled() {
		containers, err := store.Containers()
		if err != nil {
			return errors.Wrapf(err, "error getting list of containers")
		}

		for _, c := range containers {
			if id == c.ID {
				continue
			} else {
				b, err := OpenBuilder(store, c.ID)
				if err != nil {
					if os.IsNotExist(errors.Cause(err)) {
						// Ignore not exist errors since containers probably created by other tool
						// TODO, we need to read other containers json data to reserve their SELinux labels
						continue
					}
					return err
				}
				// Prevent different containers from using same MCS label
				if err := label.ReserveLabel(b.ProcessLabel); err != nil {
					return errors.Wrapf(err, "error reserving SELinux label %q", b.ProcessLabel)
				}
			}
		}
	}
	return nil
}