From 89af35175d97cf90e7336d3c817612fafc68dbdb Mon Sep 17 00:00:00 2001 From: TomSweeneyRedHat Date: Tue, 19 Jun 2018 10:03:34 -0400 Subject: Add cap-add and cap-drop to build man page Signed-off-by: TomSweeneyRedHat Closes: #968 Approved by: mheon --- vendor/github.com/projectatomic/buildah/add.go | 3 +- .../github.com/projectatomic/buildah/bind/mount.go | 294 +++++++++++ .../buildah/bind/mount_unsupported.go | 25 + .../github.com/projectatomic/buildah/bind/util.go | 39 ++ vendor/github.com/projectatomic/buildah/buildah.go | 16 +- vendor/github.com/projectatomic/buildah/config.go | 243 +++------- .../projectatomic/buildah/imagebuildah/build.go | 12 +- vendor/github.com/projectatomic/buildah/import.go | 59 +-- vendor/github.com/projectatomic/buildah/new.go | 43 +- .../projectatomic/buildah/pkg/cli/common.go | 10 +- .../projectatomic/buildah/pkg/parse/parse.go | 13 +- vendor/github.com/projectatomic/buildah/run.go | 538 +++++++++------------ vendor/github.com/projectatomic/buildah/util.go | 83 ---- .../github.com/projectatomic/buildah/util/types.go | 20 + .../github.com/projectatomic/buildah/util/util.go | 185 +++++++ .../github.com/projectatomic/buildah/vendor.conf | 2 +- 16 files changed, 927 insertions(+), 658 deletions(-) create mode 100644 vendor/github.com/projectatomic/buildah/bind/mount.go create mode 100644 vendor/github.com/projectatomic/buildah/bind/mount_unsupported.go create mode 100644 vendor/github.com/projectatomic/buildah/bind/util.go (limited to 'vendor/github.com/projectatomic/buildah') diff --git a/vendor/github.com/projectatomic/buildah/add.go b/vendor/github.com/projectatomic/buildah/add.go index 5c53c8dda..93ecba9f4 100644 --- a/vendor/github.com/projectatomic/buildah/add.go +++ b/vendor/github.com/projectatomic/buildah/add.go @@ -15,6 +15,7 @@ import ( "github.com/containers/storage/pkg/idtools" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" + "github.com/projectatomic/buildah/util" "github.com/projectatomic/libpod/pkg/chrootuser" "github.com/sirupsen/logrus" ) @@ -98,7 +99,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption 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) + hostUID, hostGID, err := util.GetHostIDs(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap, user.UID, user.GID) if err != nil { return err } diff --git a/vendor/github.com/projectatomic/buildah/bind/mount.go b/vendor/github.com/projectatomic/buildah/bind/mount.go new file mode 100644 index 000000000..9f55ae468 --- /dev/null +++ b/vendor/github.com/projectatomic/buildah/bind/mount.go @@ -0,0 +1,294 @@ +// +build linux + +package bind + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + + "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/mount" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/projectatomic/buildah/util" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// SetupIntermediateMountNamespace creates a new mount namespace and bind +// mounts all bind-mount sources into a subdirectory of bundlePath that can +// only be reached by the root user of the container's user namespace, except +// for Mounts which include the NoBindOption option in their options list. The +// NoBindOption will then merely be removed. +func SetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmountAll func() error, err error) { + defer stripNoBindOption(spec) + + // 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 + + // 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.MakeRPrivate("/"); err != nil { + return nil, errors.Wrapf(err, "error making mounts private to mount namespace for %v", spec.Process.Args) + } + + // 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 := util.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 { + // Unmount it and anything under it. + if err2 := UnmountMountpoints(mountpoint, nil); err2 != nil { + logrus.Warnf("pkg/bind: error unmounting %q: %v", mountpoint, err2) + if err == nil { + err = err2 + } + } + if err2 := unix.Unmount(mountpoint, unix.MNT_DETACH); err2 != nil { + if errno, ok := err2.(syscall.Errno); !ok || errno != syscall.EINVAL { + logrus.Warnf("pkg/bind: error detaching %q: %v", mountpoint, err2) + if err == nil { + err = err2 + } + } + } + // Remove just the mountpoint. + retry := 10 + remove := unix.Unlink + err2 := remove(mountpoint) + for err2 != nil && retry > 0 { + if errno, ok := err2.(syscall.Errno); ok { + switch errno { + default: + retry = 0 + continue + case syscall.EISDIR: + remove = unix.Rmdir + err2 = remove(mountpoint) + case syscall.EBUSY: + if err3 := unix.Unmount(mountpoint, unix.MNT_DETACH); err3 == nil { + err2 = remove(mountpoint) + } + } + retry-- + } + } + if err2 != nil { + logrus.Warnf("pkg/bind: 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 leaveBindMountAlone(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) + } + logrus.Debugf("bind mounted %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 leaveBindMountAlone(mount specs.Mount) bool { + // If we know we shouldn't do a redirection for this mount, skip it. + if util.StringInSlice(NoBindOption, 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" && !util.StringInSlice("bind", mount.Options) && !util.StringInSlice("rbind", mount.Options) { + return true + } + return false +} + +// UnmountMountpoints unmounts the given mountpoints and anything that's hanging +// off of them, rather aggressively. If a mountpoint also appears in the +// mountpointsToRemove slice, the mountpoints are removed after they are +// unmounted. +func UnmountMountpoints(mountpoint string, mountpointsToRemove []string) error { + mounts, err := mount.GetMounts() + if err != nil { + return errors.Wrapf(err, "error retrieving list of mounts") + } + // getChildren returns the list of mount IDs that hang off of the + // specified ID. + getChildren := func(id int) []int { + var list []int + for _, info := range mounts { + if info.Parent == id { + list = append(list, info.ID) + } + } + return list + } + // getTree returns the list of mount IDs that hang off of the specified + // ID, and off of those mount IDs, etc. + getTree := func(id int) []int { + mounts := []int{id} + i := 0 + for i < len(mounts) { + children := getChildren(mounts[i]) + mounts = append(mounts, children...) + i++ + } + return mounts + } + // getMountByID looks up the mount info with the specified ID + getMountByID := func(id int) *mount.Info { + for i := range mounts { + if mounts[i].ID == id { + return mounts[i] + } + } + return nil + } + // getMountByPoint looks up the mount info with the specified mountpoint + getMountByPoint := func(mountpoint string) *mount.Info { + for i := range mounts { + if mounts[i].Mountpoint == mountpoint { + return mounts[i] + } + } + return nil + } + // find the top of the tree we're unmounting + top := getMountByPoint(mountpoint) + if top == nil { + return errors.Wrapf(err, "%q is not mounted", mountpoint) + } + // add all of the mounts that are hanging off of it + tree := getTree(top.ID) + // unmount each mountpoint, working from the end of the list (leaf nodes) to the top + for i := range tree { + var st unix.Stat_t + id := tree[len(tree)-i-1] + mount := getMountByID(id) + // check if this mountpoint is mounted + if err := unix.Lstat(mount.Mountpoint, &st); err != nil { + return errors.Wrapf(err, "error checking if %q is mounted", mount.Mountpoint) + } + if mount.Major != int(unix.Major(st.Dev)) || mount.Minor != int(unix.Minor(st.Dev)) { + logrus.Debugf("%q is apparently not really mounted, skipping", mount.Mountpoint) + continue + } + // do the unmount + if err := unix.Unmount(mount.Mountpoint, 0); err != nil { + // if it was busy, detach it + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY { + err = unix.Unmount(mount.Mountpoint, unix.MNT_DETACH) + } + if err != nil { + // if it was invalid (not mounted), hide the error, else return it + if errno, ok := err.(syscall.Errno); !ok || errno != syscall.EINVAL { + logrus.Warnf("error unmounting %q: %v", mount.Mountpoint, err) + continue + } + } + } + // if we're also supposed to remove this thing, do that, too + if util.StringInSlice(mount.Mountpoint, mountpointsToRemove) { + if err := os.Remove(mount.Mountpoint); err != nil { + return errors.Wrapf(err, "error removing %q", mount.Mountpoint) + } + } + } + return nil +} diff --git a/vendor/github.com/projectatomic/buildah/bind/mount_unsupported.go b/vendor/github.com/projectatomic/buildah/bind/mount_unsupported.go new file mode 100644 index 000000000..a8786955d --- /dev/null +++ b/vendor/github.com/projectatomic/buildah/bind/mount_unsupported.go @@ -0,0 +1,25 @@ +// +build !linux + +package bind + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "syscall" + + "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/mount" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// SetupIntermediateMountNamespace returns a no-op unmountAll() and no error. +func SetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmountAll func() error, err error) { + stripNoBuildahBindOption(spec) + return func() error { return nil }, nil +} diff --git a/vendor/github.com/projectatomic/buildah/bind/util.go b/vendor/github.com/projectatomic/buildah/bind/util.go new file mode 100644 index 000000000..4408c53bb --- /dev/null +++ b/vendor/github.com/projectatomic/buildah/bind/util.go @@ -0,0 +1,39 @@ +package bind + +import ( + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/projectatomic/buildah/util" +) + +const ( + // NoBindOption is an option which, if present in a Mount structure's + // options list, will cause SetupIntermediateMountNamespace to not + // redirect it through a bind mount. + NoBindOption = "nobuildahbind" +) + +func stripNoBindOption(spec *specs.Spec) { + for i := range spec.Mounts { + if util.StringInSlice(NoBindOption, spec.Mounts[i].Options) { + prunedOptions := make([]string, 0, len(spec.Mounts[i].Options)) + for _, option := range spec.Mounts[i].Options { + if option != NoBindOption { + prunedOptions = append(prunedOptions, option) + } + } + spec.Mounts[i].Options = prunedOptions + } + } +} + +func dedupeStringSlice(slice []string) []string { + done := make([]string, 0, len(slice)) + m := make(map[string]struct{}) + for _, s := range slice { + if _, present := m[s]; !present { + m[s] = struct{}{} + done = append(done, s) + } + } + return done +} diff --git a/vendor/github.com/projectatomic/buildah/buildah.go b/vendor/github.com/projectatomic/buildah/buildah.go index 5d241564c..5fb428da2 100644 --- a/vendor/github.com/projectatomic/buildah/buildah.go +++ b/vendor/github.com/projectatomic/buildah/buildah.go @@ -163,6 +163,13 @@ type Builder struct { CNIConfigDir string // ID mapping options to use when running processes in the container with non-host user namespaces. IDMappingOptions IDMappingOptions + // AddCapabilities is a list of capabilities to add to the default set when running + // commands in the container. + AddCapabilities []string + // DropCapabilities is a list of capabilities to remove from the default set, + // after processing the AddCapabilities set, when running commands in the container. + // If a capability appears in both lists, it will be dropped. + DropCapabilities []string CommonBuildOpts *CommonBuildOptions // TopLayer is the top layer of the image @@ -221,7 +228,7 @@ func GetBuildInfo(b *Builder) BuilderInfo { // CommonBuildOptions are resources that can be defined by flags for both buildah from and build-using-dockerfile type CommonBuildOptions struct { - // AddHost is the list of hostnames to add to the resolv.conf + // AddHost is the list of hostnames to add to the build container's /etc/hosts. AddHost []string // CgroupParent is the path to cgroups under which the cgroup for the container will be created. CgroupParent string @@ -327,6 +334,13 @@ type BuilderOptions struct { CNIConfigDir string // ID mapping options to use if we're setting up our own user namespace. IDMappingOptions *IDMappingOptions + // AddCapabilities is a list of capabilities to add to the default set when + // running commands in the container. + AddCapabilities []string + // DropCapabilities is a list of capabilities to remove from the default set, + // after processing the AddCapabilities set, when running commands in the + // container. If a capability appears in both lists, it will be dropped. + DropCapabilities []string CommonBuildOpts *CommonBuildOptions } diff --git a/vendor/github.com/projectatomic/buildah/config.go b/vendor/github.com/projectatomic/buildah/config.go index 3d67895da..b39d2b6c6 100644 --- a/vendor/github.com/projectatomic/buildah/config.go +++ b/vendor/github.com/projectatomic/buildah/config.go @@ -1,218 +1,89 @@ package buildah import ( + "context" "encoding/json" "path/filepath" "runtime" "strings" "time" - digest "github.com/opencontainers/go-digest" + "github.com/containers/image/manifest" + "github.com/containers/image/transports" + "github.com/containers/image/types" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/projectatomic/buildah/docker" ) -// makeOCIv1Image builds the best OCIv1 image structure we can from the -// contents of the docker image structure. -func makeOCIv1Image(dimage *docker.V2Image) (ociv1.Image, error) { - config := dimage.Config - if config == nil { - config = &dimage.ContainerConfig - } - dcreated := dimage.Created.UTC() - image := ociv1.Image{ - Created: &dcreated, - Author: dimage.Author, - Architecture: dimage.Architecture, - OS: dimage.OS, - Config: ociv1.ImageConfig{ - User: config.User, - ExposedPorts: map[string]struct{}{}, - Env: config.Env, - Entrypoint: config.Entrypoint, - Cmd: config.Cmd, - Volumes: config.Volumes, - WorkingDir: config.WorkingDir, - Labels: config.Labels, - StopSignal: config.StopSignal, - }, - RootFS: ociv1.RootFS{ - Type: "", - DiffIDs: []digest.Digest{}, - }, - History: []ociv1.History{}, - } - for port, what := range config.ExposedPorts { - image.Config.ExposedPorts[string(port)] = what - } - RootFS := docker.V2S2RootFS{} - if dimage.RootFS != nil { - RootFS = *dimage.RootFS - } - if RootFS.Type == docker.TypeLayers { - image.RootFS.Type = docker.TypeLayers - image.RootFS.DiffIDs = append(image.RootFS.DiffIDs, RootFS.DiffIDs...) - } - for _, history := range dimage.History { - hcreated := history.Created.UTC() - ohistory := ociv1.History{ - Created: &hcreated, - CreatedBy: history.CreatedBy, - Author: history.Author, - Comment: history.Comment, - EmptyLayer: history.EmptyLayer, - } - image.History = append(image.History, ohistory) - } - return image, nil -} - -// makeDockerV2S2Image builds the best docker image structure we can from the -// contents of the OCI image structure. -func makeDockerV2S2Image(oimage *ociv1.Image) (docker.V2Image, error) { - image := docker.V2Image{ - V1Image: docker.V1Image{Created: oimage.Created.UTC(), - Author: oimage.Author, - Architecture: oimage.Architecture, - OS: oimage.OS, - ContainerConfig: docker.Config{ - User: oimage.Config.User, - ExposedPorts: docker.PortSet{}, - Env: oimage.Config.Env, - Entrypoint: oimage.Config.Entrypoint, - Cmd: oimage.Config.Cmd, - Volumes: oimage.Config.Volumes, - WorkingDir: oimage.Config.WorkingDir, - Labels: oimage.Config.Labels, - StopSignal: oimage.Config.StopSignal, +// unmarshalConvertedConfig obtains the config blob of img valid for the wantedManifestMIMEType format +// (either as it exists, or converting the image if necessary), and unmarshals it into dest. +// NOTE: The MIME type is of the _manifest_, not of the _config_ that is returned. +func unmarshalConvertedConfig(ctx context.Context, dest interface{}, img types.Image, wantedManifestMIMEType string) error { + _, actualManifestMIMEType, err := img.Manifest(ctx) + if err != nil { + return errors.Wrapf(err, "error getting manifest MIME type for %q", transports.ImageName(img.Reference())) + } + if wantedManifestMIMEType != actualManifestMIMEType { + img, err = img.UpdatedImage(ctx, types.ManifestUpdateOptions{ + ManifestMIMEType: wantedManifestMIMEType, + InformationOnly: types.ManifestUpdateInformation{ // Strictly speaking, every value in here is invalid. But… + Destination: nil, // Destination is technically required, but actually necessary only for conversion _to_ v2s1. Leave it nil, we will crash if that ever changes. + LayerInfos: nil, // LayerInfos is necessary for size information in v2s2/OCI manifests, but the code can work with nil, and we are not reading the converted manifest at all. + LayerDiffIDs: nil, // LayerDiffIDs are actually embedded in the converted manifest, but the code can work with nil, and the values are not needed until pushing the finished image, at which time containerImageRef.NewImageSource builds the values from scratch. }, - }, - RootFS: &docker.V2S2RootFS{ - Type: "", - DiffIDs: []digest.Digest{}, - }, - History: []docker.V2S2History{}, - } - for port, what := range oimage.Config.ExposedPorts { - image.ContainerConfig.ExposedPorts[docker.Port(port)] = what - } - if oimage.RootFS.Type == docker.TypeLayers { - image.RootFS.Type = docker.TypeLayers - image.RootFS.DiffIDs = append(image.RootFS.DiffIDs, oimage.RootFS.DiffIDs...) - } - for _, history := range oimage.History { - dhistory := docker.V2S2History{ - Created: history.Created.UTC(), - CreatedBy: history.CreatedBy, - Author: history.Author, - Comment: history.Comment, - EmptyLayer: history.EmptyLayer, + }) + if err != nil { + return errors.Wrapf(err, "error converting image %q to %s", transports.ImageName(img.Reference()), wantedManifestMIMEType) } - image.History = append(image.History, dhistory) } - image.Config = &image.ContainerConfig - return image, nil -} - -// makeDockerV2S1Image builds the best docker image structure we can from the -// contents of the V2S1 image structure. -func makeDockerV2S1Image(manifest docker.V2S1Manifest) (docker.V2Image, error) { - // Treat the most recent (first) item in the history as a description of the image. - if len(manifest.History) == 0 { - return docker.V2Image{}, errors.Errorf("error parsing image configuration from manifest") - } - dimage := docker.V2Image{} - err := json.Unmarshal([]byte(manifest.History[0].V1Compatibility), &dimage) + config, err := img.ConfigBlob(ctx) if err != nil { - return docker.V2Image{}, err - } - if dimage.DockerVersion == "" { - return docker.V2Image{}, errors.Errorf("error parsing image configuration from history") + return errors.Wrapf(err, "error reading %s config from %q", wantedManifestMIMEType, transports.ImageName(img.Reference())) } - // The DiffID list is intended to contain the sums of _uncompressed_ blobs, and these are most - // likely compressed, so leave the list empty to avoid potential confusion later on. We can - // construct a list with the correct values when we prep layers for pushing, so we don't lose. - // information by leaving this part undone. - rootFS := &docker.V2S2RootFS{ - Type: docker.TypeLayers, - DiffIDs: []digest.Digest{}, + if err := json.Unmarshal(config, dest); err != nil { + return errors.Wrapf(err, "error parsing %s configuration from %q", wantedManifestMIMEType, transports.ImageName(img.Reference())) } - // Build a filesystem history. - history := []docker.V2S2History{} - lastID := "" - for i := range manifest.History { - // Decode the compatibility field. - dcompat := docker.V1Compatibility{} - if err = json.Unmarshal([]byte(manifest.History[i].V1Compatibility), &dcompat); err != nil { - return docker.V2Image{}, errors.Errorf("error parsing image compatibility data (%q) from history", manifest.History[i].V1Compatibility) - } - // Skip this history item if it shares the ID of the last one - // that we saw, since the image library will do the same. - if i > 0 && dcompat.ID == lastID { - continue - } - lastID = dcompat.ID - // Construct a new history item using the recovered information. - createdBy := "" - if len(dcompat.ContainerConfig.Cmd) > 0 { - createdBy = strings.Join(dcompat.ContainerConfig.Cmd, " ") + return nil +} + +func (b *Builder) initConfig(ctx context.Context, img types.Image) error { + if img != nil { // A pre-existing image, as opposed to a "FROM scratch" new one. + rawManifest, manifestMIMEType, err := img.Manifest(ctx) + if err != nil { + return errors.Wrapf(err, "error reading image manifest for %q", transports.ImageName(img.Reference())) } - h := docker.V2S2History{ - Created: dcompat.Created.UTC(), - Author: dcompat.Author, - CreatedBy: createdBy, - Comment: dcompat.Comment, - EmptyLayer: dcompat.ThrowAway, + rawConfig, err := img.ConfigBlob(ctx) + if err != nil { + return errors.Wrapf(err, "error reading image configuration for %q", transports.ImageName(img.Reference())) } - // Prepend this layer to the list, because a v2s1 format manifest's list is in reverse order - // compared to v2s2, which lists earlier layers before later ones. - history = append([]docker.V2S2History{h}, history...) - } - dimage.RootFS = rootFS - dimage.History = history - return dimage, nil -} - -func (b *Builder) initConfig() { - image := ociv1.Image{} - dimage := docker.V2Image{} - if len(b.Config) > 0 { - // Try to parse the image configuration. If we fail start over from scratch. - if err := json.Unmarshal(b.Config, &dimage); err == nil && dimage.DockerVersion != "" { - if image, err = makeOCIv1Image(&dimage); err != nil { - image = ociv1.Image{} - } - } else { - if err := json.Unmarshal(b.Config, &image); err != nil { - if dimage, err = makeDockerV2S2Image(&image); err != nil { - dimage = docker.V2Image{} - } - } + b.Manifest = rawManifest + b.Config = rawConfig + + dimage := docker.V2Image{} + if err := unmarshalConvertedConfig(ctx, &dimage, img, manifest.DockerV2Schema2MediaType); err != nil { + return err } - b.OCIv1 = image b.Docker = dimage - } else { - // Try to dig out the image configuration from the manifest. - manifest := docker.V2S1Manifest{} - if err := json.Unmarshal(b.Manifest, &manifest); err == nil && manifest.SchemaVersion == 1 { - if dimage, err = makeDockerV2S1Image(manifest); err == nil { - if image, err = makeOCIv1Image(&dimage); err != nil { - image = ociv1.Image{} - } - } + + oimage := ociv1.Image{} + if err := unmarshalConvertedConfig(ctx, &oimage, img, ociv1.MediaTypeImageManifest); err != nil { + return err } - b.OCIv1 = image - b.Docker = dimage - } - if len(b.Manifest) > 0 { - // Attempt to recover format-specific data from the manifest. - v1Manifest := ociv1.Manifest{} - if json.Unmarshal(b.Manifest, &v1Manifest) == nil { + b.OCIv1 = oimage + + if manifestMIMEType == ociv1.MediaTypeImageManifest { + // Attempt to recover format-specific data from the manifest. + v1Manifest := ociv1.Manifest{} + if err := json.Unmarshal(b.Manifest, &v1Manifest); err != nil { + return errors.Wrapf(err, "error parsing OCI manifest") + } b.ImageAnnotations = v1Manifest.Annotations } } + b.fixupConfig() + return nil } func (b *Builder) fixupConfig() { diff --git a/vendor/github.com/projectatomic/buildah/imagebuildah/build.go b/vendor/github.com/projectatomic/buildah/imagebuildah/build.go index 58b653dce..69ed1822f 100644 --- a/vendor/github.com/projectatomic/buildah/imagebuildah/build.go +++ b/vendor/github.com/projectatomic/buildah/imagebuildah/build.go @@ -17,7 +17,6 @@ import ( "github.com/containers/image/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" - "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/stringid" "github.com/docker/docker/builder/dockerfile/parser" docker "github.com/fsouza/go-dockerclient" @@ -128,6 +127,13 @@ type BuildOptions struct { // ID mapping options to use if we're setting up our own user namespace // when handling RUN instructions. IDMappingOptions *buildah.IDMappingOptions + // AddCapabilities is a list of capabilities to add to the default set when + // handling RUN instructions. + AddCapabilities []string + // DropCapabilities is a list of capabilities to remove from the default set + // when handling RUN instructions. If a capability appears in both lists, it + // will be dropped. + DropCapabilities []string CommonBuildOpts *buildah.CommonBuildOptions // DefaultMountsFilePath is the file path holding the mounts to be mounted in "host-path:container-path" format DefaultMountsFilePath string @@ -472,8 +478,8 @@ func (b *Executor) Run(run imagebuilder.Run, config docker.Config) error { Entrypoint: config.Entrypoint, Cmd: config.Cmd, Stdin: devNull, - Stdout: ioutils.NopWriteCloser(b.out), - Stderr: ioutils.NopWriteCloser(b.err), + Stdout: b.out, + Stderr: b.err, Quiet: b.quiet, } if config.NetworkDisabled { diff --git a/vendor/github.com/projectatomic/buildah/import.go b/vendor/github.com/projectatomic/buildah/import.go index 90b1b90b9..b7ed3730f 100644 --- a/vendor/github.com/projectatomic/buildah/import.go +++ b/vendor/github.com/projectatomic/buildah/import.go @@ -13,40 +13,33 @@ import ( ) func importBuilderDataFromImage(ctx context.Context, store storage.Store, systemContext *types.SystemContext, imageID, containerName, containerID string) (*Builder, error) { - manifest := []byte{} - config := []byte{} - imageName := "" + if imageID == "" { + return nil, errors.Errorf("Internal error: imageID is empty in importBuilderDataFromImage") + } + uidmap, gidmap := convertStorageIDMaps(storage.DefaultStoreOptions.UIDMap, storage.DefaultStoreOptions.GIDMap) - if imageID != "" { - ref, err := is.Transport.ParseStoreReference(store, imageID) - if err != nil { - return nil, errors.Wrapf(err, "no such image %q", imageID) - } - src, err2 := ref.NewImage(ctx, systemContext) - if err2 != nil { - return nil, errors.Wrapf(err2, "error instantiating image") - } - defer src.Close() - config, err = src.ConfigBlob(ctx) - if err != nil { - return nil, errors.Wrapf(err, "error reading image configuration") - } - manifest, _, err = src.Manifest(ctx) - if err != nil { - return nil, errors.Wrapf(err, "error reading image manifest") + ref, err := is.Transport.ParseStoreReference(store, imageID) + if err != nil { + return nil, errors.Wrapf(err, "no such image %q", imageID) + } + src, err2 := ref.NewImage(ctx, systemContext) + if err2 != nil { + return nil, errors.Wrapf(err2, "error instantiating image") + } + defer src.Close() + + imageName := "" + if img, err3 := store.Image(imageID); err3 == nil { + if len(img.Names) > 0 { + imageName = img.Names[0] } - if img, err3 := store.Image(imageID); err3 == nil { - 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) + 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) } } @@ -55,8 +48,6 @@ func importBuilderDataFromImage(ctx context.Context, store storage.Store, system Type: containerType, FromImage: imageName, FromImageID: imageID, - Config: config, - Manifest: manifest, Container: containerName, ContainerID: containerID, ImageAnnotations: map[string]string{}, @@ -70,7 +61,9 @@ func importBuilderDataFromImage(ctx context.Context, store storage.Store, system }, } - builder.initConfig() + if err := builder.initConfig(ctx, src); err != nil { + return nil, errors.Wrapf(err, "error preparing image configuration") + } return builder, nil } diff --git a/vendor/github.com/projectatomic/buildah/new.go b/vendor/github.com/projectatomic/buildah/new.go index dc349221b..4474fac92 100644 --- a/vendor/github.com/projectatomic/buildah/new.go +++ b/vendor/github.com/projectatomic/buildah/new.go @@ -114,26 +114,6 @@ func imageNamePrefix(imageName string) string { return prefix } -func imageManifestAndConfig(ctx context.Context, ref types.ImageReference, systemContext *types.SystemContext) (manifest, config []byte, err error) { - if ref != nil { - src, err := ref.NewImage(ctx, systemContext) - if err != nil { - return nil, nil, errors.Wrapf(err, "error instantiating image for %q", transports.ImageName(ref)) - } - defer src.Close() - config, err := src.ConfigBlob(ctx) - if err != nil { - return nil, nil, errors.Wrapf(err, "error reading image configuration for %q", transports.ImageName(ref)) - } - manifest, _, err := src.Manifest(ctx) - if err != nil { - return nil, nil, errors.Wrapf(err, "error reading image manifest for %q", transports.ImageName(ref)) - } - return manifest, config, nil - } - return nil, nil, nil -} - func newContainerIDMappingOptions(idmapOptions *IDMappingOptions) storage.IDMappingOptions { var options storage.IDMappingOptions if idmapOptions != nil { @@ -229,8 +209,6 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions var ref types.ImageReference var img *storage.Image var err error - var manifest []byte - var config []byte if options.FromImage == BaseImageFakeName { options.FromImage = "" @@ -261,8 +239,13 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions imageID = img.ID topLayer = img.TopLayer } - if manifest, config, err = imageManifestAndConfig(ctx, ref, systemContext); err != nil { - return nil, errors.Wrapf(err, "error reading data from image %q", transports.ImageName(ref)) + var src types.ImageCloser + if ref != nil { + src, err = ref.NewImage(ctx, systemContext) + if err != nil { + return nil, errors.Wrapf(err, "error instantiating image for %q", transports.ImageName(ref)) + } + defer src.Close() } name := "working-container" @@ -317,8 +300,6 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions Type: containerType, FromImage: image, FromImageID: imageID, - Config: config, - Manifest: manifest, Container: name, ContainerID: container.ID, ImageAnnotations: map[string]string{}, @@ -336,8 +317,10 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions UIDMap: uidmap, GIDMap: gidmap, }, - CommonBuildOpts: options.CommonBuildOpts, - TopLayer: topLayer, + AddCapabilities: copyStringSlice(options.AddCapabilities), + DropCapabilities: copyStringSlice(options.DropCapabilities), + CommonBuildOpts: options.CommonBuildOpts, + TopLayer: topLayer, } if options.Mount { @@ -347,7 +330,9 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions } } - builder.initConfig() + if err := builder.initConfig(ctx, src); err != nil { + return nil, errors.Wrapf(err, "error preparing image configuration") + } err = builder.Save() if err != nil { return nil, errors.Wrapf(err, "error saving builder state") diff --git a/vendor/github.com/projectatomic/buildah/pkg/cli/common.go b/vendor/github.com/projectatomic/buildah/pkg/cli/common.go index 554b30a29..4a5deafca 100644 --- a/vendor/github.com/projectatomic/buildah/pkg/cli/common.go +++ b/vendor/github.com/projectatomic/buildah/pkg/cli/common.go @@ -185,7 +185,15 @@ var ( FromAndBudFlags = append(append([]cli.Flag{ cli.StringSliceFlag{ Name: "add-host", - Usage: "add a custom host-to-IP mapping (host:ip) (default [])", + Usage: "add a custom host-to-IP mapping (`host:ip`) (default [])", + }, + cli.StringSliceFlag{ + Name: "cap-add", + Usage: "add the specified capability when running (default [])", + }, + cli.StringSliceFlag{ + Name: "cap-drop", + Usage: "drop the specified capability when running (default [])", }, cli.StringFlag{ Name: "cgroup-parent", diff --git a/vendor/github.com/projectatomic/buildah/pkg/parse/parse.go b/vendor/github.com/projectatomic/buildah/pkg/parse/parse.go index 1a4a1e423..c6bd4665e 100644 --- a/vendor/github.com/projectatomic/buildah/pkg/parse/parse.go +++ b/vendor/github.com/projectatomic/buildah/pkg/parse/parse.go @@ -332,7 +332,7 @@ func getDockerAuth(creds string) (*types.DockerAuthConfig, error) { }, nil } -// IDMappingOptions parses the build options from user namespace +// IDMappingOptions parses the build options related to user namespaces and ID mapping. func IDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, idmapOptions *buildah.IDMappingOptions, err error) { user := c.String("userns-uid-map-user") group := c.String("userns-gid-map-group") @@ -367,7 +367,14 @@ func IDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, i } // Parse the flag's value as one or more triples (if it's even // been set), and append them. - idmap, err := parseIDMap(c.StringSlice(option)) + var spec []string + if c.GlobalIsSet(option) { + spec = c.GlobalStringSlice(option) + } + if c.IsSet(option) { + spec = c.StringSlice(option) + } + idmap, err := parseIDMap(spec) if err != nil { return nil, err } @@ -466,7 +473,7 @@ func parseIDMap(spec []string) (m [][3]uint32, err error) { return m, nil } -// NamesapceOptions parses the build options from all namespaces except user namespace +// NamespaceOptions parses the build options for all namespaces except for user namespace. func NamespaceOptions(c *cli.Context) (namespaceOptions buildah.NamespaceOptions, networkPolicy buildah.NetworkConfigurationPolicy, err error) { options := make(buildah.NamespaceOptions, 0, 7) policy := buildah.NetworkDefault diff --git a/vendor/github.com/projectatomic/buildah/run.go b/vendor/github.com/projectatomic/buildah/run.go index 0d96aed09..97d247fdc 100644 --- a/vendor/github.com/projectatomic/buildah/run.go +++ b/vendor/github.com/projectatomic/buildah/run.go @@ -12,7 +12,6 @@ import ( "os/exec" "path/filepath" "runtime" - "sort" "strconv" "strings" "sync" @@ -20,9 +19,7 @@ import ( "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" @@ -31,6 +28,7 @@ import ( "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/projectatomic/buildah/bind" "github.com/projectatomic/buildah/util" "github.com/projectatomic/libpod/pkg/secrets" "github.com/sirupsen/logrus" @@ -148,13 +146,23 @@ type RunOptions struct { // decision can be overridden by specifying either WithTerminal or // WithoutTerminal. Terminal TerminalPolicy + // TerminalSize provides a way to set the number of rows and columns in + // a pseudo-terminal, if we create one, and Stdin/Stdout/Stderr aren't + // connected to a terminal. + TerminalSize *specs.Box // The stdin/stdout/stderr descriptors to use. If set to nil, the // corresponding files in the "os" package are used as defaults. - Stdin io.ReadCloser `json:"-"` - Stdout io.WriteCloser `json:"-"` - Stderr io.WriteCloser `json:"-"` + Stdin io.Reader `json:"-"` + Stdout io.Writer `json:"-"` + Stderr io.Writer `json:"-"` // Quiet tells the run to turn off output to stdout. Quiet bool + // AddCapabilities is a list of capabilities to add to the default set. + AddCapabilities []string + // DropCapabilities is a list of capabilities to remove from the default set, + // after processing the AddCapabilities set. If a capability appears in both + // lists, it will be dropped. + DropCapabilities []string } // DefaultNamespaceOptions returns the default namespace settings from the @@ -229,7 +237,17 @@ func addRlimits(ulimit []string, g *generate.Generator) error { func addHosts(hosts []string, w io.Writer) error { buf := bufio.NewWriter(w) for _, host := range hosts { - fmt.Fprintln(buf, host) + values := strings.SplitN(host, ":", 2) + if len(values) != 2 { + return errors.Errorf("unable to parse host entry %q: incorrect format", host) + } + if values[0] == "" { + return errors.Errorf("hostname in host entry %q is empty", host) + } + if values[1] == "" { + return errors.Errorf("IP address in host entry %q is empty", host) + } + fmt.Fprintf(buf, "%s\t%s\n", values[1], values[0]) } return buf.Flush() } @@ -286,7 +304,7 @@ 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, namespaceOptions NamespaceOptions) error { +func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions NamespaceOptions) error { // Start building a new list of mounts. var mounts []specs.Mount haveMount := func(destination string) bool { @@ -316,7 +334,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Source: "/dev/shm", Type: "bind", Destination: "/dev/shm", - Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev"}, + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"}, } } } @@ -331,7 +349,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Source: "/dev/mqueue", Type: "bind", Destination: "/dev/mqueue", - Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev"}, + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"}, } } } @@ -347,7 +365,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Source: "/sys", Type: "bind", Destination: "/sys", - Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev", "ro"}, + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev", "ro"}, } } } @@ -363,12 +381,12 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Destination: "/sys/fs/cgroup", Type: "cgroup", Source: "cgroup", - Options: []string{"nobuildahbind", "nosuid", "noexec", "nodev", "relatime", "ro"}, + Options: []string{bind.NoBindOption, "nosuid", "noexec", "nodev", "relatime", "ro"}, }} } // Get the list of files we need to bind into the container. - bindFileMounts, err := runSetupBoundFiles(bindFiles) + bindFileMounts, err := runSetupBoundFiles(bundlePath, bindFiles) if err != nil { return err } @@ -381,7 +399,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts // Figure out which UID and GID to tell the secrets package to use // for files that it creates. - rootUID, rootGID, err := getHostRootIDs(spec) + rootUID, rootGID, err := util.GetHostRootIDs(spec) if err != nil { return err } @@ -418,13 +436,17 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts return nil } -func runSetupBoundFiles(bindFiles map[string]string) (mounts []specs.Mount, err error) { +func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts []specs.Mount, err error) { for dest, src := range bindFiles { + options := []string{"rbind"} + if strings.HasPrefix(src, bundlePath) { + options = append(options, bind.NoBindOption) + } mounts = append(mounts, specs.Mount{ Source: src, Destination: dest, Type: "bind", - Options: []string{"rbind"}, + Options: options, }) } return mounts, nil @@ -574,25 +596,88 @@ func setupReadOnlyPaths(g *generate.Generator) { } } +func setupCapAdd(g *generate.Generator, caps ...string) error { + for _, cap := range caps { + if err := g.AddProcessCapabilityBounding(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the bounding capability set", cap) + } + if err := g.AddProcessCapabilityEffective(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the effective capability set", cap) + } + if err := g.AddProcessCapabilityInheritable(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the inheritable capability set", cap) + } + if err := g.AddProcessCapabilityPermitted(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the permitted capability set", cap) + } + if err := g.AddProcessCapabilityAmbient(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the ambient capability set", cap) + } + } + return nil +} + +func setupCapDrop(g *generate.Generator, caps ...string) error { + for _, cap := range caps { + if err := g.DropProcessCapabilityBounding(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the bounding capability set", cap) + } + if err := g.DropProcessCapabilityEffective(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the effective capability set", cap) + } + if err := g.DropProcessCapabilityInheritable(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the inheritable capability set", cap) + } + if err := g.DropProcessCapabilityPermitted(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the permitted capability set", cap) + } + if err := g.DropProcessCapabilityAmbient(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the ambient capability set", cap) + } + } + return nil +} + +func setupCapabilities(g *generate.Generator, firstAdds, firstDrops, secondAdds, secondDrops []string) error { + g.ClearProcessCapabilities() + if err := setupCapAdd(g, util.DefaultCapabilities...); err != nil { + return err + } + if err := setupCapAdd(g, firstAdds...); err != nil { + return err + } + if err := setupCapDrop(g, firstDrops...); err != nil { + return err + } + if err := setupCapAdd(g, secondAdds...); err != nil { + return err + } + if err := setupCapDrop(g, secondDrops...); err != nil { + return err + } + return nil +} + 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 + switch seccompProfilePath { + case "unconfined": + spec.Linux.Seccomp = nil + case "": + seccompConfig, err := seccomp.GetDefaultProfile(spec) + if err != nil { + return errors.Wrapf(err, "loading default seccomp profile failed") + } + spec.Linux.Seccomp = seccompConfig + default: + 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 } return nil } @@ -602,15 +687,24 @@ func setupApparmor(spec *specs.Spec, apparmorProfile string) error { return nil } -func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy) { +func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) { switch terminalPolicy { case DefaultTerminal: - g.SetProcessTerminal(terminal.IsTerminal(int(os.Stdout.Fd()))) + onTerminal := terminal.IsTerminal(unix.Stdin) && terminal.IsTerminal(unix.Stdout) && terminal.IsTerminal(unix.Stderr) + if onTerminal { + logrus.Debugf("stdio is a terminal, defaulting to using a terminal") + } else { + logrus.Debugf("stdio is not a terminal, defaulting to not using a terminal") + } + g.SetProcessTerminal(onTerminal) case WithTerminal: g.SetProcessTerminal(true) case WithoutTerminal: g.SetProcessTerminal(false) } + if terminalSize != nil { + g.SetProcessConsoleSize(terminalSize.Width, terminalSize.Height) + } } func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, idmapOptions IDMappingOptions, policy NetworkConfigurationPolicy) (configureNetwork bool, configureNetworks []string, configureUTS bool, err error) { @@ -662,15 +756,15 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i 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)) } + hostUidmap, hostGidmap, err := util.GetHostIDMappings("") + if err != nil { + return false, nil, false, err + } 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 { + for _, m := range hostUidmap { g.AddLinuxUIDMapping(m.ContainerID, m.ContainerID, m.Size) } } @@ -678,11 +772,7 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i 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 { + for _, m := range hostGidmap { g.AddLinuxGIDMapping(m.ContainerID, m.ContainerID, m.Size) } } @@ -769,7 +859,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { g.SetRootPath(mountPoint) - setupTerminal(g, options.Terminal) + setupTerminal(g, options.Terminal, options.TerminalSize) namespaceOptions := DefaultNamespaceOptions() namespaceOptions.AddOrReplace(b.NamespaceOptions...) @@ -795,9 +885,13 @@ func (b *Builder) Run(command []string, options RunOptions) error { g.SetHostname("") } + // Set the user UID/GID/supplemental group list/capabilities lists. if user, err = b.user(mountPoint, options.User); err != nil { return err } + if err = setupCapabilities(g, b.AddCapabilities, b.DropCapabilities, options.AddCapabilities, options.DropCapabilities); err != nil { + return err + } g.SetProcessUID(user.UID) g.SetProcessGID(user.GID) for _, gid := range user.AdditionalGids { @@ -807,8 +901,9 @@ func (b *Builder) Run(command []string, options RunOptions) error { // 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 - //Remove capabilities if not running as root + // Remove capabilities if not running as root if user.UID != 0 { var caplist []string spec.Process.Capabilities.Permitted = caplist @@ -816,7 +911,8 @@ func (b *Builder) Run(command []string, options RunOptions) error { spec.Process.Capabilities.Effective = caplist spec.Process.Capabilities.Ambient = caplist } - g = nil + + // Set the working directory, creating it if we must. if spec.Process.Cwd == "" { spec.Process.Cwd = DefaultWorkingDir } @@ -829,7 +925,9 @@ func (b *Builder) Run(command []string, options RunOptions) error { return err } - // Set the seccomp configuration using the specified profile name. + // Set the seccomp configuration using the specified profile name. Some syscalls are + // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot), + // so we sorted out the capabilities lists first. if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil { return err } @@ -851,7 +949,7 @@ 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, append(b.NamespaceOptions, options.NamespaceOptions...)) + err = b.setupMounts(mountPoint, spec, path, 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") } @@ -977,9 +1075,11 @@ func runUsingRuntimeMain() { } 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. + // Lock the caller to a single OS-level thread. runtime.LockOSThread() - unmountAll, err := runSetupIntermediateMountNamespace(spec, bundlePath) + + // Set up bind mounts for things that a namespaced user might not be able to get to directly. + unmountAll, err := bind.SetupIntermediateMountNamespace(spec, bundlePath) if unmountAll != nil { defer func() { if err := unmountAll(); err != nil { @@ -1009,7 +1109,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork } // Default to not specifying a console socket location. - moreCreateArgs := func() []string { return nil } + var moreCreateArgs []string // Default to just passing down our stdio. getCreateStdio := func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) { return os.Stdin, os.Stdout, os.Stderr @@ -1021,7 +1121,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork var errorFds []int stdioPipe := make([][]int, 3) copyConsole := false - copyStdio := false + copyPipes := false finishCopy := make([]int, 2) if err = unix.Pipe(finishCopy); err != nil { return 1, errors.Wrapf(err, "error creating pipe for notifying to stop stdio") @@ -1037,11 +1137,11 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork return 1, errors.Wrapf(err, "error creating socket to receive terminal descriptor") } // Add console socket arguments. - moreCreateArgs = func() []string { return []string{"--console-socket", socketPath} } + moreCreateArgs = append(moreCreateArgs, "--console-socket", socketPath) } else { - copyStdio = true + copyPipes = true // Figure out who should own the pipes. - uid, gid, err := getHostRootIDs(spec) + uid, gid, err := util.GetHostRootIDs(spec) if err != nil { return 1, err } @@ -1069,7 +1169,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork // Build the commands that we'll execute. pidFile := filepath.Join(bundlePath, "pid") - args := append(append(append(options.Args, "create", "--bundle", bundlePath, "--pid-file", pidFile), moreCreateArgs()...), containerName) + args := append(append(append(options.Args, "create", "--bundle", bundlePath, "--pid-file", pidFile), moreCreateArgs...), containerName) create := exec.Command(runtime, args...) create.Dir = bundlePath stdin, stdout, stderr := getCreateStdio() @@ -1077,25 +1177,21 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork if create.SysProcAttr == nil { create.SysProcAttr = &syscall.SysProcAttr{} } - runSetDeathSig(create) args = append(options.Args, "start", containerName) start := exec.Command(runtime, args...) start.Dir = bundlePath start.Stderr = os.Stderr - runSetDeathSig(start) args = append(options.Args, "kill", containerName) kill := exec.Command(runtime, args...) kill.Dir = bundlePath kill.Stderr = os.Stderr - runSetDeathSig(kill) args = append(options.Args, "delete", containerName) del := exec.Command(runtime, args...) del.Dir = bundlePath del.Stderr = os.Stderr - runSetDeathSig(del) // Actually create the container. err = create.Run() @@ -1144,7 +1240,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork } } - if copyStdio { + if copyPipes { // We don't need the ends of the pipes that belong to the container. stdin.Close() if stdout != nil { @@ -1155,7 +1251,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork // Handle stdio for the container in the background. stdio.Add(1) - go runCopyStdio(&stdio, copyStdio, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy) + go runCopyStdio(&stdio, copyPipes, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy, spec) // Start the container. err = start.Run() @@ -1284,7 +1380,7 @@ func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNet 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 len(configureNetworks) > 0 && nc.Network != nil && (nc.Network.Name == "" || !util.StringInSlice(nc.Network.Name, configureNetworks)) { if nc.Network.Name == "" { logrus.Debugf("configuration in %q has no name, skipping it", file) } else { @@ -1304,7 +1400,7 @@ func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNet 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 len(configureNetworks) > 0 && (cl.Name == "" || !util.StringInSlice(cl.Name, configureNetworks)) { if cl.Name == "" { logrus.Debugf("configuration list in %q has no name, skipping it", list) } else { @@ -1359,10 +1455,10 @@ func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNet return teardown, nil } -func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}) { +func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}, spec *specs.Spec) { defer func() { unix.Close(finishCopy[0]) - if copyStdio { + if copyPipes { unix.Close(stdioPipe[unix.Stdin][1]) unix.Close(stdioPipe[unix.Stdout][0]) unix.Close(stdioPipe[unix.Stderr][0]) @@ -1370,37 +1466,6 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy stdio.Done() finishedCopy <- struct{}{} }() - // If we're not doing I/O handling, we're done. - if !copyConsole && !copyStdio { - return - } - terminalFD := -1 - if copyConsole { - // Accept a connection over our listening socket. - fd, err := runAcceptTerminal(consoleListener) - if err != nil { - logrus.Errorf("%v", err) - return - } - terminalFD = fd - // Set our terminal's mode to raw, to pass handling of special - // terminal input to the terminal in the container. - state, err := terminal.MakeRaw(unix.Stdin) - if err != nil { - logrus.Warnf("error setting terminal state: %v", err) - } else { - defer func() { - if err = terminal.Restore(unix.Stdin, state); err != nil { - logrus.Errorf("unable to restore terminal state: %v", err) - } - }() - // FIXME - if we're connected to a terminal, we should be - // passing the updated terminal size down when we receive a - // SIGWINCH. - } - } - // Track how many descriptors we're expecting data from. - reading := 0 // Map describing where data on an incoming descriptor should go. relayMap := make(map[int]int) // Map describing incoming and outgoing descriptors. @@ -1408,7 +1473,15 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy writeDesc := make(map[int]string) // Buffers. relayBuffer := make(map[int]*bytes.Buffer) + // Set up the terminal descriptor or pipes for polling. if copyConsole { + // Accept a connection over our listening socket. + fd, err := runAcceptTerminal(consoleListener, spec.Process.ConsoleSize) + if err != nil { + logrus.Errorf("%v", err) + return + } + terminalFD := fd // Input from our stdin, output from the terminal descriptor. relayMap[unix.Stdin] = terminalFD readDesc[unix.Stdin] = "stdin" @@ -1418,9 +1491,21 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy readDesc[terminalFD] = "container terminal output" relayBuffer[unix.Stdout] = new(bytes.Buffer) writeDesc[unix.Stdout] = "output" - reading = 2 + // Set our terminal's mode to raw, to pass handling of special + // terminal input to the terminal in the container. + if terminal.IsTerminal(unix.Stdin) { + if state, err := terminal.MakeRaw(unix.Stdin); err != nil { + logrus.Warnf("error setting terminal state: %v", err) + } else { + defer func() { + if err = terminal.Restore(unix.Stdin, state); err != nil { + logrus.Errorf("unable to restore terminal state: %v", err) + } + }() + } + } } - if copyStdio { + if copyPipes { // Input from our stdin, output from the stdout and stderr pipes. relayMap[unix.Stdin] = stdioPipe[unix.Stdin][1] readDesc[unix.Stdin] = "stdin" @@ -1434,7 +1519,6 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy readDesc[stdioPipe[unix.Stderr][0]] = "container stderr" relayBuffer[unix.Stderr] = new(bytes.Buffer) writeDesc[unix.Stderr] = "stderr" - reading = 3 } // Set our reading descriptors to non-blocking. for fd := range relayMap { @@ -1460,9 +1544,9 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy } // Pass data back and forth. pollTimeout := -1 - for { + for len(relayMap) > 0 { // Start building the list of descriptors to poll. - pollFds := make([]unix.PollFd, 0, reading+1) + pollFds := make([]unix.PollFd, 0, len(relayMap)+1) // Poll for a notification that we should stop handling stdio. pollFds = append(pollFds, unix.PollFd{Fd: int32(finishCopy[0]), Events: unix.POLLIN | unix.POLLHUP}) // Poll on our reading descriptors. @@ -1475,18 +1559,23 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy if !logIfNotRetryable(err, fmt.Sprintf("error waiting for stdio/terminal data to relay: %v", err)) { return } - var removes []int + removes := make(map[int]struct{}) for _, pollFd := range pollFds { // If this descriptor's just been closed from the other end, mark it for // removal from the set that we're checking for. if pollFd.Revents&unix.POLLHUP == unix.POLLHUP { - removes = append(removes, int(pollFd.Fd)) + removes[int(pollFd.Fd)] = struct{}{} + } + // If the descriptor was closed elsewhere, remove it from our list. + if pollFd.Revents&unix.POLLNVAL != 0 { + logrus.Debugf("error polling descriptor %d: closed?", pollFd.Fd) + removes[int(pollFd.Fd)] = struct{}{} } // If the POLLIN flag isn't set, then there's no data to be read from this descriptor. if pollFd.Revents&unix.POLLIN == 0 { // If we're using pipes and it's our stdin and it's closed, close the writing // end of the corresponding pipe. - if copyStdio && int(pollFd.Fd) == unix.Stdin && pollFd.Revents&unix.POLLHUP != 0 { + if copyPipes && int(pollFd.Fd) == unix.Stdin && pollFd.Revents&unix.POLLHUP != 0 { unix.Close(stdioPipe[unix.Stdin][1]) stdioPipe[unix.Stdin][1] = -1 } @@ -1503,7 +1592,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy // If it's zero-length on our stdin and we're // using pipes, it's an EOF, so close the stdin // pipe's writing end. - if n == 0 && copyStdio && int(pollFd.Fd) == unix.Stdin { + if n == 0 && copyPipes && int(pollFd.Fd) == unix.Stdin { unix.Close(stdioPipe[unix.Stdin][1]) stdioPipe[unix.Stdin][1] = -1 } @@ -1531,13 +1620,8 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy } } // Remove any descriptors which we don't need to poll any more from the poll descriptor list. - for _, remove := range removes { + for remove := range removes { delete(relayMap, remove) - reading-- - } - if reading == 0 { - // We have no more open descriptors to read, so we can stop now. - return } // If the we-can-return pipe had anything for us, we're done. for _, pollFd := range pollFds { @@ -1549,7 +1633,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy } } -func runAcceptTerminal(consoleListener *net.UnixListener) (int, error) { +func runAcceptTerminal(consoleListener *net.UnixListener, terminalSize *specs.Box) (int, error) { defer consoleListener.Close() c, err := consoleListener.AcceptUnix() if err != nil { @@ -1592,28 +1676,33 @@ func runAcceptTerminal(consoleListener *net.UnixListener) (int, error) { if terminalFD == -1 { return -1, errors.Errorf("unable to read terminal descriptor") } - // Set the pseudoterminal's size to match our own. - winsize, err := unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ) - if err != nil { - logrus.Warnf("error reading size of controlling terminal: %v", err) - return terminalFD, nil + // Set the pseudoterminal's size to the configured size, or our own. + winsize := &unix.Winsize{} + if terminalSize != nil { + // Use configured sizes. + winsize.Row = uint16(terminalSize.Height) + winsize.Col = uint16(terminalSize.Width) + } else { + if terminal.IsTerminal(unix.Stdin) { + // Use the size of our terminal. + if winsize, err = unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ); err != nil { + logrus.Warnf("error reading size of controlling terminal: %v", err) + winsize.Row = 0 + winsize.Col = 0 + } + } } - err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize) - if err != nil { - logrus.Warnf("error setting size of container pseudoterminal: %v", err) + if winsize.Row != 0 && winsize.Col != 0 { + if err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize); err != nil { + logrus.Warnf("error setting size of container pseudoterminal: %v", err) + } + // FIXME - if we're connected to a terminal, we should + // be passing the updated terminal size down when we + // receive a SIGWINCH. } return terminalFD, nil } -func runSetDeathSig(cmd *exec.Cmd) { - if cmd.SysProcAttr == nil { - cmd.SysProcAttr = &syscall.SysProcAttr{} - } - if cmd.SysProcAttr.Pdeathsig == 0 { - cmd.SysProcAttr.Pdeathsig = syscall.SIGTERM - } -} - // Create pipes to use for relaying stdio. func runMakeStdioPipe(uid, gid int) ([][]int, error) { stdioPipe := make([][]int, 3) @@ -1634,188 +1723,3 @@ func runMakeStdioPipe(uid, gid int) ([][]int, error) { } 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 661221ebc..3e86d8c0e 100644 --- a/vendor/github.com/projectatomic/buildah/util.go +++ b/vendor/github.com/projectatomic/buildah/util.go @@ -2,11 +2,8 @@ package buildah import ( "archive/tar" - "bufio" "io" "os" - "strconv" - "strings" "sync" "github.com/containers/image/docker/reference" @@ -41,15 +38,6 @@ func copyStringSlice(s []string) []string { 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)) @@ -178,77 +166,6 @@ func (b *Builder) tarPath() func(path string) (io.ReadCloser, error) { } } -// 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) -} - // getRegistries obtains the list of registries defined in the global registries file. func getRegistries() ([]string, error) { registryConfigPath := "" diff --git a/vendor/github.com/projectatomic/buildah/util/types.go b/vendor/github.com/projectatomic/buildah/util/types.go index bce419c02..974b707fb 100644 --- a/vendor/github.com/projectatomic/buildah/util/types.go +++ b/vendor/github.com/projectatomic/buildah/util/types.go @@ -8,3 +8,23 @@ const ( // DefaultCNIConfigDir is the default location of CNI configuration files. DefaultCNIConfigDir = "/etc/cni/net.d" ) + +var ( + // DefaultCapabilities is the list of capabilities which we grant by + // default to containers which are running under UID 0. + DefaultCapabilities = []string{ + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT", + } +) diff --git a/vendor/github.com/projectatomic/buildah/util/util.go b/vendor/github.com/projectatomic/buildah/util/util.go index 8ac9f8a6c..61705867d 100644 --- a/vendor/github.com/projectatomic/buildah/util/util.go +++ b/vendor/github.com/projectatomic/buildah/util/util.go @@ -1,11 +1,13 @@ package util import ( + "bufio" "fmt" "io" "net/url" "os" "path" + "strconv" "strings" "github.com/containers/image/directory" @@ -17,7 +19,9 @@ import ( "github.com/containers/image/tarball" "github.com/containers/image/types" "github.com/containers/storage" + "github.com/containers/storage/pkg/idtools" "github.com/docker/distribution/registry/api/errcode" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -243,3 +247,184 @@ func Runtime() string { } return DefaultRuntime } + +// StringInSlice returns a boolean indicating if the exact value s is present +// in the slice slice. +func StringInSlice(s string, slice []string) bool { + for _, v := range slice { + if v == s { + return true + } + } + return false +} + +// 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 []specs.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 *specs.Spec) (uint32, uint32, error) { + if spec.Linux == nil { + return 0, 0, nil + } + return GetHostIDs(spec.Linux.UIDMappings, spec.Linux.GIDMappings, 0, 0) +} + +// getHostIDMappings reads mappings from the named node under /proc. +func getHostIDMappings(path string) ([]specs.LinuxIDMapping, error) { + var mappings []specs.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, specs.LinuxIDMapping{ContainerID: uint32(cid), HostID: uint32(hid), Size: uint32(size)}) + } + return mappings, nil +} + +// GetHostIDMappings reads mappings for the current process from the kernel. +func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) { + if pid == "" { + pid = "self" + } + uidmap, err := getHostIDMappings(fmt.Sprintf("/proc/%s/uid_map", pid)) + if err != nil { + return nil, nil, err + } + gidmap, err := getHostIDMappings(fmt.Sprintf("/proc/%s/gid_map", pid)) + if err != nil { + return nil, nil, err + } + return uidmap, gidmap, nil +} + +// GetSubIDMappings reads mappings from /etc/subuid and /etc/subgid. +func GetSubIDMappings(user, group string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) { + mappings, err := idtools.NewIDMappings(user, group) + if err != nil { + return nil, nil, errors.Wrapf(err, "error reading subuid mappings for user %q and subgid mappings for group %q", user, group) + } + var uidmap, gidmap []specs.LinuxIDMapping + for _, m := range mappings.UIDs() { + uidmap = append(uidmap, specs.LinuxIDMapping{ + ContainerID: uint32(m.ContainerID), + HostID: uint32(m.HostID), + Size: uint32(m.Size), + }) + } + for _, m := range mappings.GIDs() { + gidmap = append(gidmap, specs.LinuxIDMapping{ + ContainerID: uint32(m.ContainerID), + HostID: uint32(m.HostID), + Size: uint32(m.Size), + }) + } + return uidmap, gidmap, nil +} + +// ParseIDMappings parses mapping triples. +func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, error) { + nonDigitsToWhitespace := func(r rune) rune { + if strings.IndexRune("0123456789", r) == -1 { + return ' ' + } else { + return r + } + } + parseTriple := func(spec []string) (container, host, size uint32, err error) { + cid, err := strconv.ParseUint(spec[0], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[0], err) + } + hid, err := strconv.ParseUint(spec[1], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[1], err) + } + sz, err := strconv.ParseUint(spec[2], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[2], err) + } + return uint32(cid), uint32(hid), uint32(sz), nil + } + parseIDMap := func(mapSpec []string, mapSetting string) (idmap []idtools.IDMap, err error) { + for _, idMapSpec := range mapSpec { + idSpec := strings.Fields(strings.Map(nonDigitsToWhitespace, idMapSpec)) + if len(idSpec)%3 != 0 { + return nil, errors.Errorf("error initializing ID mappings: %s setting is malformed", mapSetting) + } + for i := range idSpec { + if i%3 != 0 { + continue + } + cid, hid, size, err := parseTriple(idSpec[i : i+3]) + if err != nil { + return nil, errors.Errorf("error initializing ID mappings: %s setting is malformed", mapSetting) + } + mapping := idtools.IDMap{ + ContainerID: int(cid), + HostID: int(hid), + Size: int(size), + } + idmap = append(idmap, mapping) + } + } + return idmap, nil + } + uid, err := parseIDMap(uidmap, "userns-uid-map") + if err != nil { + return nil, nil, err + } + gid, err := parseIDMap(gidmap, "userns-gid-map") + if err != nil { + return nil, nil, err + } + return uid, gid, nil +} diff --git a/vendor/github.com/projectatomic/buildah/vendor.conf b/vendor/github.com/projectatomic/buildah/vendor.conf index 4a00ad05e..7fb20cd61 100644 --- a/vendor/github.com/projectatomic/buildah/vendor.conf +++ b/vendor/github.com/projectatomic/buildah/vendor.conf @@ -32,7 +32,7 @@ github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/runc master github.com/opencontainers/runtime-spec v1.0.0 github.com/opencontainers/runtime-tools master -github.com/opencontainers/selinux b29023b86e4a69d1b46b7e7b4e2b6fda03f0b9cd +github.com/opencontainers/selinux 6ccd0b50d53ae771fe5259ff7a4039110777aa2d github.com/openshift/imagebuilder master github.com/ostreedev/ostree-go aeb02c6b6aa2889db3ef62f7855650755befd460 github.com/pborman/uuid master -- cgit v1.2.3-54-g00ecf