diff --git a/go.mod b/go.mod index c9044cb859..f8ecf39717 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/fsnotify/fsnotify v1.5.1 github.com/ghodss/yaml v1.0.0 github.com/godbus/dbus/v5 v5.0.6 + github.com/google/go-cmp v0.5.7 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index afa351c173..5222513d07 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -373,6 +373,14 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, err } + if err := c.makeRunHostMount(g); err != nil { + return nil, err + } + + if err := c.injectContainerUUID(g); err != nil { + return nil, err + } + // Get host UID and GID based on the container process UID and GID. hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), uint32(execUser.Uid), uint32(execUser.Gid)) if err != nil { @@ -513,6 +521,9 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { if dstPath == "/dev/shm" && c.state.BindMounts["/dev/shm"] == c.config.ShmDir { newMount.Options = append(newMount.Options, "nosuid", "noexec", "nodev") } + if dstPath == "/run/host" { + newMount.Options = append(newMount.Options, "ro", "nosuid", "noexec", "nodev") + } if !MountExists(g.Mounts(), dstPath) { g.AddMount(newMount) } else { @@ -909,6 +920,72 @@ func (c *Container) mountNotifySocket(g generate.Generator) error { return nil } +func (c *Container) runHostDir() string { + return filepath.Join(c.state.RunDir, "run-host") +} + +// makeRunHostMount creates the run-host bind mount for the container +// https://systemd.io/CONTAINER_INTERFACE/#the-runhost-hierarchy +func (c *Container) makeRunHostMount(g generate.Generator) error { + src := c.runHostDir() + if _, err := os.Stat(src); err == nil || !os.IsNotExist(err) { + return err + } + oldUmask := umask.Set(0) + defer umask.Set(oldUmask) + + if err := os.MkdirAll(src, 0755); err != nil { + return err + } + if err := label.Relabel(src, c.config.MountLabel, false); err != nil { + return err + } + if err := os.Chown(src, c.RootUID(), c.RootGID()); err != nil { + return err + } + c.state.BindMounts["/run/host"] = src + + src = filepath.Join(src, "container-manager") + if err := os.Remove(src); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error removing %s for container %s", src, c.ID()) + } + if err := writeStringToPath(src, "podman\n", c.config.MountLabel, c.RootUID(), c.RootGID()); err != nil { + return errors.Wrapf(err, "error writing %s for container %s", src, c.ID()) + } + if err := os.Chmod(src, 0444); err != nil { + return err + } + + return nil +} + +func (c *Container) injectContainerUUID(g generate.Generator) error { + uuid := c.ID()[:32] + addEnv := true + for _, checkEnv := range g.Config.Process.Env { + if strings.SplitN(checkEnv, "=", 2)[0] == "container_uuid" { + addEnv = false + break + } + } + if addEnv { + g.AddProcessEnv("container_uuid", uuid) + } + + src := filepath.Join(c.runHostDir(), "container-uuid") + if err := os.Remove(src); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error removing %s for container %s", src, c.ID()) + } + if err := writeStringToPath(src, uuid+"\n", c.config.MountLabel, c.RootUID(), c.RootGID()); err != nil { + return errors.Wrapf(err, "error writing %s for container %s", src, c.ID()) + } + if err := os.Chmod(src, 0444); err != nil { + return err + } + + return nil +} + // systemd expects to have /run, /run/lock and /tmp on tmpfs // It also expects to be able to write to /sys/fs/cgroup/systemd and /var/log/journal func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) error { diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go index a7dd0fc319..f0e008d944 100644 --- a/libpod/container_internal_linux_test.go +++ b/libpod/container_internal_linux_test.go @@ -4,11 +4,18 @@ package libpod import ( + "context" "io/ioutil" "os" + "path/filepath" "testing" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/pkg/namespaces" + "github.com/containers/storage" + "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/stringid" + "github.com/google/go-cmp/cmp" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/stretchr/testify/assert" ) @@ -97,3 +104,96 @@ func TestAppendLocalhost(t *testing.T) { assert.Equal(t, "127.0.0.1\tlocalhost", c.appendLocalhost("127.0.0.1\tlocalhost")) } } + +func TestContainerUUIDEnv(t *testing.T) { + ctx := context.Background() + c := makeTestContainer(t) + + s, err := c.generateSpec(ctx) + if err != nil { + t.Fatal(err) + } + assert.True(t, hasProcessEnv(t, s, "container_uuid="+c.config.ID[:32])) + + c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, "container_uuid=test") + s, err = c.generateSpec(ctx) + if err != nil { + t.Fatal(err) + } + assert.True(t, hasProcessEnv(t, s, "container_uuid=test")) +} + +func TestContainerRunHost(t *testing.T) { + ctx := context.Background() + c := makeTestContainer(t) + + s, err := c.generateSpec(ctx) + if err != nil { + t.Fatal(err) + } + assert.True(t, hasMount(t, s, spec.Mount{ + Destination: "/run/host", + Source: c.runHostDir(), + Type: "bind", + Options: []string{"bind", "rprivate", "ro", "nosuid", "noexec", "nodev"}}, + ), "run-host mount not found: %+v", s.Mounts) + b, err := os.ReadFile(filepath.Join(c.runHostDir(), "container-manager")) + if err != nil { + t.Fatalf("ReadFile failed: %v", err) + } + assert.Equal(t, string(b), "podman\n") + b, err = os.ReadFile(filepath.Join(c.runHostDir(), "container-uuid")) + if err != nil { + t.Fatalf("ReadFile failed: %v", err) + } + assert.Equal(t, string(b), c.config.ID[:32]+"\n") +} + +func makeTestContainer(t *testing.T) Container { + t.Helper() + return Container{ + config: &ContainerConfig{ + Spec: &spec.Spec{ + Process: &spec.Process{}, + Root: &spec.Root{}, + Linux: &spec.Linux{ + Namespaces: []spec.LinuxNamespace{{Type: spec.NetworkNamespace, Path: ""}}, + }, + }, + ID: stringid.GenerateNonCryptoID(), + IDMappings: storage.IDMappingOptions{ + UIDMap: []idtools.IDMap{{ContainerID: 0, HostID: os.Geteuid(), Size: 1}}, + GIDMap: []idtools.IDMap{{ContainerID: 0, HostID: os.Getegid(), Size: 1}}, + }, + ContainerNetworkConfig: ContainerNetworkConfig{UseImageHosts: true}, + ContainerMiscConfig: ContainerMiscConfig{CgroupManager: config.SystemdCgroupsManager}, + }, + state: &ContainerState{ + BindMounts: map[string]string{"/run/.containerenv": ""}, + RunDir: t.TempDir(), + }, + runtime: &Runtime{ + config: &config.Config{Containers: config.ContainersConfig{}}, + }, + } +} + +func hasProcessEnv(t *testing.T, s *spec.Spec, want string) bool { + t.Helper() + for _, checkEnv := range s.Process.Env { + if checkEnv == want { + return true + } + } + return false +} + +func hasMount(t *testing.T, s *spec.Spec, want spec.Mount) bool { + t.Helper() + for _, m := range s.Mounts { + if cmp.Equal(want, m) { + return true + } + } + return false +} diff --git a/libpod/diff.go b/libpod/diff.go index 794b26b482..c0143736df 100644 --- a/libpod/diff.go +++ b/libpod/diff.go @@ -14,6 +14,7 @@ var initInodes = map[string]bool{ "/etc/resolv.conf": true, "/proc": true, "/run": true, + "/run/host": true, "/run/notify": true, "/run/.containerenv": true, "/run/secrets": true, diff --git a/vendor/modules.txt b/vendor/modules.txt index 0b125179f1..590925f66a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -413,6 +413,7 @@ github.com/golang/protobuf/ptypes/any github.com/golang/protobuf/ptypes/duration github.com/golang/protobuf/ptypes/timestamp # github.com/google/go-cmp v0.5.7 +## explicit github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags