From 8ba1e4df239e3f40f7a67fdde762a241e22f05f3 Mon Sep 17 00:00:00 2001 From: acornies Date: Fri, 3 Feb 2023 11:06:22 -0500 Subject: [PATCH 1/5] NOMAD-236 --userns podman configuration option: - add userns to task config - add parsing for userns config option - add the driver configuration, uncomment api portion - tests --- api/structs.go | 2 +- config.go | 2 ++ driver.go | 38 ++++++++++++++++++++++++++++++++++++++ driver_test.go | 18 ++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/api/structs.go b/api/structs.go index c32a855b..b83a07f3 100644 --- a/api/structs.go +++ b/api/structs.go @@ -294,7 +294,7 @@ type ContainerSecurityConfig struct { // created. // If set to private, IDMappings must be set. // Mandatory. - // UserNS Namespace `json:"userns,omitempty"` + UserNS Namespace `json:"userns,omitempty"` // IDMappings are UID and GID mappings that will be used by user // namespaces. diff --git a/config.go b/config.go index a6f1d277..f19a4d5f 100644 --- a/config.go +++ b/config.go @@ -86,6 +86,7 @@ var ( "volumes": hclspec.NewAttr("volumes", "list(string)", false), "force_pull": hclspec.NewAttr("force_pull", "bool", false), "readonly_rootfs": hclspec.NewAttr("readonly_rootfs", "bool", false), + "userns": hclspec.NewAttr("userns", "string", false), }) ) @@ -158,4 +159,5 @@ type TaskConfig struct { ForcePull bool `codec:"force_pull"` Privileged bool `codec:"privileged"` ReadOnlyRootfs bool `codec:"readonly_rootfs"` + UserNS string `codec:"userns"` } diff --git a/driver.go b/driver.go index a7f964cb..860b6de7 100644 --- a/driver.go +++ b/driver.go @@ -496,6 +496,21 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive createOpts.ContainerSecurityConfig.ReadOnlyFilesystem = driverConfig.ReadOnlyRootfs createOpts.ContainerSecurityConfig.ApparmorProfile = driverConfig.ApparmorProfile + // Populate --userns mode only if configured + if len(driverConfig.UserNS) > 0 { + userns := strings.Split(driverConfig.UserNS, ":") + mode, err := parseNamespaceMode(userns[0]) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse userns configuration: %v", err) + } + // Populate value only if specified + if len(userns) > 1 { + createOpts.ContainerSecurityConfig.UserNS = api.Namespace{NSMode: mode, Value: userns[1]} + } else { + createOpts.ContainerSecurityConfig.UserNS = api.Namespace{NSMode: mode} + } + } + // Network config options if cfg.DNS != nil { for _, strdns := range cfg.DNS.Servers { @@ -667,6 +682,29 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive return handle, driverNet, nil } +func parseNamespaceMode(s string) (c api.NamespaceMode, err error) { + mode := map[api.NamespaceMode]struct{}{ + api.Host: {}, + api.Path: {}, + api.Auto: {}, + api.FromContainer: {}, + api.FromPod: {}, + api.Private: {}, + api.NoNetwork: {}, + api.Bridge: {}, + api.Slirp: {}, + api.KeepID: {}, + api.DefaultKernelNamespaces: {}, + } + + m := api.NamespaceMode(s) + _, ok := mode[m] + if !ok { + return c, fmt.Errorf(`cannot parse:[%s] as userns (namespace mode)`, s) + } + return m, nil +} + func memoryLimits(r drivers.MemoryResources, reservation string) (hard, soft *int64, err error) { memoryMax := r.MemoryMaxMB * 1024 * 1024 memory := r.MemoryMB * 1024 * 1024 diff --git a/driver_test.go b/driver_test.go index a3e7b8ea..b8e96033 100644 --- a/driver_test.go +++ b/driver_test.go @@ -1485,6 +1485,24 @@ func TestPodmanDriver_ReadOnlyFilesystem(t *testing.T) { require.True(t, inspectData.HostConfig.ReadonlyRootfs) } +// check userns mode configuration (single value) +func TestPodmanDriver_UsernsMode(t *testing.T) { + taskCfg := newTaskConfig("", busyboxLongRunningCmd) + taskCfg.UserNS = "host" + inspectData := startDestroyInspect(t, taskCfg, "userns") + + require.Equal(t, "host", inspectData.HostConfig.UsernsMode) +} + +// check userns mode configuration (parsed value) +func TestPodmanDriver_UsernsModeParsed(t *testing.T) { + taskCfg := newTaskConfig("", busyboxLongRunningCmd) + taskCfg.UserNS = "keep-id:uid=200,gid=210" + inspectData := startDestroyInspect(t, taskCfg, "userns") + + require.Equal(t, "keep-id:uid=200,gid=210", inspectData.HostConfig.UsernsMode) +} + // check dns server configuration func TestPodmanDriver_Dns(t *testing.T) { if !tu.IsCI() { From 20cacb3f616e763d49caad20c5a24bc8fb352877 Mon Sep 17 00:00:00 2001 From: acornies Date: Tue, 7 Feb 2023 12:49:47 -0500 Subject: [PATCH 2/5] Add README documentation changes --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index b1107371..1d3a4c21 100644 --- a/README.md +++ b/README.md @@ -462,6 +462,13 @@ config { } ``` +* **userns** - (Optional) Set the [user namespace mode](https://docs.podman.io/en/latest/markdown/podman-run.1.html#userns-mode) for the container. +```hcl +config { + userns = "keep-id:uid=200,gid=210" +} +``` + * **pids_limit** - (Optional) An integer value that specifies the pid limit for the container. ```hcl config { From cff634bff295e9d78b1a17f1fbc31ad57271b8c6 Mon Sep 17 00:00:00 2001 From: Andrew Cornies <2882297+acornies@users.noreply.github.com> Date: Tue, 14 Mar 2023 14:44:10 -0400 Subject: [PATCH 3/5] Update driver.go Used suggested changes from Luis - clearer intention Co-authored-by: Luiz Aoqui --- driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver.go b/driver.go index 860b6de7..00529c90 100644 --- a/driver.go +++ b/driver.go @@ -497,7 +497,7 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive createOpts.ContainerSecurityConfig.ApparmorProfile = driverConfig.ApparmorProfile // Populate --userns mode only if configured - if len(driverConfig.UserNS) > 0 { + if driverConfig.UserNS != "" { userns := strings.Split(driverConfig.UserNS, ":") mode, err := parseNamespaceMode(userns[0]) if err != nil { From 7395015fac7e6b26a72e2ffab9dbeeab62556afd Mon Sep 17 00:00:00 2001 From: acornies Date: Tue, 14 Mar 2023 15:12:51 -0400 Subject: [PATCH 4/5] Clean up userns key value, mode validation: - remove redundant mapping code to map NamespaceMode - Switch to SplitN usage to limit split operation --- driver.go | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/driver.go b/driver.go index 00529c90..c974c3ba 100644 --- a/driver.go +++ b/driver.go @@ -498,11 +498,8 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive // Populate --userns mode only if configured if driverConfig.UserNS != "" { - userns := strings.Split(driverConfig.UserNS, ":") - mode, err := parseNamespaceMode(userns[0]) - if err != nil { - return nil, nil, fmt.Errorf("failed to parse userns configuration: %v", err) - } + userns := strings.SplitN(driverConfig.UserNS, ":", 2) + mode := api.NamespaceMode(userns[0]) // Populate value only if specified if len(userns) > 1 { createOpts.ContainerSecurityConfig.UserNS = api.Namespace{NSMode: mode, Value: userns[1]} @@ -682,29 +679,6 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive return handle, driverNet, nil } -func parseNamespaceMode(s string) (c api.NamespaceMode, err error) { - mode := map[api.NamespaceMode]struct{}{ - api.Host: {}, - api.Path: {}, - api.Auto: {}, - api.FromContainer: {}, - api.FromPod: {}, - api.Private: {}, - api.NoNetwork: {}, - api.Bridge: {}, - api.Slirp: {}, - api.KeepID: {}, - api.DefaultKernelNamespaces: {}, - } - - m := api.NamespaceMode(s) - _, ok := mode[m] - if !ok { - return c, fmt.Errorf(`cannot parse:[%s] as userns (namespace mode)`, s) - } - return m, nil -} - func memoryLimits(r drivers.MemoryResources, reservation string) (hard, soft *int64, err error) { memoryMax := r.MemoryMaxMB * 1024 * 1024 memory := r.MemoryMB * 1024 * 1024 From d942fc0b6ec38f38bf2e1575ed3aa9c7f96a89ae Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Mon, 20 Mar 2023 17:48:20 -0400 Subject: [PATCH 5/5] fix CI and add changelog entry --- CHANGELOG.md | 2 +- api/structs.go | 12 ++++++------ driver_test.go | 6 ++++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ad87ee..a32eeb03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,9 @@ IMPROVEMENTS: * config: Allow setting `pids_limit` option. [[GH-203](https://github.com/hashicorp/nomad-driver-podman/pull/203)] +* config: Allow setting `userns` option. [[GH-212](https://github.com/hashicorp/nomad-driver-podman/pull/212)] * runtime: Set mount propagation from TaskConfig [[GH-204](https://github.com/hashicorp/nomad-driver-podman/pull/204)] - ## 0.4.1 (November 15, 2022) FEATURES: diff --git a/api/structs.go b/api/structs.go index b83a07f3..a4597383 100644 --- a/api/structs.go +++ b/api/structs.go @@ -243,6 +243,12 @@ type ContainerSecurityConfig struct { // If unset, the container will be run as root. // Optional. User string `json:"user,omitempty"` + // UserNS is the container's user namespace. + // It defaults to host, indicating that no user namespace will be + // created. + // If set to private, IDMappings must be set. + // Mandatory. + UserNS Namespace `json:"userns,omitempty"` // Groups are a list of supplemental groups the container's user will // be granted access to. // Optional. @@ -289,12 +295,6 @@ type ContainerSecurityConfig struct { // privileges flag on create, which disables gaining additional // privileges (e.g. via setuid) in the container. NoNewPrivileges bool `json:"no_new_privileges,omitempty"` - // UserNS is the container's user namespace. - // It defaults to host, indicating that no user namespace will be - // created. - // If set to private, IDMappings must be set. - // Mandatory. - UserNS Namespace `json:"userns,omitempty"` // IDMappings are UID and GID mappings that will be used by user // namespaces. diff --git a/driver_test.go b/driver_test.go index b8e96033..dea7268a 100644 --- a/driver_test.go +++ b/driver_test.go @@ -1487,6 +1487,9 @@ func TestPodmanDriver_ReadOnlyFilesystem(t *testing.T) { // check userns mode configuration (single value) func TestPodmanDriver_UsernsMode(t *testing.T) { + // TODO: run test once CI can use rootless Podman. + t.Skip("Test suite requires rootful Podman") + taskCfg := newTaskConfig("", busyboxLongRunningCmd) taskCfg.UserNS = "host" inspectData := startDestroyInspect(t, taskCfg, "userns") @@ -1496,6 +1499,9 @@ func TestPodmanDriver_UsernsMode(t *testing.T) { // check userns mode configuration (parsed value) func TestPodmanDriver_UsernsModeParsed(t *testing.T) { + // TODO: run test once CI can use rootless Podman. + t.Skip("Test suite requires rootful Podman") + taskCfg := newTaskConfig("", busyboxLongRunningCmd) taskCfg.UserNS = "keep-id:uid=200,gid=210" inspectData := startDestroyInspect(t, taskCfg, "userns")