-
Notifications
You must be signed in to change notification settings - Fork 717
feature: support selinux #4639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feature: support selinux #4639
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,9 +26,15 @@ import ( | |
|
|
||
| "gotest.tools/v3/assert" | ||
|
|
||
| "github.com/containerd/nerdctl/mod/tigron/expect" | ||
| "github.com/containerd/nerdctl/mod/tigron/require" | ||
| "github.com/containerd/nerdctl/mod/tigron/test" | ||
| "github.com/containerd/nerdctl/mod/tigron/tig" | ||
|
|
||
| "github.com/containerd/nerdctl/v2/pkg/apparmorutil" | ||
| "github.com/containerd/nerdctl/v2/pkg/rootlessutil" | ||
| "github.com/containerd/nerdctl/v2/pkg/testutil" | ||
| "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" | ||
| ) | ||
|
|
||
| func getCapEff(base *testutil.Base, args ...string) uint64 { | ||
|
|
@@ -186,6 +192,102 @@ func TestRunApparmor(t *testing.T) { | |
| base.Cmd("run", "--rm", "--privileged", testutil.AlpineImage, "cat", attrCurrentPath).AssertOutContains("unconfined") | ||
| } | ||
|
|
||
| func TestRunSelinuxWithSecurityOpt(t *testing.T) { | ||
| testCase := nerdtest.Setup() | ||
| testCase.Require = require.Not(nerdtest.NoSelinux) | ||
| testContainer := testutil.Identifier(t) | ||
|
|
||
| testCase.SubTests = []*test.Case{ | ||
| { | ||
| Description: "test run with selinux-enabled", | ||
| Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { | ||
| return helpers.Command("--selinux-enabled", "run", "-d", "--security-opt", "label=type:container_t", "--name", testContainer, "sleep", "infinity") | ||
| }, | ||
| Cleanup: func(data test.Data, helpers test.Helpers) { | ||
| helpers.Anyhow("rm", "-f", testContainer) | ||
| }, | ||
| Expected: func(data test.Data, helpers test.Helpers) *test.Expected { | ||
| return &test.Expected{ | ||
| ExitCode: 0, | ||
| Output: expect.All( | ||
| func(stdout string, t tig.T) { | ||
| inspectOut := helpers.Capture("container", "inspect", "--format", "{{.State.Pid}}", testContainer) | ||
| pid := strings.TrimSpace(inspectOut) | ||
| fileName := fmt.Sprintf("/proc/%s/attr/current", pid) | ||
| data, err := os.ReadFile(fileName) | ||
| assert.NilError(t, err) | ||
| assert.Equal(t, strings.Contains(string(data), "container_t"), true) | ||
| }, | ||
| ), | ||
| } | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
| func TestRunSelinux(t *testing.T) { | ||
ningmingxiao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| testCase := nerdtest.Setup() | ||
| testCase.Require = require.Not(nerdtest.NoSelinux) | ||
| testContainer := testutil.Identifier(t) | ||
|
|
||
| testCase.SubTests = []*test.Case{ | ||
| { | ||
| Description: "test run with selinux-enabled", | ||
| Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { | ||
| return helpers.Command("--selinux-enabled", "run", "-d", "--name", testContainer, "sleep", "infinity") | ||
| }, | ||
| Cleanup: func(data test.Data, helpers test.Helpers) { | ||
| helpers.Anyhow("rm", "-f", testContainer) | ||
| }, | ||
| Expected: func(data test.Data, helpers test.Helpers) *test.Expected { | ||
| return &test.Expected{ | ||
| ExitCode: 0, | ||
| Output: expect.All( | ||
| func(stdout string, t tig.T) { | ||
| inspectOut := helpers.Capture("container", "inspect", "--format", "{{.State.Pid}}", testContainer) | ||
| pid := strings.TrimSpace(inspectOut) | ||
| fileName := fmt.Sprintf("/proc/%s/attr/current", pid) | ||
| data, err := os.ReadFile(fileName) | ||
| assert.NilError(t, err) | ||
| assert.Equal(t, strings.Contains(string(data), "container_t"), true) | ||
| }, | ||
| ), | ||
| } | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func TestRunSelinuxWithVolumeLabel(t *testing.T) { | ||
| testCase := nerdtest.Setup() | ||
| testCase.Require = require.Not(nerdtest.NoSelinux) | ||
| testContainer := testutil.Identifier(t) | ||
|
|
||
| testCase.SubTests = []*test.Case{ | ||
| { | ||
| Description: "test run with selinux-enabled", | ||
| Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { | ||
| return helpers.Command("--selinux-enabled", "run", "-d", "-v", fmt.Sprintf("/%s:/%s:Z", testContainer, testContainer), "--name", testContainer, "sleep", "infinity") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lower z should be tested too
This should be verified by launching multiple containers |
||
| }, | ||
| Cleanup: func(data test.Data, helpers test.Helpers) { | ||
| helpers.Anyhow("rm", "-f", testContainer) | ||
| }, | ||
| Expected: func(data test.Data, helpers test.Helpers) *test.Expected { | ||
| return &test.Expected{ | ||
| ExitCode: 0, | ||
| Output: expect.All( | ||
| func(stdout string, t tig.T) { | ||
| cmd := exec.Command("ls", "-Z", fmt.Sprintf("/%s", testContainer)) | ||
| lsStdout, err := cmd.CombinedOutput() | ||
| assert.NilError(t, err) | ||
| assert.Equal(t, strings.Contains(string(lsStdout), "container_t"), true) | ||
| }, | ||
| ), | ||
| } | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // TestRunSeccompCapSysPtrace tests https://github.com/containerd/nerdctl/issues/976 | ||
| func TestRunSeccompCapSysPtrace(t *testing.T) { | ||
| base := testutil.NewBase(t) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -254,6 +254,7 @@ Security flags: | |
|
|
||
| - :whale: `--security-opt seccomp=<PROFILE_JSON_FILE>`: specify custom seccomp profile | ||
| - :whale: `--security-opt apparmor=<PROFILE>`: specify custom AppArmor profile | ||
| :whale: `--security-opt label=<selinuxbel>`: specify custom selinux label | ||
| - :whale: `--security-opt no-new-privileges`: disallow privilege escalation, e.g., setuid and file capabilities | ||
| - :whale: `--security-opt systempaths=unconfined`: Turn off confinement for system paths (masked paths, read-only paths) for the container | ||
| - :whale: `--security-opt writable-cgroups`: making the cgroups writeable | ||
|
|
@@ -1959,6 +1960,7 @@ Flags: | |
| - :nerd_face: `--host-gateway-ip`: IP address that the special 'host-gateway' string in --add-host resolves to. It has no effect without setting --add-host | ||
| - Default: the IP address of the host | ||
| - :nerd_face: `--userns-remap=<username>:<groupname>`: Support idmapping of containers. This options is only supported on rootful linux for container create and run if a user name and optionally group name is passed, it does idmapping based on the uidmap and gidmap ranges specified in /etc/subuid and /etc/subgid respectively. Note: `--userns-remap` is not supported for building containers. Nerdctl Build doesn't support userns-remap feature. (format: <name|uid>[:<group|gid>]) | ||
| - :nerd_face: `--selinux-enabled`: Enable selinux support | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/containerd/nerdctl/blob/main/docs/config.md needs to be updated too |
||
|
|
||
| The global flags can be also specified in `/etc/nerdctl/nerdctl.toml` (rootful) and `~/.config/nerdctl/nerdctl.toml` (rootless). | ||
| See [`./config.md`](./config.md). | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ userns_remap = "" | |
| dns = ["8.8.8.8", "1.1.1.1"] | ||
| dns_opts = ["ndots:1", "timeout:2"] | ||
| dns_search = ["example.com", "example.org"] | ||
| selinux_enabled= true | ||
| ``` | ||
|
|
||
| ## Properties | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs to be updated |
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,7 @@ import ( | |||||
| "github.com/docker/go-units" | ||||||
| mobymount "github.com/moby/sys/mount" | ||||||
| "github.com/opencontainers/runtime-spec/specs-go" | ||||||
| "github.com/opencontainers/selinux/go-selinux/label" | ||||||
| "golang.org/x/sys/unix" | ||||||
|
|
||||||
| "github.com/containerd/containerd/v2/core/containers" | ||||||
|
|
@@ -112,6 +113,7 @@ func parseVolumeOptionsWithMountInfo(vType, src, optsRaw string, getMountInfoFun | |||||
| propagationRawOpts []string | ||||||
| bindOpts []string | ||||||
| ) | ||||||
| var specOpts []oci.SpecOpts | ||||||
| for _, opt := range strings.Split(optsRaw, ",") { | ||||||
| switch opt { | ||||||
| case "rw", "ro", "rro": | ||||||
|
|
@@ -121,6 +123,15 @@ func parseVolumeOptionsWithMountInfo(vType, src, optsRaw string, getMountInfoFun | |||||
| case "bind", "rbind": | ||||||
| // bind means not recursively bind-mounted, rbind is the opposite | ||||||
| bindOpts = append(bindOpts, opt) | ||||||
| case "Z", "z": | ||||||
| specOpts = append(specOpts, func(ctx context.Context, cli oci.Client, c *containers.Container, s *oci.Spec) error { | ||||||
| if s.Linux != nil && s.Linux.MountLabel != "" { | ||||||
| if err := label.Relabel(src, s.Linux.MountLabel, strings.Contains(opt, "z")); err != nil { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| return err | ||||||
| } | ||||||
| } | ||||||
| return nil | ||||||
| }) | ||||||
| case "": | ||||||
| // NOP | ||||||
| default: | ||||||
|
|
@@ -129,7 +140,6 @@ func parseVolumeOptionsWithMountInfo(vType, src, optsRaw string, getMountInfoFun | |||||
| } | ||||||
|
|
||||||
| var opts []string | ||||||
| var specOpts []oci.SpecOpts | ||||||
|
|
||||||
| if len(bindOpts) > 0 && vType != Bind { | ||||||
| return nil, nil, fmt.Errorf("volume bind/rbind option is only supported for bind mount: %+v", bindOpts) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ import ( | |
| "strings" | ||
|
|
||
| "github.com/Masterminds/semver/v3" | ||
| "github.com/opencontainers/selinux/go-selinux" | ||
| "gotest.tools/v3/assert" | ||
|
|
||
| "github.com/containerd/containerd/v2/defaults" | ||
|
|
@@ -161,6 +162,19 @@ var Rootless = &test.Requirement{ | |
| }, | ||
| } | ||
|
|
||
| // NoSexlinux marks a test as suitable only for the noselinux enable environment | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The typo still remains |
||
| var NoSelinux = &test.Requirement{ | ||
| Check: func(data test.Data, helpers test.Helpers) (ret bool, mess string) { | ||
| ret = !selinux.GetEnabled() | ||
| if ret { | ||
| mess = "selinux is disabled" | ||
| } else { | ||
| mess = "selinux is enabled" | ||
| } | ||
| return ret, mess | ||
| }, | ||
| } | ||
|
|
||
| // RootlessWithDetachNetNS marks a test as suitable only for rootless environment with detached netns support. | ||
| var RootlessWithDetachNetNS = &test.Requirement{ | ||
| Check: func(data test.Data, helpers test.Helpers) (ret bool, mess string) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test has to be skipped on Docker, as Docker lacks
--selinux-enabledon the client side