mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Merge pull request #41893 from AkihiroSuda/fix-41457
rootless: support --pid=host
This commit is contained in:
		
						commit
						c8ff7305f6
					
				
					 5 changed files with 120 additions and 35 deletions
				
			
		| 
						 | 
				
			
			@ -2425,28 +2425,6 @@ func (s *DockerSuite) TestContainerNetworkMode(c *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestRunModePIDHost(c *testing.T) {
 | 
			
		||||
	// Not applicable on Windows as uses Unix-specific capabilities
 | 
			
		||||
	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux, NotUserNamespace)
 | 
			
		||||
 | 
			
		||||
	hostPid, err := os.Readlink("/proc/1/ns/pid")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "run", "--pid=host", "busybox", "readlink", "/proc/self/ns/pid")
 | 
			
		||||
	out = strings.Trim(out, "\n")
 | 
			
		||||
	if hostPid != out {
 | 
			
		||||
		c.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "run", "busybox", "readlink", "/proc/self/ns/pid")
 | 
			
		||||
	out = strings.Trim(out, "\n")
 | 
			
		||||
	if hostPid == out {
 | 
			
		||||
		c.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestRunModeUTSHost(c *testing.T) {
 | 
			
		||||
	// Not applicable on Windows as uses Unix-specific capabilities
 | 
			
		||||
	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										37
									
								
								integration/container/pidmode_linux_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								integration/container/pidmode_linux_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
package container // import "github.com/docker/docker/integration/container"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/integration/internal/container"
 | 
			
		||||
	"gotest.tools/v3/assert"
 | 
			
		||||
	"gotest.tools/v3/poll"
 | 
			
		||||
	"gotest.tools/v3/skip"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPidHost(t *testing.T) {
 | 
			
		||||
	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
 | 
			
		||||
	skip.If(t, testEnv.IsRemoteDaemon())
 | 
			
		||||
 | 
			
		||||
	hostPid, err := os.Readlink("/proc/1/ns/pid")
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	defer setupTest(t)()
 | 
			
		||||
	client := testEnv.APIClient()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	cID := container.Run(ctx, t, client, func(c *container.TestContainerConfig) {
 | 
			
		||||
		c.HostConfig.PidMode = "host"
 | 
			
		||||
	})
 | 
			
		||||
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
 | 
			
		||||
	cPid := container.GetContainerNS(ctx, t, client, cID, "pid")
 | 
			
		||||
	assert.Assert(t, hostPid == cPid)
 | 
			
		||||
 | 
			
		||||
	cID = container.Run(ctx, t, client)
 | 
			
		||||
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
 | 
			
		||||
	cPid = container.GetContainerNS(ctx, t, client, cID, "pid")
 | 
			
		||||
	assert.Assert(t, hostPid != cPid)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ package container // import "github.com/docker/docker/integration/container"
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11,20 +10,10 @@ import (
 | 
			
		|||
	"github.com/docker/docker/integration/internal/requirement"
 | 
			
		||||
	"github.com/docker/docker/testutil/daemon"
 | 
			
		||||
	"gotest.tools/v3/assert"
 | 
			
		||||
	is "gotest.tools/v3/assert/cmp"
 | 
			
		||||
	"gotest.tools/v3/poll"
 | 
			
		||||
	"gotest.tools/v3/skip"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Gets the value of the cgroup namespace for pid 1 of a container
 | 
			
		||||
func containerCgroupNamespace(ctx context.Context, t *testing.T, client *client.Client, cID string) string {
 | 
			
		||||
	res, err := container.Exec(ctx, client, cID, []string{"readlink", "/proc/1/ns/cgroup"})
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(res.Stderr(), 0))
 | 
			
		||||
	assert.Equal(t, 0, res.ExitCode)
 | 
			
		||||
	return strings.TrimSpace(res.Stdout())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Bring up a daemon with the specified default cgroup namespace mode, and then create a container with the container options
 | 
			
		||||
func testRunWithCgroupNs(t *testing.T, daemonNsMode string, containerOpts ...func(*container.TestContainerConfig)) (string, string) {
 | 
			
		||||
	d := daemon.New(t, daemon.WithDefaultCgroupNamespaceMode(daemonNsMode))
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +27,7 @@ func testRunWithCgroupNs(t *testing.T, daemonNsMode string, containerOpts ...fun
 | 
			
		|||
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
 | 
			
		||||
 | 
			
		||||
	daemonCgroup := d.CgroupNamespace(t)
 | 
			
		||||
	containerCgroup := containerCgroupNamespace(ctx, t, client, cID)
 | 
			
		||||
	containerCgroup := container.GetContainerNS(ctx, t, client, cID, "cgroup")
 | 
			
		||||
	return containerCgroup, daemonCgroup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +136,7 @@ func TestCgroupNamespacesRunOlderClient(t *testing.T) {
 | 
			
		|||
	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
 | 
			
		||||
 | 
			
		||||
	daemonCgroup := d.CgroupNamespace(t)
 | 
			
		||||
	containerCgroup := containerCgroupNamespace(ctx, t, client, cID)
 | 
			
		||||
	containerCgroup := container.GetContainerNS(ctx, t, client, cID, "cgroup")
 | 
			
		||||
	if testEnv.DaemonInfo.CgroupVersion != "2" {
 | 
			
		||||
		assert.Assert(t, daemonCgroup == containerCgroup)
 | 
			
		||||
	} else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										21
									
								
								integration/internal/container/ns.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								integration/internal/container/ns.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
package container
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/client"
 | 
			
		||||
	"gotest.tools/v3/assert"
 | 
			
		||||
	is "gotest.tools/v3/assert/cmp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetContainerNS gets the value of the specified namespace of a container
 | 
			
		||||
func GetContainerNS(ctx context.Context, t *testing.T, client client.APIClient, cID, nsName string) string {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	res, err := Exec(ctx, client, cID, []string{"readlink", "/proc/self/ns/" + nsName})
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(res.Stderr(), 0))
 | 
			
		||||
	assert.Equal(t, 0, res.ExitCode)
 | 
			
		||||
	return strings.TrimSpace(res.Stdout())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,8 @@ package specconv // import "github.com/docker/docker/rootless/specconv"
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +14,7 @@ import (
 | 
			
		|||
// ToRootless converts spec to be compatible with "rootless" runc.
 | 
			
		||||
// * Remove non-supported cgroups
 | 
			
		||||
// * Fix up OOMScoreAdj
 | 
			
		||||
// * Fix up /proc if --pid=host
 | 
			
		||||
//
 | 
			
		||||
// v2Controllers should be non-nil only if running with v2 and systemd.
 | 
			
		||||
func ToRootless(spec *specs.Spec, v2Controllers []string) error {
 | 
			
		||||
| 
						 | 
				
			
			@ -75,5 +78,62 @@ func toRootless(spec *specs.Spec, v2Controllers []string, currentOOMScoreAdj int
 | 
			
		|||
	if spec.Process.OOMScoreAdj != nil && *spec.Process.OOMScoreAdj < currentOOMScoreAdj {
 | 
			
		||||
		*spec.Process.OOMScoreAdj = currentOOMScoreAdj
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fix up /proc if --pid=host
 | 
			
		||||
	pidHost, err := isPidHost(spec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !pidHost {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return bindMountHostProcfs(spec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isPidHost(spec *specs.Spec) (bool, error) {
 | 
			
		||||
	for _, ns := range spec.Linux.Namespaces {
 | 
			
		||||
		if ns.Type == specs.PIDNamespace {
 | 
			
		||||
			if ns.Path == "" {
 | 
			
		||||
				return false, nil
 | 
			
		||||
			}
 | 
			
		||||
			pidNS, err := os.Readlink(ns.Path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false, err
 | 
			
		||||
			}
 | 
			
		||||
			selfPidNS, err := os.Readlink("/proc/self/ns/pid")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false, err
 | 
			
		||||
			}
 | 
			
		||||
			return pidNS == selfPidNS, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func bindMountHostProcfs(spec *specs.Spec) error {
 | 
			
		||||
	// Replace procfs mount with rbind
 | 
			
		||||
	// https://github.com/containers/podman/blob/v3.0.0-rc1/pkg/specgen/generate/oci.go#L248-L257
 | 
			
		||||
	for i, m := range spec.Mounts {
 | 
			
		||||
		if path.Clean(m.Destination) == "/proc" {
 | 
			
		||||
			newM := specs.Mount{
 | 
			
		||||
				Destination: "/proc",
 | 
			
		||||
				Type:        "bind",
 | 
			
		||||
				Source:      "/proc",
 | 
			
		||||
				Options:     []string{"rbind", "nosuid", "noexec", "nodev"},
 | 
			
		||||
			}
 | 
			
		||||
			spec.Mounts[i] = newM
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Remove ReadonlyPaths for /proc/*
 | 
			
		||||
	newROP := spec.Linux.ReadonlyPaths[:0]
 | 
			
		||||
	for _, s := range spec.Linux.ReadonlyPaths {
 | 
			
		||||
		s = path.Clean(s)
 | 
			
		||||
		if !strings.HasPrefix(s, "/proc/") {
 | 
			
		||||
			newROP = append(newROP, s)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	spec.Linux.ReadonlyPaths = newROP
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue