summaryrefslogtreecommitdiff
path: root/vendor/github.com/projectatomic/buildah
diff options
context:
space:
mode:
authorumohnani8 <umohnani@redhat.com>2018-05-25 11:30:57 -0400
committerAtomic Bot <atomic-devel@projectatomic.io>2018-05-25 17:03:09 +0000
commit1930f5d7093e84cdcc9e327e296f4733f32b20c8 (patch)
tree4c41a5e955b12881dc7a88dae573a36dea9982ba /vendor/github.com/projectatomic/buildah
parent8fcf1aaa29e61b9305096f61a8ba26bc81462b05 (diff)
downloadpodman-1930f5d7093e84cdcc9e327e296f4733f32b20c8.tar.gz
podman-1930f5d7093e84cdcc9e327e296f4733f32b20c8.tar.bz2
podman-1930f5d7093e84cdcc9e327e296f4733f32b20c8.zip
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 <umohnani@redhat.com> Closes: #832 Approved by: mheon
Diffstat (limited to 'vendor/github.com/projectatomic/buildah')
-rw-r--r--vendor/github.com/projectatomic/buildah/add.go79
-rw-r--r--vendor/github.com/projectatomic/buildah/buildah.go78
-rw-r--r--vendor/github.com/projectatomic/buildah/image.go7
-rw-r--r--vendor/github.com/projectatomic/buildah/imagebuildah/build.go64
-rw-r--r--vendor/github.com/projectatomic/buildah/import.go16
-rw-r--r--vendor/github.com/projectatomic/buildah/new.go35
-rw-r--r--vendor/github.com/projectatomic/buildah/pkg/cli/common.go66
-rw-r--r--vendor/github.com/projectatomic/buildah/run.go993
-rw-r--r--vendor/github.com/projectatomic/buildah/util.go182
-rw-r--r--vendor/github.com/projectatomic/buildah/util/types.go10
10 files changed, 1296 insertions, 234 deletions
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"
+)