From 1930f5d7093e84cdcc9e327e296f4733f32b20c8 Mon Sep 17 00:00:00 2001 From: umohnani8 Date: Fri, 25 May 2018 11:30:57 -0400 Subject: Vendor in latest projectatomic/buildah buildah fixed its probelm where it was not pulling in the ENV of the base image. This pulls that change into libpod as well. Signed-off-by: umohnani8 Closes: #832 Approved by: mheon --- vendor/github.com/projectatomic/buildah/add.go | 79 +- vendor/github.com/projectatomic/buildah/buildah.go | 78 +- vendor/github.com/projectatomic/buildah/image.go | 7 +- .../projectatomic/buildah/imagebuildah/build.go | 64 +- vendor/github.com/projectatomic/buildah/import.go | 16 + vendor/github.com/projectatomic/buildah/new.go | 35 +- .../projectatomic/buildah/pkg/cli/common.go | 66 +- vendor/github.com/projectatomic/buildah/run.go | 993 ++++++++++++++++++--- vendor/github.com/projectatomic/buildah/util.go | 182 +++- .../github.com/projectatomic/buildah/util/types.go | 10 + 10 files changed, 1296 insertions(+), 234 deletions(-) create mode 100644 vendor/github.com/projectatomic/buildah/util/types.go (limited to 'vendor/github.com') diff --git a/vendor/github.com/projectatomic/buildah/add.go b/vendor/github.com/projectatomic/buildah/add.go index 4fab5a8d7..ee2cf253a 100644 --- a/vendor/github.com/projectatomic/buildah/add.go +++ b/vendor/github.com/projectatomic/buildah/add.go @@ -12,6 +12,7 @@ import ( "time" "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/idtools" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/projectatomic/libpod/pkg/chrootuser" @@ -26,7 +27,7 @@ type AddAndCopyOptions struct { // addURL copies the contents of the source URL to the destination. This is // its own function so that deferred closes happen after we're done pulling // down each item of potentially many. -func addURL(destination, srcurl string) error { +func addURL(destination, srcurl string, owner idtools.IDPair) error { logrus.Debugf("saving %q to %q", srcurl, destination) resp, err := http.Get(srcurl) if err != nil { @@ -37,6 +38,9 @@ func addURL(destination, srcurl string) error { if err != nil { return errors.Wrapf(err, "error creating %q", destination) } + if err = f.Chown(owner.UID, owner.GID); err != nil { + return errors.Wrapf(err, "error setting owner of %q", destination) + } if last := resp.Header.Get("Last-Modified"); last != "" { if mtime, err2 := time.Parse(time.RFC1123, last); err2 != nil { logrus.Debugf("error parsing Last-Modified time %q: %v", last, err2) @@ -80,11 +84,17 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption if err != nil { return err } + containerOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} + hostUID, hostGID, err := getHostIDs(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap, user.UID, user.GID) + if err != nil { + return err + } + hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} dest := mountPoint if destination != "" && filepath.IsAbs(destination) { dest = filepath.Join(dest, destination) } else { - if err = ensureDir(filepath.Join(dest, b.WorkDir()), user, 0755); err != nil { + if err = idtools.MkdirAllAndChownNew(filepath.Join(dest, b.WorkDir()), 0755, hostOwner); err != nil { return err } dest = filepath.Join(dest, b.WorkDir(), destination) @@ -93,7 +103,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption // with a '/', create it so that we can be sure that it's a directory, // and any files we're copying will be placed in the directory. if len(destination) > 0 && destination[len(destination)-1] == os.PathSeparator { - if err = ensureDir(dest, user, 0755); err != nil { + if err = idtools.MkdirAllAndChownNew(dest, 0755, hostOwner); err != nil { return err } } @@ -112,6 +122,9 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption if len(source) > 1 && (destfi == nil || !destfi.IsDir()) { return errors.Errorf("destination %q is not a directory", dest) } + copyFileWithTar := b.copyFileWithTar(&containerOwner) + copyWithTar := b.copyWithTar(&containerOwner) + untarPath := b.untarPath(nil) for _, src := range source { if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { // We assume that source is a file, and we're copying @@ -127,10 +140,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption if destfi != nil && destfi.IsDir() { d = filepath.Join(dest, path.Base(url.Path)) } - if err := addURL(d, src); err != nil { - return err - } - if err := setOwner("", d, user); err != nil { + if err := addURL(d, src, hostOwner); err != nil { return err } continue @@ -153,16 +163,13 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption // the source directory into the target directory. Try // to create it first, so that if there's a problem, // we'll discover why that won't work. - if err = ensureDir(dest, user, 0755); err != nil { + if err = idtools.MkdirAllAndChownNew(dest, 0755, hostOwner); err != nil { return err } logrus.Debugf("copying %q to %q", gsrc+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") if err := copyWithTar(gsrc, dest); err != nil { return errors.Wrapf(err, "error copying %q to %q", gsrc, dest) } - if err := setOwner(gsrc, dest, user); err != nil { - return err - } continue } if !extract || !archive.IsArchivePath(gsrc) { @@ -178,9 +185,6 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption if err := copyFileWithTar(gsrc, d); err != nil { return errors.Wrapf(err, "error copying %q to %q", gsrc, d) } - if err := setOwner(gsrc, d, user); err != nil { - return err - } continue } // We're extracting an archive into the destination directory. @@ -205,49 +209,16 @@ func (b *Builder) user(mountPoint string, userspec string) (specs.User, error) { GID: gid, Username: userspec, } - return u, err -} - -// setOwner sets the uid and gid owners of a given path. -func setOwner(src, dest string, user specs.User) error { - fid, err := os.Stat(dest) - if err != nil { - return errors.Wrapf(err, "error reading %q", dest) - } - if !fid.IsDir() || src == "" { - if err := os.Lchown(dest, int(user.UID), int(user.GID)); err != nil { - return errors.Wrapf(err, "error setting ownership of %q", dest) - } - return nil - } - err = filepath.Walk(src, func(p string, info os.FileInfo, we error) error { - relPath, err2 := filepath.Rel(src, p) + if !strings.Contains(userspec, ":") { + groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) if err2 != nil { - return errors.Wrapf(err2, "error getting relative path of %q to set ownership on destination", p) - } - if relPath != "." { - absPath := filepath.Join(dest, relPath) - if err2 := os.Lchown(absPath, int(user.UID), int(user.GID)); err != nil { - return errors.Wrapf(err2, "error setting ownership of %q", absPath) + if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil { + err = err2 } + } else { + u.AdditionalGids = groups } - return nil - }) - if err != nil { - return errors.Wrapf(err, "error walking dir %q to set ownership", src) - } - return nil -} -// ensureDir creates a directory if it doesn't exist, setting ownership and permissions as passed by user and perm. -func ensureDir(path string, user specs.User, perm os.FileMode) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.MkdirAll(path, perm); err != nil { - return errors.Wrapf(err, "error ensuring directory %q exists", path) - } - if err := os.Chown(path, int(user.UID), int(user.GID)); err != nil { - return errors.Wrapf(err, "error setting ownership of %q", path) - } } - return nil + return u, err } diff --git a/vendor/github.com/projectatomic/buildah/buildah.go b/vendor/github.com/projectatomic/buildah/buildah.go index b05e5deb1..e90e2ee68 100644 --- a/vendor/github.com/projectatomic/buildah/buildah.go +++ b/vendor/github.com/projectatomic/buildah/buildah.go @@ -67,6 +67,37 @@ func (p PullPolicy) String() string { return fmt.Sprintf("unrecognized policy %d", p) } +// NetworkConfigurationPolicy takes the value NetworkDefault, NetworkDisabled, +// or NetworkEnabled. +type NetworkConfigurationPolicy int + +const ( + // NetworkDefault is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that the default behavior should be used. + NetworkDefault NetworkConfigurationPolicy = iota + // NetworkDisabled is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that network interfaces should NOT be configured for + // newly-created network namespaces. + NetworkDisabled + // NetworkEnabled is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that network interfaces should be configured for + // newly-created network namespaces. + NetworkEnabled +) + +// String formats a NetworkConfigurationPolicy as a string. +func (p NetworkConfigurationPolicy) String() string { + switch p { + case NetworkDefault: + return "NetworkDefault" + case NetworkDisabled: + return "NetworkDisabled" + case NetworkEnabled: + return "NetworkEnabled" + } + return fmt.Sprintf("unknown NetworkConfigurationPolicy %d", p) +} + // Builder objects are used to represent containers which are being used to // build images. They also carry potential updates which will be applied to // the image's configuration when the container's contents are used to build an @@ -116,6 +147,23 @@ type Builder struct { // DefaultMountsFilePath is the file path holding the mounts to be mounted in "host-path:container-path" format. DefaultMountsFilePath string `json:"defaultMountsFilePath,omitempty"` + // NamespaceOptions controls how we set up the namespaces for processes that we run in the container. + NamespaceOptions NamespaceOptions + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string + // ID mapping options to use when running processes in the container with non-host user namespaces. + IDMappingOptions IDMappingOptions + CommonBuildOpts *CommonBuildOptions } @@ -136,6 +184,11 @@ type BuilderInfo struct { OCIv1 v1.Image Docker docker.V2Image DefaultMountsFilePath string + NamespaceOptions NamespaceOptions + ConfigureNetwork string + CNIPluginPath string + CNIConfigDir string + IDMappingOptions IDMappingOptions } // GetBuildInfo gets a pointer to a Builder object and returns a BuilderInfo object from it. @@ -156,6 +209,11 @@ func GetBuildInfo(b *Builder) BuilderInfo { OCIv1: b.OCIv1, Docker: b.Docker, DefaultMountsFilePath: b.DefaultMountsFilePath, + NamespaceOptions: b.NamespaceOptions, + ConfigureNetwork: fmt.Sprintf("%v", b.ConfigureNetwork), + CNIPluginPath: b.CNIPluginPath, + CNIConfigDir: b.CNIConfigDir, + IDMappingOptions: b.IDMappingOptions, } } @@ -250,7 +308,25 @@ type BuilderOptions struct { // DefaultMountsFilePath is the file path holding the mounts to be // mounted in "host-path:container-path" format DefaultMountsFilePath string - CommonBuildOpts *CommonBuildOptions + // NamespaceOptions controls how we set up namespaces for processes that + // we might need to run using the container's root filesystem. + NamespaceOptions NamespaceOptions + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string + // ID mapping options to use if we're setting up our own user namespace. + IDMappingOptions *IDMappingOptions + + CommonBuildOpts *CommonBuildOptions } // ImportOptions are used to initialize a Builder from an existing container diff --git a/vendor/github.com/projectatomic/buildah/image.go b/vendor/github.com/projectatomic/buildah/image.go index c66a5cd08..1cb5d7022 100644 --- a/vendor/github.com/projectatomic/buildah/image.go +++ b/vendor/github.com/projectatomic/buildah/image.go @@ -54,6 +54,7 @@ type containerImageRef struct { preferredManifestType string exporting bool squash bool + tarPath func(path string) (io.ReadCloser, error) } type containerImageSource struct { @@ -132,10 +133,7 @@ func (i *containerImageRef) extractRootfs() (io.ReadCloser, error) { if err != nil { return nil, errors.Wrapf(err, "error extracting container %q", i.containerID) } - tarOptions := &archive.TarOptions{ - Compression: archive.Uncompressed, - } - rc, err := archive.TarWithOptions(mountPoint, tarOptions) + rc, err := i.tarPath(mountPoint) if err != nil { return nil, errors.Wrapf(err, "error extracting container %q", i.containerID) } @@ -623,6 +621,7 @@ func (b *Builder) makeImageRef(manifestType string, exporting bool, squash bool, preferredManifestType: manifestType, exporting: exporting, squash: squash, + tarPath: b.tarPath(), } return ref, nil } diff --git a/vendor/github.com/projectatomic/buildah/imagebuildah/build.go b/vendor/github.com/projectatomic/buildah/imagebuildah/build.go index 5472c1db5..a2e2912e3 100644 --- a/vendor/github.com/projectatomic/buildah/imagebuildah/build.go +++ b/vendor/github.com/projectatomic/buildah/imagebuildah/build.go @@ -107,8 +107,26 @@ type BuildOptions struct { // Accepted values are OCIv1ImageFormat and Dockerv2ImageFormat. OutputFormat string // SystemContext holds parameters used for authentication. - SystemContext *types.SystemContext - CommonBuildOpts *buildah.CommonBuildOptions + SystemContext *types.SystemContext + // NamespaceOptions controls how we set up namespaces processes that we + // might need when handling RUN instructions. + NamespaceOptions []buildah.NamespaceOption + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork buildah.NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string + // ID mapping options to use if we're setting up our own user namespace + // when handling RUN instructions. + IDMappingOptions *buildah.IDMappingOptions + CommonBuildOpts *buildah.CommonBuildOptions // DefaultMountsFilePath is the file path holding the mounts to be mounted in "host-path:container-path" format DefaultMountsFilePath string // IIDFile tells the builder to write the image ID to the specified file @@ -154,6 +172,11 @@ type Executor struct { volumeCache map[string]string volumeCacheInfo map[string]os.FileInfo reportWriter io.Writer + namespaceOptions []buildah.NamespaceOption + configureNetwork buildah.NetworkConfigurationPolicy + cniPluginPath string + cniConfigDir string + idmappingOptions *buildah.IDMappingOptions commonBuildOptions *buildah.CommonBuildOptions defaultMountsFilePath string iidfile string @@ -413,17 +436,21 @@ func (b *Executor) Run(run imagebuilder.Run, config docker.Config) error { return errors.Errorf("no build container available") } options := buildah.RunOptions{ - Hostname: config.Hostname, - Runtime: b.runtime, - Args: b.runtimeArgs, - Mounts: convertMounts(b.transientMounts), - Env: config.Env, - User: config.User, - WorkingDir: config.WorkingDir, - Entrypoint: config.Entrypoint, - Cmd: config.Cmd, - NetworkDisabled: config.NetworkDisabled, - Quiet: b.quiet, + Hostname: config.Hostname, + Runtime: b.runtime, + Args: b.runtimeArgs, + Mounts: convertMounts(b.transientMounts), + Env: config.Env, + User: config.User, + WorkingDir: config.WorkingDir, + Entrypoint: config.Entrypoint, + Cmd: config.Cmd, + Quiet: b.quiet, + } + if config.NetworkDisabled { + options.ConfigureNetwork = buildah.NetworkDisabled + } else { + options.ConfigureNetwork = buildah.NetworkEnabled } args := run.Args @@ -489,6 +516,11 @@ func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) { out: options.Out, err: options.Err, reportWriter: options.ReportWriter, + namespaceOptions: options.NamespaceOptions, + configureNetwork: options.ConfigureNetwork, + cniPluginPath: options.CNIPluginPath, + cniConfigDir: options.CNIConfigDir, + idmappingOptions: options.IDMappingOptions, commonBuildOptions: options.CommonBuildOpts, defaultMountsFilePath: options.DefaultMountsFilePath, iidfile: options.IIDFile, @@ -537,6 +569,11 @@ func (b *Executor) Prepare(ctx context.Context, ib *imagebuilder.Builder, node * SignaturePolicyPath: b.signaturePolicyPath, ReportWriter: b.reportWriter, SystemContext: b.systemContext, + NamespaceOptions: b.namespaceOptions, + ConfigureNetwork: b.configureNetwork, + CNIPluginPath: b.cniPluginPath, + CNIConfigDir: b.cniConfigDir, + IDMappingOptions: b.idmappingOptions, CommonBuildOpts: b.commonBuildOptions, DefaultMountsFilePath: b.defaultMountsFilePath, } @@ -668,7 +705,6 @@ func (b *Executor) Commit(ctx context.Context, ib *imagebuilder.Builder) (err er for p := range config.ExposedPorts { b.builder.SetPort(string(p)) } - b.builder.ClearEnv() for _, envSpec := range config.Env { spec := strings.SplitN(envSpec, "=", 2) b.builder.SetEnv(spec[0], spec[1]) diff --git a/vendor/github.com/projectatomic/buildah/import.go b/vendor/github.com/projectatomic/buildah/import.go index 2a48d2c34..90b1b90b9 100644 --- a/vendor/github.com/projectatomic/buildah/import.go +++ b/vendor/github.com/projectatomic/buildah/import.go @@ -16,6 +16,7 @@ func importBuilderDataFromImage(ctx context.Context, store storage.Store, system manifest := []byte{} config := []byte{} imageName := "" + uidmap, gidmap := convertStorageIDMaps(storage.DefaultStoreOptions.UIDMap, storage.DefaultStoreOptions.GIDMap) if imageID != "" { ref, err := is.Transport.ParseStoreReference(store, imageID) @@ -39,6 +40,13 @@ func importBuilderDataFromImage(ctx context.Context, store storage.Store, system if len(img.Names) > 0 { imageName = img.Names[0] } + if img.TopLayer != "" { + layer, err4 := store.Layer(img.TopLayer) + if err4 != nil { + return nil, errors.Wrapf(err4, "error reading information about image's top layer") + } + uidmap, gidmap = convertStorageIDMaps(layer.UIDMap, layer.GIDMap) + } } } @@ -53,6 +61,13 @@ func importBuilderDataFromImage(ctx context.Context, store storage.Store, system ContainerID: containerID, ImageAnnotations: map[string]string{}, ImageCreatedBy: "", + NamespaceOptions: DefaultNamespaceOptions(), + IDMappingOptions: IDMappingOptions{ + HostUIDMapping: len(uidmap) == 0, + HostGIDMapping: len(uidmap) == 0, + UIDMap: uidmap, + GIDMap: gidmap, + }, } builder.initConfig() @@ -87,6 +102,7 @@ func importBuilder(ctx context.Context, store storage.Store, options ImportOptio if builder.FromImage != "" { builder.Docker.ContainerConfig.Image = builder.FromImage } + builder.IDMappingOptions.UIDMap, builder.IDMappingOptions.GIDMap = convertStorageIDMaps(c.UIDMap, c.GIDMap) err = builder.Save() if err != nil { diff --git a/vendor/github.com/projectatomic/buildah/new.go b/vendor/github.com/projectatomic/buildah/new.go index 82de524c0..edc1b898e 100644 --- a/vendor/github.com/projectatomic/buildah/new.go +++ b/vendor/github.com/projectatomic/buildah/new.go @@ -54,7 +54,7 @@ func reserveSELinuxLabels(store storage.Store, id string) error { } return err } - // Prevent containers from using same MCS Label + // Prevent different containers from using same MCS label if err := label.ReserveLabel(b.ProcessLabel); err != nil { return err } @@ -133,6 +133,22 @@ func imageManifestAndConfig(ctx context.Context, ref types.ImageReference, syste return nil, nil, nil } +func newContainerIDMappingOptions(idmapOptions *IDMappingOptions) storage.IDMappingOptions { + var options storage.IDMappingOptions + if idmapOptions != nil { + options.HostUIDMapping = idmapOptions.HostUIDMapping + options.HostGIDMapping = idmapOptions.HostGIDMapping + uidmap, gidmap := convertRuntimeIDMaps(idmapOptions.UIDMap, idmapOptions.GIDMap) + if len(uidmap) > 0 && len(gidmap) > 0 { + options.UIDMap = uidmap + options.GIDMap = gidmap + } else { + options.HostUIDMapping = true + options.HostGIDMapping = true + } + } + return options +} func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions) (*Builder, error) { var ref types.ImageReference var img *storage.Image @@ -258,6 +274,8 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions } coptions := storage.ContainerOptions{} + coptions.IDMappingOptions = newContainerIDMappingOptions(options.IDMappingOptions) + container, err := store.CreateContainer("", []string{name}, imageID, "", "", &coptions) if err != nil { return nil, errors.Wrapf(err, "error creating container") @@ -278,6 +296,9 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions if err != nil { return nil, err } + uidmap, gidmap := convertStorageIDMaps(container.UIDMap, container.GIDMap) + namespaceOptions := DefaultNamespaceOptions() + namespaceOptions.AddOrReplace(options.NamespaceOptions...) builder := &Builder{ store: store, @@ -293,7 +314,17 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions ProcessLabel: processLabel, MountLabel: mountLabel, DefaultMountsFilePath: options.DefaultMountsFilePath, - CommonBuildOpts: options.CommonBuildOpts, + NamespaceOptions: namespaceOptions, + ConfigureNetwork: options.ConfigureNetwork, + CNIPluginPath: options.CNIPluginPath, + CNIConfigDir: options.CNIConfigDir, + IDMappingOptions: IDMappingOptions{ + HostUIDMapping: len(uidmap) == 0, + HostGIDMapping: len(uidmap) == 0, + UIDMap: uidmap, + GIDMap: gidmap, + }, + CommonBuildOpts: options.CommonBuildOpts, } if options.Mount { diff --git a/vendor/github.com/projectatomic/buildah/pkg/cli/common.go b/vendor/github.com/projectatomic/buildah/pkg/cli/common.go index e65dba2bd..e4a30a315 100644 --- a/vendor/github.com/projectatomic/buildah/pkg/cli/common.go +++ b/vendor/github.com/projectatomic/buildah/pkg/cli/common.go @@ -5,11 +5,65 @@ package cli // that vendor in this code can use them too. import ( - "github.com/projectatomic/buildah/imagebuildah" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/projectatomic/buildah" + "github.com/projectatomic/buildah/util" "github.com/urfave/cli" ) var ( + usernsFlags = []cli.Flag{ + cli.StringFlag{ + Name: "userns", + Usage: "'container', `path` of user namespace to join, or 'host'", + }, + cli.StringSliceFlag{ + Name: "userns-uid-map", + Usage: "`containerID:hostID:length` UID mapping to use in user namespace", + }, + cli.StringSliceFlag{ + Name: "userns-gid-map", + Usage: "`containerID:hostID:length` GID mapping to use in user namespace", + }, + cli.StringFlag{ + Name: "userns-uid-map-user", + Usage: "`name` of entries from /etc/subuid to use to set user namespace UID mapping", + }, + cli.StringFlag{ + Name: "userns-gid-map-group", + Usage: "`name` of entries from /etc/subgid to use to set user namespace GID mapping", + }, + } + + NamespaceFlags = []cli.Flag{ + cli.StringFlag{ + Name: string(specs.IPCNamespace), + Usage: "'container', `path` of IPC namespace to join, or 'host'", + }, + cli.StringFlag{ + Name: string(specs.NetworkNamespace) + ", net", + Usage: "'container', `path` of network namespace to join, or 'host'", + }, + cli.StringFlag{ + Name: "cni-config-dir", + Usage: "`directory` of CNI configuration files", + Value: util.DefaultCNIConfigDir, + }, + cli.StringFlag{ + Name: "cni-plugin-path", + Usage: "`path` of CNI network plugins", + Value: util.DefaultCNIPluginPath, + }, + cli.StringFlag{ + Name: string(specs.PIDNamespace), + Usage: "'container', `path` of PID namespace to join, or 'host'", + }, + cli.StringFlag{ + Name: string(specs.UTSNamespace), + Usage: "'container', `path` of UTS namespace to join, or 'host'", + }, + } + BudFlags = []cli.Flag{ cli.StringSliceFlag{ Name: "annotation", @@ -55,7 +109,7 @@ var ( }, cli.StringFlag{ Name: "iidfile", - Usage: "Write the image ID to the file", + Usage: "`file` to write the image ID to", }, cli.StringSliceFlag{ Name: "label", @@ -84,7 +138,7 @@ var ( cli.StringFlag{ Name: "runtime", Usage: "`path` to an alternate runtime", - Value: imagebuildah.DefaultRuntime, + Value: buildah.DefaultRuntime, }, cli.StringSliceFlag{ Name: "runtime-flag", @@ -100,7 +154,7 @@ var ( }, cli.StringSliceFlag{ Name: "tag, t", - Usage: "`tag` to apply to the built image", + Usage: "tagged `name` to apply to the built image", }, cli.BoolTFlag{ Name: "tls-verify", @@ -108,7 +162,7 @@ var ( }, } - FromAndBudFlags = []cli.Flag{ + FromAndBudFlags = append(append([]cli.Flag{ cli.StringSliceFlag{ Name: "add-host", Usage: "add a custom host-to-IP mapping (host:ip) (default [])", @@ -162,5 +216,5 @@ var ( Name: "volume, v", Usage: "bind mount a volume into the container (default [])", }, - } + }, usernsFlags...), NamespaceFlags...) ) diff --git a/vendor/github.com/projectatomic/buildah/run.go b/vendor/github.com/projectatomic/buildah/run.go index 0af21b7f0..4a962a0d6 100644 --- a/vendor/github.com/projectatomic/buildah/run.go +++ b/vendor/github.com/projectatomic/buildah/run.go @@ -11,13 +11,18 @@ import ( "os" "os/exec" "path/filepath" + "runtime" + "sort" "strconv" "strings" "sync" "syscall" "time" + "github.com/containernetworking/cni/libcni" + "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/ioutils" + "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/reexec" "github.com/docker/docker/profiles/seccomp" units "github.com/docker/go-units" @@ -26,6 +31,7 @@ import ( "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/projectatomic/buildah/util" "github.com/projectatomic/libpod/pkg/secrets" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -69,11 +75,44 @@ func (t TerminalPolicy) String() string { return fmt.Sprintf("unrecognized terminal setting %d", t) } +// NamespaceOption controls how we set up a namespace when launching processes. +type NamespaceOption struct { + // Name specifies the type of namespace, typically matching one of the + // ...Namespace constants defined in + // github.com/opencontainers/runtime-spec/specs-go. + Name string + // Host is used to force our processes to use the host's namespace of + // this type. + Host bool + // Path is the path of the namespace to attach our process to, if Host + // is not set. If Host is not set and Path is also empty, a new + // namespace will be created for the process that we're starting. + // If Name is specs.NetworkNamespace, if Path doesn't look like an + // absolute path, it is treated as a comma-separated list of CNI + // configuration names which will be selected from among all of the CNI + // network configurations which we find. + Path string +} + +// NamespaceOptions provides some helper methods for a slice of NamespaceOption +// structs. +type NamespaceOptions []NamespaceOption + +// IDMappingOptions controls how we set up UID/GID mapping when we set up a +// user namespace. +type IDMappingOptions struct { + HostUIDMapping bool + HostGIDMapping bool + UIDMap []specs.LinuxIDMapping + GIDMap []specs.LinuxIDMapping +} + // RunOptions can be used to alter how a command is run in the container. type RunOptions struct { // Hostname is the hostname we set for the running container. Hostname string - // Runtime is the name of the command to run. It should accept the same arguments that runc does. + // Runtime is the name of the command to run. It should accept the same arguments + // that runc does, and produce similar output. Runtime string // Args adds global arguments for the runtime. Args []string @@ -91,8 +130,20 @@ type RunOptions struct { Cmd []string // Entrypoint is an override for the configured entry point. Entrypoint []string - // NetworkDisabled puts the container into its own network namespace. - NetworkDisabled bool + // NamespaceOptions controls how we set up the namespaces for the process. + NamespaceOptions NamespaceOptions + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string // Terminal provides a way to specify whether or not the command should // be run with a pseudoterminal. By default (DefaultTerminal), a // terminal is used if os.Stdout is connected to a terminal, but that @@ -103,6 +154,59 @@ type RunOptions struct { Quiet bool } +// DefaultNamespaceOptions returns the default namespace settings from the +// runtime-tools generator library. +func DefaultNamespaceOptions() NamespaceOptions { + options := NamespaceOptions{ + {Name: string(specs.CgroupNamespace), Host: true}, + {Name: string(specs.IPCNamespace), Host: true}, + {Name: string(specs.MountNamespace), Host: true}, + {Name: string(specs.NetworkNamespace), Host: true}, + {Name: string(specs.PIDNamespace), Host: true}, + {Name: string(specs.UserNamespace), Host: true}, + {Name: string(specs.UTSNamespace), Host: true}, + } + g := generate.New() + spec := g.Spec() + if spec.Linux != nil { + for _, ns := range spec.Linux.Namespaces { + options.AddOrReplace(NamespaceOption{ + Name: string(ns.Type), + Path: ns.Path, + }) + } + } + return options +} + +// Find the configuration for the namespace of the given type. If there are +// duplicates, find the _last_ one of the type, since we assume it was appended +// more recently. +func (n *NamespaceOptions) Find(namespace string) *NamespaceOption { + for i := range *n { + j := len(*n) - 1 - i + if (*n)[j].Name == namespace { + return &((*n)[j]) + } + } + return nil +} + +// AddOrReplace either adds or replaces the configuration for a given namespace. +func (n *NamespaceOptions) AddOrReplace(options ...NamespaceOption) { +nextOption: + for _, option := range options { + for i := range *n { + j := len(*n) - 1 - i + if (*n)[j].Name == option.Name { + (*n)[j] = option + continue nextOption + } + } + *n = append(*n, option) + } +} + func addRlimits(ulimit []string, g *generate.Generator) error { var ( ul *units.Ulimit @@ -179,8 +283,8 @@ func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) return nil } -func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string) error { - // The passed-in mounts matter the most to us. +func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions NamespaceOptions) error { + // Start building a new list of mounts. mounts := make([]specs.Mount, len(optionMounts)) copy(mounts, optionMounts) haveMount := func(destination string) bool { @@ -192,65 +296,156 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts } return false } - // Add mounts from the generated list, unless they conflict. + + // Copy mounts from the generated list. + mountCgroups := true + specMounts := []specs.Mount{} for _, specMount := range spec.Mounts { + // Override some of the mounts from the generated list if we're doing different things with namespaces. if specMount.Destination == "/dev/shm" { specMount.Options = []string{"nosuid", "noexec", "nodev", "mode=1777", "size=" + shmSize} + user := namespaceOptions.Find(string(specs.UserNamespace)) + ipc := namespaceOptions.Find(string(specs.IPCNamespace)) + if (ipc == nil || ipc.Host) && (user != nil && !user.Host) { + if _, err := os.Stat("/dev/shm"); err != nil && os.IsNotExist(err) { + continue + } + specMount = specs.Mount{ + Source: "/dev/shm", + Type: "bind", + Destination: "/dev/shm", + Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev"}, + } + } } - if haveMount(specMount.Destination) { - // Already have something to mount there, so skip this one. - continue + if specMount.Destination == "/dev/mqueue" { + user := namespaceOptions.Find(string(specs.UserNamespace)) + ipc := namespaceOptions.Find(string(specs.IPCNamespace)) + if (ipc == nil || ipc.Host) && (user != nil && !user.Host) { + if _, err := os.Stat("/dev/mqueue"); err != nil && os.IsNotExist(err) { + continue + } + specMount = specs.Mount{ + Source: "/dev/mqueue", + Type: "bind", + Destination: "/dev/mqueue", + Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev"}, + } + } } - mounts = append(mounts, specMount) - } - // Add bind mounts for important files, unless they conflict. - for dest, src := range bindFiles { - if haveMount(dest) { - // Already have something to mount there, so skip this one. - continue + if specMount.Destination == "/sys" { + user := namespaceOptions.Find(string(specs.UserNamespace)) + net := namespaceOptions.Find(string(specs.NetworkNamespace)) + if (net == nil || net.Host) && (user != nil && !user.Host) { + mountCgroups = false + if _, err := os.Stat("/sys"); err != nil && os.IsNotExist(err) { + continue + } + specMount = specs.Mount{ + Source: "/sys", + Type: "bind", + Destination: "/sys", + Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev", "ro"}, + } + } } - mounts = append(mounts, specs.Mount{ - Source: src, - Destination: dest, - Type: "bind", - Options: []string{"rbind", "ro"}, - }) + specMounts = append(specMounts, specMount) } + // Add a mount for the cgroups filesystem, unless we're already + // recursively bind mounting all of /sys, in which case we shouldn't + // bother with it. + sysfsMount := []specs.Mount{} + if mountCgroups { + sysfsMount = []specs.Mount{{ + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Source: "cgroup", + Options: []string{"nobuildahbind", "nosuid", "noexec", "nodev", "relatime", "ro"}, + }} + } + + // Get the list of files we need to bind into the container. + bindFileMounts, err := runSetupBoundFiles(bindFiles) + if err != nil { + return err + } + + // After this point we need to know the per-container persistent storage directory. cdir, err := b.store.ContainerDirectory(b.ContainerID) if err != nil { return errors.Wrapf(err, "error determining work directory for container %q", b.ContainerID) } - // Add secrets mounts - secretMounts := secrets.SecretMounts(b.MountLabel, cdir, b.DefaultMountsFilePath) - for _, mount := range secretMounts { + // Figure out which UID and GID to tell the secrets package to use + // for files that it creates. + rootUID, rootGID, err := getHostRootIDs(spec) + if err != nil { + return err + } + + // Get the list of secrets mounts. + secretMounts := secrets.SecretMountsWithUIDGID(b.MountLabel, cdir, b.DefaultMountsFilePath, cdir, int(rootUID), int(rootGID)) + + // Add temporary copies of the contents of volume locations at the + // volume locations, unless we already have something there. + copyWithTar := b.copyWithTar(nil) + builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes) + if err != nil { + return err + } + + // Get the list of explicitly-specified volume mounts. + volumes, err := runSetupVolumeMounts(spec.Linux.MountLabel, volumeMounts) + if err != nil { + return err + } + + // Add them all, in the preferred order, except where they conflict with something that was previously added. + for _, mount := range append(append(append(append(append(volumes, builtins...), secretMounts...), bindFileMounts...), specMounts...), sysfsMount...) { if haveMount(mount.Destination) { + // Already mounting something there, no need to bother with this one. continue } + // Add the mount. mounts = append(mounts, mount) } + // Set the list in the spec. + spec.Mounts = mounts + return nil +} + +func runSetupBoundFiles(bindFiles map[string]string) (mounts []specs.Mount, err error) { + for dest, src := range bindFiles { + mounts = append(mounts, specs.Mount{ + Source: src, + Destination: dest, + Type: "bind", + Options: []string{"rbind"}, + }) + } + return mounts, nil +} + +func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWithTar func(srcPath, dstPath string) error, builtinVolumes []string) ([]specs.Mount, error) { + var mounts []specs.Mount // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. for _, volume := range builtinVolumes { - if haveMount(volume) { - // Already mounting something there, no need to bother. - continue - } subdir := digest.Canonical.FromString(volume).Hex() - volumePath := filepath.Join(cdir, "buildah-volumes", subdir) + volumePath := filepath.Join(containerDir, "buildah-volumes", subdir) // If we need to, initialize the volume path's initial contents. - if _, err = os.Stat(volumePath); os.IsNotExist(err) { + if _, err := os.Stat(volumePath); os.IsNotExist(err) { if err = os.MkdirAll(volumePath, 0755); err != nil { - return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, volume, b.ContainerID) + return nil, errors.Wrapf(err, "error creating directory %q for volume %q", volumePath, volume) } - if err = label.Relabel(volumePath, b.MountLabel, false); err != nil { - return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, volume, b.ContainerID) + if err = label.Relabel(volumePath, mountLabel, false); err != nil { + return nil, errors.Wrapf(err, "error relabeling directory %q for volume %q", volumePath, volume) } srcPath := filepath.Join(mountPoint, volume) if err = copyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, volume, b.ContainerID, srcPath) + return nil, errors.Wrapf(err, "error populating directory %q for volume %q using contents of %q", volumePath, volume, srcPath) } } @@ -262,16 +457,19 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Options: []string{"bind"}, }) } + return mounts, nil +} + +func runSetupVolumeMounts(mountLabel string, volumeMounts []string) ([]specs.Mount, error) { + var mounts []specs.Mount + // Bind mount volumes given by the user at execution - var options []string for _, i := range volumeMounts { + var options []string spliti := strings.Split(i, ":") if len(spliti) > 2 { options = strings.Split(spliti[2], ",") } - if haveMount(spliti[1]) { - continue - } options = append(options, "rbind") var foundrw, foundro, foundz, foundZ bool var rootProp string @@ -293,13 +491,13 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts options = append(options, "rw") } if foundz { - if err := label.Relabel(spliti[0], spec.Linux.MountLabel, true); err != nil { - return errors.Wrapf(err, "relabel failed %q", spliti[0]) + if err := label.Relabel(spliti[0], mountLabel, true); err != nil { + return nil, errors.Wrapf(err, "relabeling %q failed", spliti[0]) } } if foundZ { - if err := label.Relabel(spliti[0], spec.Linux.MountLabel, false); err != nil { - return errors.Wrapf(err, "relabel failed %q", spliti[0]) + if err := label.Relabel(spliti[0], mountLabel, false); err != nil { + return nil, errors.Wrapf(err, "relabeling %q failed", spliti[0]) } } if rootProp == "" { @@ -313,33 +511,184 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Options: options, }) } - // Set the list in the spec. - spec.Mounts = mounts - return nil + return mounts, nil } // addNetworkConfig copies files from host and sets them up to bind mount into container func (b *Builder) addNetworkConfig(rdir, hostPath string) (string, error) { - stat, err := os.Stat(hostPath) - if err != nil { - return "", errors.Wrapf(err, "stat %q failed", hostPath) - } + copyFileWithTar := b.copyFileWithTar(nil) - buf, err := ioutil.ReadFile(hostPath) - if err != nil { - return "", errors.Wrapf(err, "opening %q failed", hostPath) - } cfile := filepath.Join(rdir, filepath.Base(hostPath)) - if err := ioutil.WriteFile(cfile, buf, stat.Mode()); err != nil { - return "", errors.Wrapf(err, "opening %q failed", cfile) + + if err := copyFileWithTar(hostPath, cfile); err != nil { + return "", errors.Wrapf(err, "error copying %q for container %q", cfile, b.ContainerID) } - if err = label.Relabel(cfile, b.MountLabel, false); err != nil { + + if err := label.Relabel(cfile, b.MountLabel, false); err != nil { return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID) } return cfile, nil } +func setupMaskedPaths(g *generate.Generator) { + for _, mp := range []string{ + "/proc/kcore", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/sys/firmware", + } { + g.AddLinuxMaskedPaths(mp) + } +} + +func setupReadOnlyPaths(g *generate.Generator) { + for _, rp := range []string{ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger", + } { + g.AddLinuxReadonlyPaths(rp) + } +} + +func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error { + if seccompProfilePath != "unconfined" { + if seccompProfilePath != "" { + seccompProfile, err := ioutil.ReadFile(seccompProfilePath) + if err != nil { + return errors.Wrapf(err, "opening seccomp profile (%s) failed", seccompProfilePath) + } + seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), spec) + if err != nil { + return errors.Wrapf(err, "loading seccomp profile (%s) failed", seccompProfilePath) + } + spec.Linux.Seccomp = seccompConfig + } else { + seccompConfig, err := seccomp.GetDefaultProfile(spec) + if err != nil { + return errors.Wrapf(err, "loading seccomp profile (%s) failed", seccompProfilePath) + } + spec.Linux.Seccomp = seccompConfig + } + } + return nil +} + +func setupApparmor(spec *specs.Spec, apparmorProfile string) error { + spec.Process.ApparmorProfile = apparmorProfile + return nil +} + +func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy) { + switch terminalPolicy { + case DefaultTerminal: + g.SetProcessTerminal(terminal.IsTerminal(int(os.Stdout.Fd()))) + case WithTerminal: + g.SetProcessTerminal(true) + case WithoutTerminal: + g.SetProcessTerminal(false) + } +} + +func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, idmapOptions IDMappingOptions, policy NetworkConfigurationPolicy) (configureNetwork bool, configureNetworks []string, configureUTS bool, err error) { + // Set namespace options in the container configuration. + hostPidns := false + configureUserns := false + specifiedNetwork := false + for _, namespaceOption := range namespaceOptions { + switch namespaceOption.Name { + case string(specs.UserNamespace): + configureUserns = false + if !namespaceOption.Host && namespaceOption.Path == "" { + configureUserns = true + } + case string(specs.PIDNamespace): + hostPidns = namespaceOption.Host + case string(specs.NetworkNamespace): + specifiedNetwork = true + configureNetwork = false + if !namespaceOption.Host && (namespaceOption.Path == "" || !filepath.IsAbs(namespaceOption.Path)) { + if namespaceOption.Path != "" && !filepath.IsAbs(namespaceOption.Path) { + configureNetworks = strings.Split(namespaceOption.Path, ",") + namespaceOption.Path = "" + } + configureNetwork = (policy != NetworkDisabled) + } + case string(specs.UTSNamespace): + configureUTS = false + if !namespaceOption.Host && namespaceOption.Path == "" { + configureUTS = true + } + } + if namespaceOption.Host { + if err := g.RemoveLinuxNamespace(namespaceOption.Name); err != nil { + return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", namespaceOption.Name) + } + } else if err := g.AddOrReplaceLinuxNamespace(namespaceOption.Name, namespaceOption.Path); err != nil { + if namespaceOption.Path == "" { + return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", namespaceOption.Name) + } + return false, nil, false, errors.Wrapf(err, "error adding %q namespace %q for run", namespaceOption.Name, namespaceOption.Path) + } + } + // If we've got mappings, we're going to have to create a user namespace. + if len(idmapOptions.UIDMap) > 0 || len(idmapOptions.GIDMap) > 0 || configureUserns { + if hostPidns { + return false, nil, false, errors.Wrapf(err, "unable to mix host PID namespace with user namespace") + } + if err := g.AddOrReplaceLinuxNamespace(specs.UserNamespace, ""); err != nil { + return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.UserNamespace)) + } + for _, m := range idmapOptions.UIDMap { + g.AddLinuxUIDMapping(m.HostID, m.ContainerID, m.Size) + } + if len(idmapOptions.UIDMap) == 0 { + mappings, err := getProcIDMappings("/proc/self/uid_map") + if err != nil { + return false, nil, false, err + } + for _, m := range mappings { + g.AddLinuxUIDMapping(m.ContainerID, m.ContainerID, m.Size) + } + } + for _, m := range idmapOptions.GIDMap { + g.AddLinuxGIDMapping(m.HostID, m.ContainerID, m.Size) + } + if len(idmapOptions.GIDMap) == 0 { + mappings, err := getProcIDMappings("/proc/self/gid_map") + if err != nil { + return false, nil, false, err + } + for _, m := range mappings { + g.AddLinuxGIDMapping(m.ContainerID, m.ContainerID, m.Size) + } + } + if !specifiedNetwork { + if err := g.AddOrReplaceLinuxNamespace(specs.NetworkNamespace, ""); err != nil { + return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.NetworkNamespace)) + } + configureNetwork = (policy != NetworkDisabled) + } + } else { + if err := g.RemoveLinuxNamespace(specs.UserNamespace); err != nil { + return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", string(specs.UserNamespace)) + } + if !specifiedNetwork { + if err := g.RemoveLinuxNamespace(specs.NetworkNamespace); err != nil { + return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", string(specs.NetworkNamespace)) + } + } + } + return configureNetwork, configureNetworks, configureUTS, nil +} + // Run runs the specified command in the container's root filesystem. func (b *Builder) Run(command []string, options RunOptions) error { var user specs.User @@ -353,7 +702,8 @@ func (b *Builder) Run(command []string, options RunOptions) error { logrus.Errorf("error removing %q: %v", path, err2) } }() - g := generate.New() + gp := generate.New() + g := &gp for _, envSpec := range append(b.Env(), options.Env...) { env := strings.SplitN(envSpec, "=", 2) @@ -366,7 +716,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { return errors.Errorf("Invalid format on container you must recreate the container") } - if err := addCommonOptsToSpec(b.CommonBuildOpts, &g); err != nil { + if err := addCommonOptsToSpec(b.CommonBuildOpts, g); err != nil { return err } @@ -380,11 +730,6 @@ func (b *Builder) Run(command []string, options RunOptions) error { } else if b.WorkDir() != "" { g.SetProcessCwd(b.WorkDir()) } - if options.Hostname != "" { - g.SetHostname(options.Hostname) - } else if b.Hostname() != "" { - g.SetHostname(b.Hostname()) - } g.SetProcessSelinuxLabel(b.ProcessLabel) g.SetLinuxMountLabel(b.MountLabel) mountPoint, err := b.Mount(b.MountLabel) @@ -396,49 +741,51 @@ func (b *Builder) Run(command []string, options RunOptions) error { logrus.Errorf("error unmounting container: %v", err2) } }() - for _, mp := range []string{ - "/proc/kcore", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/proc/scsi", - "/sys/firmware", - } { - g.AddLinuxMaskedPaths(mp) - } - for _, rp := range []string{ - "/proc/asound", - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger", - } { - g.AddLinuxReadonlyPaths(rp) - } + setupMaskedPaths(g) + setupReadOnlyPaths(g) + g.SetRootPath(mountPoint) - switch options.Terminal { - case DefaultTerminal: - g.SetProcessTerminal(terminal.IsTerminal(int(os.Stdout.Fd()))) - case WithTerminal: - g.SetProcessTerminal(true) - case WithoutTerminal: - g.SetProcessTerminal(false) + + setupTerminal(g, options.Terminal) + + namespaceOptions := DefaultNamespaceOptions() + namespaceOptions.AddOrReplace(b.NamespaceOptions...) + namespaceOptions.AddOrReplace(options.NamespaceOptions...) + + networkPolicy := options.ConfigureNetwork + if networkPolicy == NetworkDefault { + networkPolicy = b.ConfigureNetwork + } + + configureNetwork, configureNetworks, configureUTS, err := setupNamespaces(g, namespaceOptions, b.IDMappingOptions, networkPolicy) + if err != nil { + return err } - if !options.NetworkDisabled { - if err = g.RemoveLinuxNamespace("network"); err != nil { - return errors.Wrapf(err, "error removing network namespace for run") + + if configureUTS { + if options.Hostname != "" { + g.SetHostname(options.Hostname) + } else if b.Hostname() != "" { + g.SetHostname(b.Hostname()) } + } else { + g.SetHostname("") } - user, err = b.user(mountPoint, options.User) - if err != nil { + + if user, err = b.user(mountPoint, options.User); err != nil { return err } g.SetProcessUID(user.UID) g.SetProcessGID(user.GID) + for _, gid := range user.AdditionalGids { + g.AddProcessAdditionalGid(gid) + } + + // Now grab the spec from the generator. Set the generator to nil so that future contributors + // will quickly be able to tell that they're supposed to be modifying the spec directly from here. spec := g.Spec() + g = nil if spec.Process.Cwd == "" { spec.Process.Cwd = DefaultWorkingDir } @@ -447,36 +794,15 @@ func (b *Builder) Run(command []string, options RunOptions) error { } // Set the apparmor profile name. - g.SetProcessApparmorProfile(b.CommonBuildOpts.ApparmorProfile) + if err = setupApparmor(spec, b.CommonBuildOpts.ApparmorProfile); err != nil { + return err + } // Set the seccomp configuration using the specified profile name. - if b.CommonBuildOpts.SeccompProfilePath != "unconfined" { - if b.CommonBuildOpts.SeccompProfilePath != "" { - seccompProfile, err := ioutil.ReadFile(b.CommonBuildOpts.SeccompProfilePath) - if err != nil { - return errors.Wrapf(err, "opening seccomp profile (%s) failed", b.CommonBuildOpts.SeccompProfilePath) - } - seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), spec) - if err != nil { - return errors.Wrapf(err, "loading seccomp profile (%s) failed", b.CommonBuildOpts.SeccompProfilePath) - } - spec.Linux.Seccomp = seccompConfig - } else { - seccompConfig, err := seccomp.GetDefaultProfile(spec) - if err != nil { - return errors.Wrapf(err, "loading seccomp profile (%s) failed", b.CommonBuildOpts.SeccompProfilePath) - } - spec.Linux.Seccomp = seccompConfig - } + if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil { + return err } - cgroupMnt := specs.Mount{ - Destination: "/sys/fs/cgroup", - Type: "cgroup", - Source: "cgroup", - Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"}, - } - g.AddMount(cgroupMnt) hostFile, err := b.addNetworkConfig(path, "/etc/hosts") if err != nil { return err @@ -494,29 +820,47 @@ func (b *Builder) Run(command []string, options RunOptions) error { "/etc/hosts": hostFile, "/etc/resolv.conf": resolvFile, } - err = b.setupMounts(mountPoint, spec, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize) + err = b.setupMounts(mountPoint, spec, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, append(b.NamespaceOptions, options.NamespaceOptions...)) if err != nil { return errors.Wrapf(err, "error resolving mountpoints for container") } - return b.runUsingRuntimeSubproc(options, spec, mountPoint, path, Package+"-"+filepath.Base(path)) + + if options.CNIConfigDir == "" { + options.CNIConfigDir = b.CNIConfigDir + if b.CNIConfigDir == "" { + options.CNIConfigDir = util.DefaultCNIConfigDir + } + } + if options.CNIPluginPath == "" { + options.CNIPluginPath = b.CNIPluginPath + if b.CNIPluginPath == "" { + options.CNIPluginPath = util.DefaultCNIPluginPath + } + } + + return b.runUsingRuntimeSubproc(options, configureNetwork, configureNetworks, spec, mountPoint, path, Package+"-"+filepath.Base(path)) } type runUsingRuntimeSubprocOptions struct { - Options RunOptions - Spec *specs.Spec - RootPath string - BundlePath string - ContainerName string + Options RunOptions + Spec *specs.Spec + RootPath string + BundlePath string + ConfigureNetwork bool + ConfigureNetworks []string + ContainerName string } -func (b *Builder) runUsingRuntimeSubproc(options RunOptions, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) { +func (b *Builder) runUsingRuntimeSubproc(options RunOptions, configureNetwork bool, configureNetworks []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) { var confwg sync.WaitGroup config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{ - Options: options, - Spec: spec, - RootPath: rootPath, - BundlePath: bundlePath, - ContainerName: containerName, + Options: options, + Spec: spec, + RootPath: rootPath, + BundlePath: bundlePath, + ConfigureNetwork: configureNetwork, + ConfigureNetworks: configureNetworks, + ContainerName: containerName, }) if conferr != nil { return errors.Wrapf(conferr, "error encoding configuration for %q", runUsingRuntimeCommand) @@ -577,7 +921,7 @@ func runUsingRuntimeMain() { os.Exit(1) } // Run the container, start to finish. - status, err := runUsingRuntime(options.Options, options.Spec, options.RootPath, options.BundlePath, options.ContainerName) + status, err := runUsingRuntime(options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.Spec, options.RootPath, options.BundlePath, options.ContainerName) if err != nil { fmt.Fprintf(os.Stderr, "error running container: %v\n", err) os.Exit(1) @@ -592,7 +936,21 @@ func runUsingRuntimeMain() { os.Exit(1) } -func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) { +func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetworks []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) { + // Set up bind mounts for things that a namespaced user might not be able to get to directly. + runtime.LockOSThread() + unmountAll, err := runSetupIntermediateMountNamespace(spec, bundlePath) + if unmountAll != nil { + defer func() { + if err := unmountAll(); err != nil { + logrus.Error(err) + } + }() + } + if err != nil { + return 1, err + } + // Write the runtime configuration. specbytes, err := json.Marshal(spec) if err != nil { @@ -639,12 +997,14 @@ func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath, moreCreateArgs = func() []string { return []string{"--console-socket", socketPath} } } else { copyStdio = true - // Create pipes to use for relaying stdio. - for i := range stdioPipe { - stdioPipe[i] = make([]int, 2) - if err = unix.Pipe(stdioPipe[i]); err != nil { - return 1, errors.Wrapf(err, "error creating pipe for container FD %d", i) - } + // Figure out who should own the pipes. + uid, gid, err := getHostRootIDs(spec) + if err != nil { + return 1, err + } + // Create stdio pipes. + if stdioPipe, err = runMakeStdioPipe(int(uid), int(gid)); err != nil { + return 1, err } // Set stdio to our pipes. getCreateStdio = func() (*os.File, *os.File, *os.File) { @@ -696,7 +1056,7 @@ func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath, // Actually create the container. err = create.Run() if err != nil { - return 1, errors.Wrapf(err, "error creating container for %v", spec.Process.Args) + return 1, errors.Wrapf(err, "error creating container for %v: %s", spec.Process.Args, runCollectOutput(stdioPipe[unix.Stdout][0], stdioPipe[unix.Stderr][0])) } defer func() { err2 := del.Run() @@ -726,10 +1086,20 @@ func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath, _, err = unix.Wait4(pid, &wstatus, 0, nil) if err != nil { wstatus = 0 - logrus.Errorf("error waiting for container child process: %v\n", err) + logrus.Errorf("error waiting for container child process %d: %v\n", pid, err) } }() + if configureNetwork { + teardown, err := runConfigureNetwork(options, configureNetwork, configureNetworks, pid, containerName, spec.Process.Args) + if teardown != nil { + defer teardown() + } + if err != nil { + return 1, err + } + } + if copyStdio { // We don't need the ends of the pipes that belong to the container. stdin.Close() @@ -808,6 +1178,131 @@ func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath, return wstatus, nil } +func runCollectOutput(fds ...int) string { + var b bytes.Buffer + buf := make([]byte, 8192) + for _, fd := range fds { + if err := unix.SetNonblock(fd, true); err != nil { + logrus.Errorf("error setting pipe descriptor %d nonblocking: %v", fd, err) + continue + } + nread, err := unix.Read(fd, buf) + if err != nil { + logrus.Errorf("error reading from pipe %d: %v", fd, err) + break + } + for nread > 0 { + r := buf[:nread] + if nwritten, err := b.Write(r); err != nil || nwritten != len(r) { + if nwritten != len(r) { + logrus.Errorf("error buffering data from pipe %d: %v", fd, err) + break + } + } + nread, err = unix.Read(fd, buf) + if err != nil { + logrus.Errorf("error reading from pipe %d: %v", fd, err) + break + } + } + } + return b.String() +} + +func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) { + var netconf, undo []*libcni.NetworkConfigList + // Scan for CNI configuration files. + confdir := options.CNIConfigDir + files, err := libcni.ConfFiles(confdir, []string{".conf"}) + if err != nil { + return nil, errors.Wrapf(err, "error finding CNI networking configuration files named *.conf in directory %q", confdir) + } + lists, err := libcni.ConfFiles(confdir, []string{".conflist"}) + if err != nil { + return nil, errors.Wrapf(err, "error finding CNI networking configuration list files named *.conflist in directory %q", confdir) + } + logrus.Debugf("CNI network configuration file list: %#v", append(files, lists...)) + // Read the CNI configuration files. + for _, file := range files { + nc, err := libcni.ConfFromFile(file) + if err != nil { + return nil, errors.Wrapf(err, "error loading networking configuration from file %q for %v", file, command) + } + if len(configureNetworks) > 0 && nc.Network != nil && (nc.Network.Name == "" || !stringInSlice(nc.Network.Name, configureNetworks)) { + if nc.Network.Name == "" { + logrus.Debugf("configuration in %q has no name, skipping it", file) + } else { + logrus.Debugf("configuration in %q has name %q, skipping it", file, nc.Network.Name) + } + continue + } + cl, err := libcni.ConfListFromConf(nc) + if err != nil { + return nil, errors.Wrapf(err, "error converting networking configuration from file %q for %v", file, command) + } + logrus.Debugf("using network configuration from %q", file) + netconf = append(netconf, cl) + } + for _, list := range lists { + cl, err := libcni.ConfListFromFile(list) + if err != nil { + return nil, errors.Wrapf(err, "error loading networking configuration list from file %q for %v", list, command) + } + if len(configureNetworks) > 0 && (cl.Name == "" || !stringInSlice(cl.Name, configureNetworks)) { + if cl.Name == "" { + logrus.Debugf("configuration list in %q has no name, skipping it", list) + } else { + logrus.Debugf("configuration list in %q has name %q, skipping it", list, cl.Name) + } + continue + } + logrus.Debugf("using network configuration list from %q", list) + netconf = append(netconf, cl) + } + // Make sure we can access the container's network namespace, + // even after it exits, to successfully tear down the + // interfaces. Ensure this by opening a handle to the network + // namespace, and using our copy to both configure and + // deconfigure it. + netns := fmt.Sprintf("/proc/%d/ns/net", pid) + netFD, err := unix.Open(netns, unix.O_RDONLY, 0) + if err != nil { + return nil, errors.Wrapf(err, "error opening network namespace for %v", command) + } + mynetns := fmt.Sprintf("/proc/%d/fd/%d", unix.Getpid(), netFD) + // Build our search path for the plugins. + pluginPaths := strings.Split(options.CNIPluginPath, string(os.PathListSeparator)) + cni := libcni.CNIConfig{Path: pluginPaths} + // Configure the interfaces. + rtconf := make(map[*libcni.NetworkConfigList]*libcni.RuntimeConf) + teardown = func() { + for _, nc := range undo { + if err = cni.DelNetworkList(nc, rtconf[nc]); err != nil { + logrus.Errorf("error cleaning up network %v for %v: %v", rtconf[nc].IfName, command, err) + } + } + unix.Close(netFD) + } + for i, nc := range netconf { + // Build the runtime config for use with this network configuration. + rtconf[nc] = &libcni.RuntimeConf{ + ContainerID: containerName, + NetNS: mynetns, + IfName: fmt.Sprintf("if%d", i), + Args: [][2]string{}, + CapabilityArgs: map[string]interface{}{}, + } + // Bring it up. + _, err := cni.AddNetworkList(nc, rtconf[nc]) + if err != nil { + return teardown, errors.Wrapf(err, "error configuring network list %v for %v", rtconf[nc].IfName, command) + } + // Add it to the list of networks to take down when the container process exits. + undo = append([]*libcni.NetworkConfigList{nc}, undo...) + } + return teardown, nil +} + func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}) { defer func() { unix.Close(finishCopy[0]) @@ -1054,3 +1549,209 @@ func runSetDeathSig(cmd *exec.Cmd) { cmd.SysProcAttr.Pdeathsig = syscall.SIGTERM } } + +// Create pipes to use for relaying stdio. +func runMakeStdioPipe(uid, gid int) ([][]int, error) { + stdioPipe := make([][]int, 3) + for i := range stdioPipe { + stdioPipe[i] = make([]int, 2) + if err := unix.Pipe(stdioPipe[i]); err != nil { + return nil, errors.Wrapf(err, "error creating pipe for container FD %d", i) + } + } + if err := unix.Fchown(stdioPipe[unix.Stdin][0], uid, gid); err != nil { + return nil, errors.Wrapf(err, "error setting owner of stdin pipe descriptor") + } + if err := unix.Fchown(stdioPipe[unix.Stdout][1], uid, gid); err != nil { + return nil, errors.Wrapf(err, "error setting owner of stdout pipe descriptor") + } + if err := unix.Fchown(stdioPipe[unix.Stderr][1], uid, gid); err != nil { + return nil, errors.Wrapf(err, "error setting owner of stderr pipe descriptor") + } + return stdioPipe, nil +} + +// Create and bind mount all bind-mount sources into a subdirectory of +// bundlePath that can only be reached by the root user of the container's user +// namespace. +func runSetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmountAll func() error, err error) { + defer func() { + // Strip "nobuildahbind" options out of the spec, at least. */ + for i := range spec.Mounts { + if stringInSlice("nobuildahbind", spec.Mounts[i].Options) { + prunedOptions := make([]string, 0, len(spec.Mounts[i].Options)) + for _, option := range spec.Mounts[i].Options { + if option != "nobuildahbind" { + prunedOptions = append(prunedOptions, option) + } + } + spec.Mounts[i].Options = prunedOptions + } + } + }() + + // Create a new mount namespace in which to do the things we're doing. + if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { + return nil, errors.Wrapf(err, "error creating new mount namespace for %v", spec.Process.Args) + } + + // Make all of our mounts private to our namespace. + if err := mount.MakePrivate("/"); err != nil { + return nil, errors.Wrapf(err, "error making mounts private to mount namespace for %v", spec.Process.Args) + } + + // We expect a root directory to be defined. + if spec.Root == nil { + return nil, errors.Errorf("configuration has no root filesystem?") + } + rootPath := spec.Root.Path + + // Make sure the bundle directory is searchable. We created it with + // TempDir(), so it should have started with permissions set to 0700. + info, err := os.Stat(bundlePath) + if err != nil { + return nil, errors.Wrapf(err, "error checking permissions on %q", bundlePath) + } + if err = os.Chmod(bundlePath, info.Mode()|0111); err != nil { + return nil, errors.Wrapf(err, "error loosening permissions on %q", bundlePath) + } + + // Figure out who needs to be able to reach these bind mounts in order + // for the container to be started. + rootUID, rootGID, err := getHostRootIDs(spec) + if err != nil { + return nil, err + } + + // Hand back a callback that the caller can use to clean up everything + // we're doing here. + unmount := []string{} + unmountAll = func() (err error) { + for _, mountpoint := range unmount { + subdirs := []string{mountpoint} + var infos []*mount.Info + infos, err = mount.GetMounts() + // Gather up mountpoints below this one, since we did + // some recursive mounting. + if err == nil { + for _, info := range infos { + if strings.HasPrefix(info.Mountpoint, mountpoint) { + subdirs = append(subdirs, info.Mountpoint) + } + } + } + // Unmount all of the lower mountpoints, then the + // mountpoint itself. + sort.Strings(subdirs) + for i := range subdirs { + var err2 error + subdir := subdirs[len(subdirs)-i-1] + for err2 == nil { + err2 = unix.Unmount(subdir, 0) + } + if errno, ok := err2.(syscall.Errno); !ok || errno != unix.EINVAL { + logrus.Warnf("error unmounting %q: %v", mountpoint, err2) + if err == nil { + err = err2 + } + } + } + // Remove just the mountpoint. + if err2 := os.Remove(mountpoint); err2 != nil { + logrus.Warnf("error removing %q: %v", mountpoint, err2) + if err == nil { + err = err2 + } + } + } + return err + } + + // Create a top-level directory that the "root" user will be able to + // access, that "root" from containers which use different mappings, or + // other unprivileged users outside of containers, shouldn't be able to + // access. + mnt := filepath.Join(bundlePath, "mnt") + if err = idtools.MkdirAndChown(mnt, 0100, idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}); err != nil { + return unmountAll, errors.Wrapf(err, "error creating %q owned by the container's root user", mnt) + } + + // Make that directory private, and add it to the list of locations we + // unmount at cleanup time. + if err = mount.MakeRPrivate(mnt); err != nil { + return unmountAll, errors.Wrapf(err, "error marking filesystem at %q as private", mnt) + } + unmount = append([]string{mnt}, unmount...) + + // Create a bind mount for the root filesystem and add it to the list. + rootfs := filepath.Join(mnt, "rootfs") + if err = os.Mkdir(rootfs, 0000); err != nil { + return unmountAll, errors.Wrapf(err, "error creating directory %q", rootfs) + } + if err = unix.Mount(rootPath, rootfs, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { + return unmountAll, errors.Wrapf(err, "error bind mounting root filesystem from %q to %q", rootPath, rootfs) + } + unmount = append([]string{rootfs}, unmount...) + spec.Root.Path = rootfs + + // Do the same for everything we're binding in. + mounts := make([]specs.Mount, 0, len(spec.Mounts)) + for i := range spec.Mounts { + // If we're not using an intermediate, leave it in the list. + if runLeaveBindMountAlone(spec.Mounts[i]) { + mounts = append(mounts, spec.Mounts[i]) + continue + } + // Check if the source is a directory or something else. + info, err := os.Stat(spec.Mounts[i].Source) + if err != nil { + if os.IsNotExist(err) { + logrus.Warnf("couldn't find %q on host to bind mount into container", spec.Mounts[i].Source) + continue + } + return unmountAll, errors.Wrapf(err, "error checking if %q is a directory", spec.Mounts[i].Source) + } + stage := filepath.Join(mnt, fmt.Sprintf("buildah-bind-target-%d", i)) + if info.IsDir() { + // If the source is a directory, make one to use as the + // mount target. + if err = os.Mkdir(stage, 0000); err != nil { + return unmountAll, errors.Wrapf(err, "error creating directory %q", stage) + } + } else { + // If the source is not a directory, create an empty + // file to use as the mount target. + file, err := os.OpenFile(stage, os.O_WRONLY|os.O_CREATE, 0000) + if err != nil { + return unmountAll, errors.Wrapf(err, "error creating file %q", stage) + } + file.Close() + } + // Bind mount the source from wherever it is to a place where + // we know the runtime helper will be able to get to it... + if err = unix.Mount(spec.Mounts[i].Source, stage, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { + return unmountAll, errors.Wrapf(err, "error bind mounting bind object from %q to %q", spec.Mounts[i].Source, stage) + } + spec.Mounts[i].Source = stage + // ... and update the source location that we'll pass to the + // runtime to our intermediate location. + mounts = append(mounts, spec.Mounts[i]) + unmount = append([]string{stage}, unmount...) + } + spec.Mounts = mounts + + return unmountAll, nil +} + +// Decide if the mount should not be redirected to an intermediate location first. +func runLeaveBindMountAlone(mount specs.Mount) bool { + // If we know we shouldn't do a redirection for this mount, skip it. + if stringInSlice("nobuildahbind", mount.Options) { + return true + } + // If we're not bind mounting it in, we don't need to do anything for it. + if mount.Type != "bind" && !stringInSlice("bind", mount.Options) && !stringInSlice("rbind", mount.Options) { + return true + } + return false +} diff --git a/vendor/github.com/projectatomic/buildah/util.go b/vendor/github.com/projectatomic/buildah/util.go index 33b5b9e83..0d05aa8a2 100644 --- a/vendor/github.com/projectatomic/buildah/util.go +++ b/vendor/github.com/projectatomic/buildah/util.go @@ -1,15 +1,18 @@ package buildah import ( + "bufio" + "io" + "os" + "strconv" + "strings" + + "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" + "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/reexec" -) - -var ( - // CopyWithTar defines the copy method to use. - copyWithTar = chrootarchive.NewArchiver(nil).CopyWithTar - copyFileWithTar = chrootarchive.NewArchiver(nil).CopyFileWithTar - untarPath = chrootarchive.NewArchiver(nil).UntarPath + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) // InitReexec is a wrapper for reexec.Init(). It should be called at @@ -32,3 +35,168 @@ func copyStringSlice(s []string) []string { copy(t, s) return t } + +func stringInSlice(s string, slice []string) bool { + for _, v := range slice { + if v == s { + return true + } + } + return false +} + +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 into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func (b *Builder) copyFileWithTar(chownOpts *idtools.IDPair) func(src, dest string) error { + convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) + untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) + archiver := chrootarchive.NewArchiverWithChown(nil, chownOpts, untarMappings) + return archiver.CopyFileWithTar +} + +// copyWithTar returns a function which copies a directory tree from outside of +// any container into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func (b *Builder) copyWithTar(chownOpts *idtools.IDPair) func(src, dest string) error { + convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) + untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) + archiver := chrootarchive.NewArchiverWithChown(nil, chownOpts, untarMappings) + return archiver.CopyWithTar +} + +// 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) func(src, dest string) error { + convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) + untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) + archiver := chrootarchive.NewArchiverWithChown(nil, chownOpts, untarMappings) + return archiver.UntarPath +} + +// tarPath returns a function which creates an archive of a specified +// location in the container's filesystem, mapping permissions using the +// container's ID maps +func (b *Builder) tarPath() func(path string) (io.ReadCloser, error) { + convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) + tarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) + return func(path string) (io.ReadCloser, error) { + return archive.TarWithOptions(path, &archive.TarOptions{ + Compression: archive.Uncompressed, + UIDMaps: tarMappings.UIDs(), + GIDMaps: tarMappings.GIDs(), + }) + } +} + +// getProcIDMappings reads mappings from the named node under /proc. +func getProcIDMappings(path string) ([]rspec.LinuxIDMapping, error) { + var mappings []rspec.LinuxIDMapping + f, err := os.Open(path) + if err != nil { + return nil, errors.Wrapf(err, "error reading ID mappings from %q", path) + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) != 3 { + return nil, errors.Errorf("line %q from %q has %d fields, not 3", line, path, len(fields)) + } + cid, err := strconv.ParseUint(fields[0], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "error parsing container ID value %q from line %q in %q", fields[0], line, path) + } + hid, err := strconv.ParseUint(fields[1], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "error parsing host ID value %q from line %q in %q", fields[1], line, path) + } + size, err := strconv.ParseUint(fields[2], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "error parsing size value %q from line %q in %q", fields[2], line, path) + } + mappings = append(mappings, rspec.LinuxIDMapping{ContainerID: uint32(cid), HostID: uint32(hid), Size: uint32(size)}) + } + return mappings, nil +} + +// getHostIDs uses ID mappings to compute the host-level IDs that will +// correspond to a UID/GID pair in the container. +func getHostIDs(uidmap, gidmap []rspec.LinuxIDMapping, uid, gid uint32) (uint32, uint32, error) { + uidMapped := true + for _, m := range uidmap { + uidMapped = false + if uid >= m.ContainerID && uid < m.ContainerID+m.Size { + uid = (uid - m.ContainerID) + m.HostID + uidMapped = true + break + } + } + if !uidMapped { + return 0, 0, errors.Errorf("container uses ID mappings, but doesn't map UID %d", uid) + } + gidMapped := true + for _, m := range gidmap { + gidMapped = false + if gid >= m.ContainerID && gid < m.ContainerID+m.Size { + gid = (gid - m.ContainerID) + m.HostID + gidMapped = true + break + } + } + if !gidMapped { + return 0, 0, errors.Errorf("container uses ID mappings, but doesn't map GID %d", gid) + } + return uid, gid, nil +} + +// getHostRootIDs uses ID mappings in spec to compute the host-level IDs that will +// correspond to UID/GID 0/0 in the container. +func getHostRootIDs(spec *rspec.Spec) (uint32, uint32, error) { + if spec.Linux == nil { + return 0, 0, nil + } + return getHostIDs(spec.Linux.UIDMappings, spec.Linux.GIDMappings, 0, 0) +} diff --git a/vendor/github.com/projectatomic/buildah/util/types.go b/vendor/github.com/projectatomic/buildah/util/types.go new file mode 100644 index 000000000..bce419c02 --- /dev/null +++ b/vendor/github.com/projectatomic/buildah/util/types.go @@ -0,0 +1,10 @@ +package util + +const ( + // DefaultRuntime is the default command to use to run the container. + DefaultRuntime = "runc" + // DefaultCNIPluginPath is the default location of CNI plugin helpers. + DefaultCNIPluginPath = "/usr/libexec/cni:/opt/cni/bin" + // DefaultCNIConfigDir is the default location of CNI configuration files. + DefaultCNIConfigDir = "/etc/cni/net.d" +) -- cgit v1.2.3-54-g00ecf