Add cap-add and cap-drop to build man page

Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #968
Approved by: mheon
This commit is contained in:
TomSweeneyRedHat
2018-06-19 10:03:34 -04:00
committed by Atomic Bot
parent 82a948c04e
commit 89af35175d
19 changed files with 968 additions and 662 deletions

View File

@ -859,7 +859,8 @@ _podman_build() {
--annotation
--authfile
--build-arg
--cert-dir
--cap-add
--cap-drop
--cgroup-parent
--cni-config-dir
--cni-plugin-path

View File

@ -47,6 +47,26 @@ resulting image's configuration.
Images to utilize as potential cache sources. Podman does not currently support caching so this is a NOOP.
**--cap-add**=*CAP\_xxx*
When executing RUN instructions, run the command specified in the instruction
with the specified capability added to its capability set.
Certain capabilities are granted by default; this option can be used to add
more.
**--cap-drop**=*CAP\_xxx*
When executing RUN instructions, run the command specified in the instruction
with the specified capability removed from its capability set.
The 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, and CAP\_SYS\_CHROOT capabilities are
granted by default; this option can be used to remove them.
If a capability is specified to both the **--cap-add** and **--cap-drop**
options, it will be dropped, regardless of the order in which the options were
given.
**--cert-dir** *path*
Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry.
@ -360,9 +380,17 @@ Directly specifies a UID mapping which should be used to set ownership, at the
filesytem level, on the working container's contents.
Commands run when handling `RUN` instructions will default to being run in
their own user namespaces, configured using the UID and GID maps.
Entries in this map take the form of one or more triples of a starting
in-container UID, a corresponding starting host-level UID, and the number of
consecutive IDs which the map entry represents.
This option overrides the *remap-uids* setting in the *options* section of
/etc/containers/storage.conf.
If this option is not specified, but a global --userns-uid-map setting is
supplied, settings from the global option will be used.
If none of --userns-uid-map-user, --userns-gid-map-group, or --userns-uid-map
are specified, but --userns-gid-map is specified, the UID map will be set to
use the same numeric values as the GID map.
@ -373,9 +401,17 @@ Directly specifies a GID mapping which should be used to set ownership, at the
filesytem level, on the working container's contents.
Commands run when handling `RUN` instructions will default to being run in
their own user namespaces, configured using the UID and GID maps.
Entries in this map take the form of one or more triples of a starting
in-container GID, a corresponding starting host-level GID, and the number of
consecutive IDs which the map entry represents.
This option overrides the *remap-gids* setting in the *options* section of
/etc/containers/storage.conf.
If this option is not specified, but a global --userns-gid-map setting is
supplied, settings from the global option will be used.
If none of --userns-uid-map-user, --userns-gid-map-group, or --userns-gid-map
are specified, but --userns-uid-map is specified, the GID map will be set to
use the same numeric values as the UID map.

View File

@ -88,7 +88,7 @@ k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/
k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils
github.com/mrunalp/fileutils master
github.com/varlink/go master
github.com/projectatomic/buildah fc438bb932e891cbe04109cfae1dfbe3c99307a5
github.com/projectatomic/buildah 2441ff4f9f6a5e635f85c177892f096a46503d6f
github.com/Nvveen/Gotty master
github.com/fsouza/go-dockerclient master
github.com/openshift/imagebuilder master

View File

@ -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
}

294
vendor/github.com/projectatomic/buildah/bind/mount.go generated vendored Normal file
View File

@ -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
}

View File

@ -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
}

39
vendor/github.com/projectatomic/buildah/bind/util.go generated vendored Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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,
},
},
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,
}
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)
// 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 docker.V2Image{}, err
return errors.Wrapf(err, "error getting manifest MIME type for %q", transports.ImageName(img.Reference()))
}
if dimage.DockerVersion == "" {
return docker.V2Image{}, errors.Errorf("error parsing image configuration from history")
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.
},
})
if err != nil {
return errors.Wrapf(err, "error converting image %q to %s", transports.ImageName(img.Reference()), wantedManifestMIMEType)
}
}
// 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{},
config, err := img.ConfigBlob(ctx)
if err != nil {
return errors.Wrapf(err, "error reading %s config 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, " ")
}
h := docker.V2S2History{
Created: dcompat.Created.UTC(),
Author: dcompat.Author,
CreatedBy: createdBy,
Comment: dcompat.Comment,
EmptyLayer: dcompat.ThrowAway,
}
// 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...)
if err := json.Unmarshal(config, dest); err != nil {
return errors.Wrapf(err, "error parsing %s configuration from %q", wantedManifestMIMEType, transports.ImageName(img.Reference()))
}
dimage.RootFS = rootFS
dimage.History = history
return dimage, nil
return 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{}
}
}
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()))
}
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{}
}
}
rawConfig, err := img.ConfigBlob(ctx)
if err != nil {
return errors.Wrapf(err, "error reading image configuration for %q", transports.ImageName(img.Reference()))
}
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
}
if len(b.Manifest) > 0 {
// Attempt to recover format-specific data from the manifest.
v1Manifest := ociv1.Manifest{}
if json.Unmarshal(b.Manifest, &v1Manifest) == nil {
oimage := ociv1.Image{}
if err := unmarshalConvertedConfig(ctx, &oimage, img, ociv1.MediaTypeImageManifest); err != nil {
return err
}
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() {

View File

@ -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 {

View File

@ -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)
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]
}
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")
}
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
}

View File

@ -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")

View File

@ -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",

View File

@ -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

View File

@ -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 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
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 {
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
}

View File

@ -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 := ""

View File

@ -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",
}
)

View File

@ -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
}

View File

@ -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