summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.papr.yml1
-rw-r--r--Dockerfile.Fedora2
-rw-r--r--cmd/podman/main.go5
-rw-r--r--cmd/podman/ps.go13
-rw-r--r--cmd/podman/rm.go23
-rw-r--r--cmd/podman/shared/container.go4
-rw-r--r--cmd/podman/shared/parallel.go91
-rw-r--r--cmd/podman/stop.go20
-rw-r--r--cmd/podman/utils.go66
-rw-r--r--libpod/container_internal.go34
-rw-r--r--libpod/container_internal_linux.go92
-rw-r--r--libpod/networking_linux.go30
-rw-r--r--vendor/github.com/cyphar/filepath-securejoin/LICENSE28
-rw-r--r--vendor/github.com/cyphar/filepath-securejoin/README.md65
-rw-r--r--vendor/github.com/cyphar/filepath-securejoin/join.go134
-rw-r--r--vendor/github.com/cyphar/filepath-securejoin/vendor.conf1
-rw-r--r--vendor/github.com/cyphar/filepath-securejoin/vfs.go41
17 files changed, 510 insertions, 140 deletions
diff --git a/.papr.yml b/.papr.yml
index 97068546d..8a043a04a 100644
--- a/.papr.yml
+++ b/.papr.yml
@@ -122,6 +122,7 @@ packages:
- python3-varlink
- python3-dateutil
- python3-psutil
+ - https://kojipkgs.fedoraproject.org//packages/runc/1.0.0/55.dev.git578fe65.fc28/x86_64/runc-1.0.0-55.dev.git578fe65.fc28.x86_64.rpm
tests:
- sed 's/^expand-check.*/expand-check=0/g' -i /etc/selinux/semanage.conf
diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora
index c83097227..38cd599d4 100644
--- a/Dockerfile.Fedora
+++ b/Dockerfile.Fedora
@@ -17,7 +17,7 @@ RUN dnf -y install btrfs-progs-devel \
libseccomp-devel \
libselinux-devel \
skopeo-containers \
- runc \
+ https://kojipkgs.fedoraproject.org//packages/runc/1.0.0/55.dev.git578fe65.fc28/x86_64/runc-1.0.0-55.dev.git578fe65.fc28.x86_64.rpm \
make \
ostree-devel \
python \
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index d4c8454a8..38eac4504 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -211,6 +211,11 @@ func main() {
Value: hooks.DefaultDir,
Hidden: true,
},
+ cli.IntFlag{
+ Name: "max-workers",
+ Usage: "the maximum number of workers for parallel operations",
+ Hidden: true,
+ },
cli.StringFlag{
Name: "log-level",
Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic",
diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go
index a468f6121..d63618e58 100644
--- a/cmd/podman/ps.go
+++ b/cmd/podman/ps.go
@@ -20,6 +20,7 @@ import (
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-units"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/urfave/cli"
"k8s.io/apimachinery/pkg/fields"
)
@@ -300,7 +301,13 @@ func psCmd(c *cli.Context) error {
outputContainers = []*libpod.Container{latestCtr}
}
- pss := shared.PBatch(outputContainers, 8, opts)
+ maxWorkers := shared.Parallelize("ps")
+ if c.GlobalIsSet("max-workers") {
+ maxWorkers = c.GlobalInt("max-workers")
+ }
+ logrus.Debugf("Setting maximum workers to %d", maxWorkers)
+
+ pss := shared.PBatch(outputContainers, maxWorkers, opts)
if opts.Sort != "" {
pss, err = sortPsOutput(opts.Sort, pss)
if err != nil {
@@ -344,7 +351,9 @@ func psCmd(c *cli.Context) error {
// Output Namespace headers
fmt.Fprintf(w, "\n%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", hid, hnames, nspid, nscgroup, nsipc, nsmnt, nsnet, nspidns, nsuserns, nsuts)
}
-
+ if len(pss) == 0 {
+ fmt.Fprint(w, "\n")
+ }
// Now iterate each container and output its information
for _, container := range pss {
diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go
index c6641e879..0fb5345ee 100644
--- a/cmd/podman/rm.go
+++ b/cmd/podman/rm.go
@@ -2,11 +2,11 @@ package main
import (
"fmt"
- rt "runtime"
-
"github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@@ -48,14 +48,13 @@ func rmCmd(c *cli.Context) error {
var (
delContainers []*libpod.Container
lastError error
- deleteFuncs []workerInput
+ deleteFuncs []shared.ParallelWorkerInput
)
ctx := getContext()
if err := validateFlags(c, rmFlags); err != nil {
return err
}
-
runtime, err := libpodruntime.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
@@ -69,17 +68,23 @@ func rmCmd(c *cli.Context) error {
delContainers, lastError = getAllOrLatestContainers(c, runtime, -1, "all")
for _, container := range delContainers {
+ con := container
f := func() error {
- return runtime.RemoveContainer(ctx, container, c.Bool("force"))
+ return runtime.RemoveContainer(ctx, con, c.Bool("force"))
}
- deleteFuncs = append(deleteFuncs, workerInput{
- containerID: container.ID(),
- parallelFunc: f,
+ deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{
+ ContainerID: con.ID(),
+ ParallelFunc: f,
})
}
+ maxWorkers := shared.Parallelize("rm")
+ if c.GlobalIsSet("max-workers") {
+ maxWorkers = c.GlobalInt("max-workers")
+ }
+ logrus.Debugf("Setting maximum workers to %d", maxWorkers)
- deleteErrors := parallelExecuteWorkerPool(rt.NumCPU()*3, deleteFuncs)
+ deleteErrors := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs)
for cid, result := range deleteErrors {
if result != nil {
fmt.Println(result.Error())
diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go
index 4af737e0a..b847314a4 100644
--- a/cmd/podman/shared/container.go
+++ b/cmd/podman/shared/container.go
@@ -226,10 +226,10 @@ func NewBatchContainer(ctr *libpod.Container, opts PsOptions) (PsContainerOutput
return pso, nil
}
-type pFunc func() (PsContainerOutput, error)
+type batchFunc func() (PsContainerOutput, error)
type workerInput struct {
- parallelFunc pFunc
+ parallelFunc batchFunc
opts PsOptions
cid string
job int
diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go
new file mode 100644
index 000000000..03eba2f0b
--- /dev/null
+++ b/cmd/podman/shared/parallel.go
@@ -0,0 +1,91 @@
+package shared
+
+import (
+ "runtime"
+ "sync"
+)
+
+type pFunc func() error
+
+// ParallelWorkerInput is a struct used to pass in a slice of parallel funcs to be
+// performed on a container ID
+type ParallelWorkerInput struct {
+ ContainerID string
+ ParallelFunc pFunc
+}
+
+type containerError struct {
+ ContainerID string
+ Err error
+}
+
+// ParallelWorker is a "threaded" worker that takes jobs from the channel "queue"
+func ParallelWorker(wg *sync.WaitGroup, jobs <-chan ParallelWorkerInput, results chan<- containerError) {
+ for j := range jobs {
+ err := j.ParallelFunc()
+ results <- containerError{ContainerID: j.ContainerID, Err: err}
+ wg.Done()
+ }
+}
+
+// ParallelExecuteWorkerPool takes container jobs and performs them in parallel. The worker
+// int determines how many workers/threads should be premade.
+func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map[string]error {
+ var (
+ wg sync.WaitGroup
+ )
+
+ resultChan := make(chan containerError, len(functions))
+ results := make(map[string]error)
+ paraJobs := make(chan ParallelWorkerInput, len(functions))
+
+ // If we have more workers than functions, match up the number of workers and functions
+ if workers > len(functions) {
+ workers = len(functions)
+ }
+
+ // Create the workers
+ for w := 1; w <= workers; w++ {
+ go ParallelWorker(&wg, paraJobs, resultChan)
+ }
+
+ // Add jobs to the workers
+ for _, j := range functions {
+ j := j
+ wg.Add(1)
+ paraJobs <- j
+ }
+
+ close(paraJobs)
+ wg.Wait()
+
+ close(resultChan)
+ for ctrError := range resultChan {
+ results[ctrError.ContainerID] = ctrError.Err
+ }
+
+ return results
+}
+
+// Parallelize provides the maximum number of parallel workers (int) as calculated by a basic
+// heuristic. This can be overriden by the --max-workers primary switch to podman.
+func Parallelize(job string) int {
+ numCpus := runtime.NumCPU()
+ switch job {
+ case "stop":
+ if numCpus <= 2 {
+ return 4
+ } else {
+ return numCpus * 3
+ }
+ case "rm":
+ if numCpus <= 3 {
+ return numCpus * 3
+ } else {
+ return numCpus * 4
+ }
+ case "ps":
+ return 8
+ }
+ return 3
+}
diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go
index edadbda89..afeb49f76 100644
--- a/cmd/podman/stop.go
+++ b/cmd/podman/stop.go
@@ -2,12 +2,12 @@ package main
import (
"fmt"
- rt "runtime"
-
"github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@@ -61,7 +61,7 @@ func stopCmd(c *cli.Context) error {
containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running")
- var stopFuncs []workerInput
+ var stopFuncs []shared.ParallelWorkerInput
for _, ctr := range containers {
con := ctr
var stopTimeout uint
@@ -73,13 +73,19 @@ func stopCmd(c *cli.Context) error {
f := func() error {
return con.StopWithTimeout(stopTimeout)
}
- stopFuncs = append(stopFuncs, workerInput{
- containerID: con.ID(),
- parallelFunc: f,
+ stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{
+ ContainerID: con.ID(),
+ ParallelFunc: f,
})
}
- stopErrors := parallelExecuteWorkerPool(rt.NumCPU()*3, stopFuncs)
+ maxWorkers := shared.Parallelize("stop")
+ if c.GlobalIsSet("max-workers") {
+ maxWorkers = c.GlobalInt("max-workers")
+ }
+ logrus.Debugf("Setting maximum workers to %d", maxWorkers)
+
+ stopErrors := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs)
for cid, result := range stopErrors {
if result != nil && result != libpod.ErrCtrStopped {
diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go
index f9971fd88..afeccb668 100644
--- a/cmd/podman/utils.go
+++ b/cmd/podman/utils.go
@@ -3,10 +3,6 @@ package main
import (
"context"
"fmt"
- "os"
- gosignal "os/signal"
- "sync"
-
"github.com/containers/libpod/libpod"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
@@ -15,6 +11,8 @@ import (
"github.com/urfave/cli"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/client-go/tools/remotecommand"
+ "os"
+ gosignal "os/signal"
)
type RawTtyFormatter struct {
@@ -209,63 +207,3 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error
}
return pods, lastError
}
-
-type pFunc func() error
-
-type workerInput struct {
- containerID string
- parallelFunc pFunc
-}
-
-type containerError struct {
- containerID string
- err error
-}
-
-// worker is a "threaded" worker that takes jobs from the channel "queue"
-func worker(wg *sync.WaitGroup, jobs <-chan workerInput, results chan<- containerError) {
- for j := range jobs {
- err := j.parallelFunc()
- results <- containerError{containerID: j.containerID, err: err}
- wg.Done()
- }
-}
-
-// parallelExecuteWorkerPool takes container jobs and performs them in parallel. The worker
-// int is determines how many workers/threads should be premade.
-func parallelExecuteWorkerPool(workers int, functions []workerInput) map[string]error {
- var (
- wg sync.WaitGroup
- )
-
- resultChan := make(chan containerError, len(functions))
- results := make(map[string]error)
- paraJobs := make(chan workerInput, len(functions))
-
- // If we have more workers than functions, match up the number of workers and functions
- if workers > len(functions) {
- workers = len(functions)
- }
-
- // Create the workers
- for w := 1; w <= workers; w++ {
- go worker(&wg, paraJobs, resultChan)
- }
-
- // Add jobs to the workers
- for _, j := range functions {
- j := j
- wg.Add(1)
- paraJobs <- j
- }
-
- close(paraJobs)
- wg.Wait()
-
- close(resultChan)
- for ctrError := range resultChan {
- results[ctrError.containerID] = ctrError.err
- }
-
- return results
-}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index cb6b940fd..2af216358 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -750,30 +750,31 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e
// TODO: Add ability to override mount label so we can use this for Mount() too
// TODO: Can we use this for export? Copying SHM into the export might not be
// good
-func (c *Container) mountStorage() (err error) {
+func (c *Container) mountStorage() (string, error) {
+ var err error
// Container already mounted, nothing to do
if c.state.Mounted {
- return nil
+ return c.state.Mountpoint, nil
}
if !rootless.IsRootless() {
// TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts
mounted, err := mount.Mounted(c.config.ShmDir)
if err != nil {
- return errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir)
+ return "", errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir)
}
if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil {
- return errors.Wrapf(err, "failed to chown %s", c.config.ShmDir)
+ return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir)
}
if !mounted {
shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize)
if err := c.mountSHM(shmOptions); err != nil {
- return err
+ return "", err
}
if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil {
- return errors.Wrapf(err, "failed to chown %s", c.config.ShmDir)
+ return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir)
}
}
}
@@ -782,28 +783,11 @@ func (c *Container) mountStorage() (err error) {
if mountPoint == "" {
mountPoint, err = c.mount()
if err != nil {
- return err
+ return "", err
}
}
- c.state.Mounted = true
- c.state.Mountpoint = mountPoint
- if c.state.UserNSRoot == "" {
- c.state.RealMountpoint = c.state.Mountpoint
- } else {
- c.state.RealMountpoint = filepath.Join(c.state.UserNSRoot, "mountpoint")
- }
-
- logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint)
-
- defer func() {
- if err != nil {
- if err2 := c.cleanupStorage(); err2 != nil {
- logrus.Errorf("Error unmounting storage for container %s: %v", c.ID(), err)
- }
- }
- }()
- return c.save()
+ return mountPoint, nil
}
// cleanupStorage unmounts and cleans up the container's root filesystem
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index b25645e5c..0a1784ba7 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -12,15 +12,19 @@ import (
"path"
"path/filepath"
"strings"
+ "sync"
"syscall"
"time"
cnitypes "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containernetworking/plugins/pkg/ns"
crioAnnotations "github.com/containers/libpod/pkg/annotations"
"github.com/containers/libpod/pkg/chrootuser"
"github.com/containers/libpod/pkg/criu"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage/pkg/idtools"
+ "github.com/cyphar/filepath-securejoin"
+ "github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
@@ -49,24 +53,61 @@ func (c *Container) unmountSHM(mount string) error {
// prepare mounts the container and sets up other required resources like net
// namespaces
func (c *Container) prepare() (err error) {
+ var (
+ wg sync.WaitGroup
+ netNS ns.NetNS
+ networkStatus []*cnitypes.Result
+ createNetNSErr, mountStorageErr error
+ mountPoint string
+ saveNetworkStatus bool
+ )
+
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+ // Set up network namespace if not already set up
+ if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS {
+ saveNetworkStatus = true
+ netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
+ }
+ }()
// Mount storage if not mounted
- if err := c.mountStorage(); err != nil {
- return err
- }
+ go func() {
+ defer wg.Done()
+ mountPoint, mountStorageErr = c.mountStorage()
+ }()
- // Set up network namespace if not already set up
- if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS {
- if err := c.runtime.createNetNS(c); err != nil {
- // Tear down storage before exiting to make sure we
- // don't leak mounts
- if err2 := c.cleanupStorage(); err2 != nil {
- logrus.Errorf("Error cleaning up storage for container %s: %v", c.ID(), err2)
- }
- return err
+ wg.Wait()
+ if createNetNSErr != nil {
+ if mountStorageErr != nil {
+ logrus.Error(createNetNSErr)
+ return mountStorageErr
}
+ return createNetNSErr
+ }
+ if mountStorageErr != nil {
+ return mountStorageErr
}
- return nil
+ // Assign NetNS attributes to container
+ if saveNetworkStatus {
+ c.state.NetNS = netNS
+ c.state.NetworkStatus = networkStatus
+ }
+
+ // Finish up mountStorage
+ c.state.Mounted = true
+ c.state.Mountpoint = mountPoint
+ if c.state.UserNSRoot == "" {
+ c.state.RealMountpoint = c.state.Mountpoint
+ } else {
+ c.state.RealMountpoint = filepath.Join(c.state.UserNSRoot, "mountpoint")
+ }
+
+ logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint)
+ // Save the container
+ return c.save()
}
// cleanupNetwork unmounts and cleans up the container's network
@@ -104,7 +145,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path())
}
}
-
// Check if the spec file mounts contain the label Relabel flags z or Z.
// If they do, relabel the source directory and then remove the option.
for _, m := range g.Mounts() {
@@ -197,12 +237,28 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
// Look up and add groups the user belongs to, if a group wasn't directly specified
if !rootless.IsRootless() && !strings.Contains(c.config.User, ":") {
- groups, err := chrootuser.GetAdditionalGroupsForUser(c.state.Mountpoint, uint64(g.Config.Process.User.UID))
- if err != nil && errors.Cause(err) != chrootuser.ErrNoSuchUser {
+ var groupDest, passwdDest string
+ defaultExecUser := user.ExecUser{
+ Uid: 0,
+ Gid: 0,
+ Home: "/",
+ }
+
+ // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty
+ if groupDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/group"); err != nil {
+ logrus.Debug(err)
return nil, err
}
- for _, gid := range groups {
- g.AddProcessAdditionalGid(gid)
+ if passwdDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd"); err != nil {
+ logrus.Debug(err)
+ return nil, err
+ }
+ execUser, err := user.GetExecUserPath(c.config.User, &defaultExecUser, passwdDest, groupDest)
+ if err != nil {
+ return nil, err
+ }
+ for _, gid := range execUser.Sgids {
+ g.AddProcessAdditionalGid(uint32(gid))
}
}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index acb4e2a90..0d9ec2809 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -48,12 +48,12 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
}
// Create and configure a new network namespace for a container
-func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) {
+func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Result, error) {
podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP)
results, err := r.netPlugin.SetUpPod(podNetwork)
if err != nil {
- return errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID())
+ return nil, errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID())
}
defer func() {
if err != nil {
@@ -63,15 +63,14 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) {
}
}()
- ctr.state.NetNS = ctrNS
- ctr.state.NetworkStatus = make([]*cnitypes.Result, 0)
+ networkStatus := make([]*cnitypes.Result, 1)
for idx, r := range results {
logrus.Debugf("[%d] CNI result: %v", idx, r.String())
resultCurrent, err := cnitypes.GetResult(r)
if err != nil {
- return errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.String(), err)
+ return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.String(), err)
}
- ctr.state.NetworkStatus = append(ctr.state.NetworkStatus, resultCurrent)
+ networkStatus = append(ctr.state.NetworkStatus, resultCurrent)
}
// Add firewall rules to ensure the container has network access.
@@ -82,18 +81,18 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) {
PrevResult: netStatus,
}
if err := r.firewallBackend.Add(firewallConf); err != nil {
- return errors.Wrapf(err, "error adding firewall rules for container %s", ctr.ID())
+ return nil, errors.Wrapf(err, "error adding firewall rules for container %s", ctr.ID())
}
}
- return nil
+ return networkStatus, nil
}
// Create and configure a new network namespace for a container
-func (r *Runtime) createNetNS(ctr *Container) (err error) {
+func (r *Runtime) createNetNS(ctr *Container) (ns.NetNS, []*cnitypes.Result, error) {
ctrNS, err := netns.NewNS()
if err != nil {
- return errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID())
+ return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID())
}
defer func() {
if err != nil {
@@ -104,7 +103,9 @@ func (r *Runtime) createNetNS(ctr *Container) (err error) {
}()
logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID())
- return r.configureNetNS(ctr, ctrNS)
+
+ networkStatus, err := r.configureNetNS(ctr, ctrNS)
+ return ctrNS, networkStatus, err
}
// Configure the network namespace for a rootless container
@@ -173,7 +174,12 @@ func (r *Runtime) setupNetNS(ctr *Container) (err error) {
if err != nil {
return err
}
- return r.configureNetNS(ctr, netNS)
+ networkStatus, err := r.configureNetNS(ctr, netNS)
+
+ // Assign NetNS attributes to container
+ ctr.state.NetNS = netNS
+ ctr.state.NetworkStatus = networkStatus
+ return err
}
// Join an existing network namespace
diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE b/vendor/github.com/cyphar/filepath-securejoin/LICENSE
new file mode 100644
index 000000000..bec842f29
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/LICENSE
@@ -0,0 +1,28 @@
+Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
+Copyright (C) 2017 SUSE LLC. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/cyphar/filepath-securejoin/README.md b/vendor/github.com/cyphar/filepath-securejoin/README.md
new file mode 100644
index 000000000..49b2baa9f
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/README.md
@@ -0,0 +1,65 @@
+## `filepath-securejoin` ##
+
+[![Build Status](https://travis-ci.org/cyphar/filepath-securejoin.svg?branch=master)](https://travis-ci.org/cyphar/filepath-securejoin)
+
+An implementation of `SecureJoin`, a [candidate for inclusion in the Go
+standard library][go#20126]. The purpose of this function is to be a "secure"
+alternative to `filepath.Join`, and in particular it provides certain
+guarantees that are not provided by `filepath.Join`.
+
+This is the function prototype:
+
+```go
+func SecureJoin(root, unsafePath string) (string, error)
+```
+
+This library **guarantees** the following:
+
+* If no error is set, the resulting string **must** be a child path of
+ `SecureJoin` and will not contain any symlink path components (they will all
+ be expanded).
+
+* When expanding symlinks, all symlink path components **must** be resolved
+ relative to the provided root. In particular, this can be considered a
+ userspace implementation of how `chroot(2)` operates on file paths. Note that
+ these symlinks will **not** be expanded lexically (`filepath.Clean` is not
+ called on the input before processing).
+
+* Non-existant path components are unaffected by `SecureJoin` (similar to
+ `filepath.EvalSymlinks`'s semantics).
+
+* The returned path will always be `filepath.Clean`ed and thus not contain any
+ `..` components.
+
+A (trivial) implementation of this function on GNU/Linux systems could be done
+with the following (note that this requires root privileges and is far more
+opaque than the implementation in this library, and also requires that
+`readlink` is inside the `root` path):
+
+```go
+package securejoin
+
+import (
+ "os/exec"
+ "path/filepath"
+)
+
+func SecureJoin(root, unsafePath string) (string, error) {
+ unsafePath = string(filepath.Separator) + unsafePath
+ cmd := exec.Command("chroot", root,
+ "readlink", "--canonicalize-missing", "--no-newline", unsafePath)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", err
+ }
+ expanded := string(output)
+ return filepath.Join(root, expanded), nil
+}
+```
+
+[go#20126]: https://github.com/golang/go/issues/20126
+
+### License ###
+
+The license of this project is the same as Go, which is a BSD 3-clause license
+available in the `LICENSE` file.
diff --git a/vendor/github.com/cyphar/filepath-securejoin/join.go b/vendor/github.com/cyphar/filepath-securejoin/join.go
new file mode 100644
index 000000000..c4ca3d713
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/join.go
@@ -0,0 +1,134 @@
+// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
+// Copyright (C) 2017 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package securejoin is an implementation of the hopefully-soon-to-be-included
+// SecureJoin helper that is meant to be part of the "path/filepath" package.
+// The purpose of this project is to provide a PoC implementation to make the
+// SecureJoin proposal (https://github.com/golang/go/issues/20126) more
+// tangible.
+package securejoin
+
+import (
+ "bytes"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+
+ "github.com/pkg/errors"
+)
+
+// ErrSymlinkLoop is returned by SecureJoinVFS when too many symlinks have been
+// evaluated in attempting to securely join the two given paths.
+var ErrSymlinkLoop = errors.Wrap(syscall.ELOOP, "secure join")
+
+// IsNotExist tells you if err is an error that implies that either the path
+// accessed does not exist (or path components don't exist). This is
+// effectively a more broad version of os.IsNotExist.
+func IsNotExist(err error) bool {
+ // If it's a bone-fide ENOENT just bail.
+ if os.IsNotExist(errors.Cause(err)) {
+ return true
+ }
+
+ // Check that it's not actually an ENOTDIR, which in some cases is a more
+ // convoluted case of ENOENT (usually involving weird paths).
+ var errno error
+ switch err := errors.Cause(err).(type) {
+ case *os.PathError:
+ errno = err.Err
+ case *os.LinkError:
+ errno = err.Err
+ case *os.SyscallError:
+ errno = err.Err
+ }
+ return errno == syscall.ENOTDIR || errno == syscall.ENOENT
+}
+
+// SecureJoinVFS joins the two given path components (similar to Join) except
+// that the returned path is guaranteed to be scoped inside the provided root
+// path (when evaluated). Any symbolic links in the path are evaluated with the
+// given root treated as the root of the filesystem, similar to a chroot. The
+// filesystem state is evaluated through the given VFS interface (if nil, the
+// standard os.* family of functions are used).
+//
+// Note that the guarantees provided by this function only apply if the path
+// components in the returned string are not modified (in other words are not
+// replaced with symlinks on the filesystem) after this function has returned.
+// Such a symlink race is necessarily out-of-scope of SecureJoin.
+func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
+ // Use the os.* VFS implementation if none was specified.
+ if vfs == nil {
+ vfs = osVFS{}
+ }
+
+ var path bytes.Buffer
+ n := 0
+ for unsafePath != "" {
+ if n > 255 {
+ return "", ErrSymlinkLoop
+ }
+
+ // Next path component, p.
+ i := strings.IndexRune(unsafePath, filepath.Separator)
+ var p string
+ if i == -1 {
+ p, unsafePath = unsafePath, ""
+ } else {
+ p, unsafePath = unsafePath[:i], unsafePath[i+1:]
+ }
+
+ // Create a cleaned path, using the lexical semantics of /../a, to
+ // create a "scoped" path component which can safely be joined to fullP
+ // for evaluation. At this point, path.String() doesn't contain any
+ // symlink components.
+ cleanP := filepath.Clean(string(filepath.Separator) + path.String() + p)
+ if cleanP == string(filepath.Separator) {
+ path.Reset()
+ continue
+ }
+ fullP := filepath.Clean(root + cleanP)
+
+ // Figure out whether the path is a symlink.
+ fi, err := vfs.Lstat(fullP)
+ if err != nil && !IsNotExist(err) {
+ return "", err
+ }
+ // Treat non-existent path components the same as non-symlinks (we
+ // can't do any better here).
+ if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 {
+ path.WriteString(p)
+ path.WriteRune(filepath.Separator)
+ continue
+ }
+
+ // Only increment when we actually dereference a link.
+ n++
+
+ // It's a symlink, expand it by prepending it to the yet-unparsed path.
+ dest, err := vfs.Readlink(fullP)
+ if err != nil {
+ return "", err
+ }
+ // Absolute symlinks reset any work we've already done.
+ if filepath.IsAbs(dest) {
+ path.Reset()
+ }
+ unsafePath = dest + string(filepath.Separator) + unsafePath
+ }
+
+ // We have to clean path.String() here because it may contain '..'
+ // components that are entirely lexical, but would be misleading otherwise.
+ // And finally do a final clean to ensure that root is also lexically
+ // clean.
+ fullP := filepath.Clean(string(filepath.Separator) + path.String())
+ return filepath.Clean(root + fullP), nil
+}
+
+// SecureJoin is a wrapper around SecureJoinVFS that just uses the os.* library
+// of functions as the VFS. If in doubt, use this function over SecureJoinVFS.
+func SecureJoin(root, unsafePath string) (string, error) {
+ return SecureJoinVFS(root, unsafePath, nil)
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/vendor.conf b/vendor/github.com/cyphar/filepath-securejoin/vendor.conf
new file mode 100644
index 000000000..66bb574b9
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/vendor.conf
@@ -0,0 +1 @@
+github.com/pkg/errors v0.8.0
diff --git a/vendor/github.com/cyphar/filepath-securejoin/vfs.go b/vendor/github.com/cyphar/filepath-securejoin/vfs.go
new file mode 100644
index 000000000..a82a5eae1
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/vfs.go
@@ -0,0 +1,41 @@
+// Copyright (C) 2017 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package securejoin
+
+import "os"
+
+// In future this should be moved into a separate package, because now there
+// are several projects (umoci and go-mtree) that are using this sort of
+// interface.
+
+// VFS is the minimal interface necessary to use SecureJoinVFS. A nil VFS is
+// equivalent to using the standard os.* family of functions. This is mainly
+// used for the purposes of mock testing, but also can be used to otherwise use
+// SecureJoin with VFS-like system.
+type VFS interface {
+ // Lstat returns a FileInfo describing the named file. If the file is a
+ // symbolic link, the returned FileInfo describes the symbolic link. Lstat
+ // makes no attempt to follow the link. These semantics are identical to
+ // os.Lstat.
+ Lstat(name string) (os.FileInfo, error)
+
+ // Readlink returns the destination of the named symbolic link. These
+ // semantics are identical to os.Readlink.
+ Readlink(name string) (string, error)
+}
+
+// osVFS is the "nil" VFS, in that it just passes everything through to the os
+// module.
+type osVFS struct{}
+
+// Lstat returns a FileInfo describing the named file. If the file is a
+// symbolic link, the returned FileInfo describes the symbolic link. Lstat
+// makes no attempt to follow the link. These semantics are identical to
+// os.Lstat.
+func (o osVFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
+
+// Readlink returns the destination of the named symbolic link. These
+// semantics are identical to os.Readlink.
+func (o osVFS) Readlink(name string) (string, error) { return os.Readlink(name) }