Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Add SELinux Support for CRI #1487

Merged
merged 2 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add MCS label support
Carry of #1246

Signed-off-by: Darren Shepherd <[email protected]>
Signed-off-by: Michael Crosby <[email protected]>
  • Loading branch information
ibuildthecloud authored and crosbymichael committed May 20, 2020
commit 24209b91bf361e131478d15cfea1ab05694dc3eb
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ TARBALL := $(TARBALL_PREFIX)-$(VERSION).$(GOOS)-$(GOARCH).tar.gz
ifneq ($(GOOS),windows)
BUILD_TAGS := seccomp apparmor selinux
endif
export BUILDTAGS := $(BUILD_TAGS)
# Add `-TEST` suffix to indicate that all binaries built from this repo are for test.
GO_LDFLAGS := -X $(PROJECT)/vendor/github.com/containerd/containerd/version.Version=$(VERSION)-TEST
SOURCES := $(shell find cmd/ pkg/ vendor/ -name '*.go')
Expand Down Expand Up @@ -91,7 +92,7 @@ test: ## unit test
@echo "$(WHALE) $@"
$(GO) test -timeout=10m -race ./pkg/... \
-tags '$(BUILD_TAGS)' \
-ldflags '$(GO_LDFLAGS)' \
-ldflags '$(GO_LDFLAGS)' \
-gcflags '$(GO_GCFLAGS)'

$(BUILD_DIR)/integration.test: $(INTEGRATION_SOURCES)
Expand Down Expand Up @@ -162,7 +163,7 @@ else
install.deps: .install.deps.linux ## install windows deps on linux
endif

.install.deps.linux: ## install dependencies of cri (default 'seccomp apparmor' BUILDTAGS for runc build)
.install.deps.linux: ## install dependencies of cri
@echo "$(WHALE) $@"
@./hack/install/install-deps.sh

Expand Down
2 changes: 1 addition & 1 deletion hack/install/install-runc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ GOPATH=$(mktemp -d /tmp/cri-install-runc.XXXX)
from-vendor RUNC github.com/opencontainers/runc
checkout_repo ${RUNC_PKG} ${RUNC_VERSION} ${RUNC_REPO}
cd ${GOPATH}/src/${RUNC_PKG}
make static BUILDTAGS="$BUILDTAGS" VERSION=${RUNC_VERSION}
make BUILDTAGS="$BUILDTAGS" VERSION=${RUNC_VERSION}
${SUDO} make install -e DESTDIR=${RUNC_DIR}

# Clean the tmp GOPATH dir. Use sudo because runc build generates
Expand Down
9 changes: 9 additions & 0 deletions hack/test-utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ CONTAINERD_FLAGS="--log-level=debug "

# Use a configuration file for containerd.
CONTAINERD_CONFIG_FILE=${CONTAINERD_CONFIG_FILE:-""}
if [ -z "${CONTAINERD_CONFIG_FILE}" ] && command -v sestatus >/dev/null 2>&1; then
selinux_config="/tmp/containerd-config-selinux.toml"
cat >${selinux_config} <<<'
[plugins.cri]
enable_selinux = true
'
CONTAINERD_CONFIG_FILE=${CONTAINERD_CONFIG_FILE:-"${selinux_config}"}
fi

# CONTAINERD_TEST_SUFFIX is the suffix appended to the root/state directory used
# by test containerd.
CONTAINERD_TEST_SUFFIX=${CONTAINERD_TEST_SUFFIX:-"-test"}
Expand Down
2 changes: 1 addition & 1 deletion hack/utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..

# Not from vendor.conf.
CRITOOL_VERSION=v1.18.0
CRITOOL_VERSION=89384cc13a27bb9128553c9fe75a7cc07c6a95bb
CRITOOL_PKG=github.com/kubernetes-sigs/cri-tools
CRITOOL_REPO=github.com/kubernetes-sigs/cri-tools

Expand Down
2 changes: 1 addition & 1 deletion pkg/containerd/opts/spec_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*ru
}

if mount.GetSelinuxRelabel() {
if err := label.Relabel(src, mountLabel, true); err != nil && err != unix.ENOTSUP {
if err := label.Relabel(src, mountLabel, false); err != nil && err != unix.ENOTSUP {
return errors.Wrapf(err, "relabel %q with %q failed", src, mountLabel)
}
}
Expand Down
20 changes: 16 additions & 4 deletions pkg/server/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/davecgh/go-spew/spew"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"golang.org/x/net/context"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
Expand Down Expand Up @@ -154,6 +155,18 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
return nil, errors.Wrapf(err, "failed to generate container %q spec", id)
}

meta.ProcessLabel = spec.Process.SelinuxLabel
if config.GetLinux().GetSecurityContext().GetPrivileged() {
// If privileged don't set the SELinux label but still record it on the container so
// the unused MCS label can be release later
spec.Process.SelinuxLabel = ""
}
defer func() {
if retErr != nil {
_ = label.ReleaseLabel(spec.Process.SelinuxLabel)
}
}()

log.G(ctx).Debugf("Container %q spec: %#+v", id, spew.NewFormatter(spec))

// Set snapshotter before any other options.
Expand Down Expand Up @@ -275,10 +288,9 @@ func (c *criService) volumeMounts(containerRootDir string, criMounts []*runtime.
src := filepath.Join(containerRootDir, "volumes", volumeID)
// addOCIBindMounts will create these volumes.
mounts = append(mounts, &runtime.Mount{
ContainerPath: dst,
HostPath: src,
// Use default mount propagation.
// TODO(random-liu): What about selinux relabel?
ContainerPath: dst,
HostPath: src,
SelinuxRelabel: true,
})
}
return mounts
Expand Down
26 changes: 23 additions & 3 deletions pkg/server/container_create_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/containerd/containerd/oci"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"

Expand Down Expand Up @@ -109,7 +110,7 @@ func (c *criService) containerMounts(sandboxID string, config *runtime.Container

func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint32, netNSPath string, containerName string,
config *runtime.ContainerConfig, sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig,
extraMounts []*runtime.Mount, ociRuntime config.Runtime) (*runtimespec.Spec, error) {
extraMounts []*runtime.Mount, ociRuntime config.Runtime) (_ *runtimespec.Spec, retErr error) {

specOpts := []oci.SpecOpts{
customopts.WithoutRunMount,
Expand Down Expand Up @@ -151,11 +152,30 @@ func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint3
specOpts = append(specOpts, oci.WithEnv(env))

securityContext := config.GetLinux().GetSecurityContext()
selinuxOpt := securityContext.GetSelinuxOptions()
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt)
labelOptions, err := toLabel(securityContext.GetSelinuxOptions())
if err != nil {
return nil, err
}
if len(labelOptions) == 0 {
// Use pod level SELinux config
if sandbox, err := c.sandboxStore.Get(sandboxID); err == nil {
labelOptions, err = label.DupSecOpt(sandbox.ProcessLabel)
if err != nil {
return nil, err
}
}
}

processLabel, mountLabel, err := label.InitLabels(labelOptions)
if err != nil {
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions())
}
defer func() {
if retErr != nil {
_ = label.ReleaseLabel(processLabel)
}
}()

specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel))

if !c.config.DisableProcMount {
Expand Down
7 changes: 7 additions & 0 deletions pkg/server/container_create_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runc/libcontainer/devices"
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -233,6 +234,12 @@ func TestContainerCapabilities(t *testing.T) {
containerConfig.Linux.SecurityContext.Capabilities = test.capability
spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
require.NoError(t, err)

if selinux.GetEnabled() {
assert.NotEqual(t, "", spec.Process.SelinuxLabel)
assert.NotEqual(t, "", spec.Linux.MountLabel)
}

specCheck(t, testID, testSandboxID, testPid, spec)
for _, include := range test.includes {
assert.Contains(t, spec.Process.Capabilities.Bounding, include)
Expand Down
39 changes: 14 additions & 25 deletions pkg/server/helpers_selinux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package server

import (
"strings"
"testing"

"github.com/opencontainers/selinux/go-selinux"
Expand All @@ -35,23 +34,23 @@ func TestInitSelinuxOpts(t *testing.T) {
for desc, test := range map[string]struct {
selinuxOpt *runtime.SELinuxOption
processLabel string
mountLabels []string
mountLabel string
expectErr bool
}{
"Should return empty strings for processLabel and mountLabel when selinuxOpt is nil": {
selinuxOpt: nil,
processLabel: "",
mountLabels: []string{"", ""},
processLabel: ".*:c[0-9]{1,3},c[0-9]{1,3}",
mountLabel: ".*:c[0-9]{1,3},c[0-9]{1,3}",
},
"Should return empty strings for processLabel and mountLabel when selinuxOpt has been initialized partially": {
"Should overlay fields on processLabel when selinuxOpt has been initialized partially": {
selinuxOpt: &runtime.SELinuxOption{
User: "",
Role: "user_r",
Type: "",
Level: "s0:c1,c2",
},
processLabel: "",
mountLabels: []string{"", ""},
processLabel: "system_u:user_r:(container_file_t|svirt_lxc_net_t):s0:c1,c2",
mountLabel: "system_u:object_r:(container_file_t|svirt_sandbox_file_t):s0:c1,c2",
},
"Should be resolved correctly when selinuxOpt has been initialized completely": {
selinuxOpt: &runtime.SELinuxOption{
Expand All @@ -61,7 +60,7 @@ func TestInitSelinuxOpts(t *testing.T) {
Level: "s0:c1,c2",
},
processLabel: "user_u:user_r:user_t:s0:c1,c2",
mountLabels: []string{"user_u:object_r:container_file_t:s0:c1,c2", "user_u:object_r:svirt_sandbox_file_t:s0:c1,c2"},
mountLabel: "user_u:object_r:(container_file_t|svirt_sandbox_file_t):s0:c1,c2",
},
"Should be resolved correctly when selinuxOpt has been initialized with level=''": {
selinuxOpt: &runtime.SELinuxOption{
Expand All @@ -70,8 +69,8 @@ func TestInitSelinuxOpts(t *testing.T) {
Type: "user_t",
Level: "",
},
processLabel: "user_u:user_r:user_t:s0",
mountLabels: []string{"user_u:object_r:container_file_t:s0", "user_u:object_r:svirt_sandbox_file_t:s0"},
processLabel: "user_u:user_r:user_t:s0:c[0-9]{1,3},c[0-9]{1,3}",
mountLabel: "user_u:object_r:(container_file_t|svirt_sandbox_file_t):s0",
},
"Should return error when the format of 'level' is not correct": {
selinuxOpt: &runtime.SELinuxOption{
Expand All @@ -84,20 +83,12 @@ func TestInitSelinuxOpts(t *testing.T) {
},
} {
t.Run(desc, func(t *testing.T) {
processLabel, mountLabel, err := initSelinuxOpts(test.selinuxOpt)
processLabel, mountLabel, err := initLabelsFromOpt(test.selinuxOpt)
if test.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
if test.selinuxOpt == nil || test.selinuxOpt.Level != "" {
assert.Equal(t, test.processLabel, processLabel)
assert.Contains(t, test.mountLabels, mountLabel)
} else {
assert.Equal(t, 0, strings.LastIndex(processLabel, test.processLabel))
contain := strings.LastIndex(mountLabel, test.mountLabels[0]) == 0 ||
strings.LastIndex(mountLabel, test.mountLabels[1]) == 0
assert.True(t, contain)
}
assert.Regexp(t, test.processLabel, processLabel)
assert.Regexp(t, test.mountLabel, mountLabel)
}
})
}
Expand Down Expand Up @@ -157,13 +148,11 @@ func TestCheckSelinuxLevel(t *testing.T) {
},
} {
t.Run(desc, func(t *testing.T) {
ok, err := checkSelinuxLevel(test.level)
err := checkSelinuxLevel(test.level)
if test.expectNoMatch {
assert.NoError(t, err)
assert.False(t, ok)
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.True(t, ok)
}
})
}
Expand Down
59 changes: 32 additions & 27 deletions pkg/server/helpers_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,47 +93,52 @@ func (c *criService) getSandboxDevShm(id string) string {
return filepath.Join(c.getVolatileSandboxRootDir(id), "shm")
}

func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error) {
if selinuxOpt == nil {
return "", "", nil
}
func toLabel(selinuxOptions *runtime.SELinuxOption) ([]string, error) {
var labels []string

// Should ignored selinuxOpts if they are incomplete.
if selinuxOpt.GetUser() == "" ||
selinuxOpt.GetRole() == "" ||
selinuxOpt.GetType() == "" {
return "", "", nil
if selinuxOptions == nil {
return nil, nil
}

// make sure the format of "level" is correct.
ok, err := checkSelinuxLevel(selinuxOpt.GetLevel())
if err != nil || !ok {
return "", "", err
if err := checkSelinuxLevel(selinuxOptions.Level); err != nil {
return nil, err
}
if selinuxOptions.User != "" {
labels = append(labels, "user:"+selinuxOptions.User)
}
if selinuxOptions.Role != "" {
labels = append(labels, "role:"+selinuxOptions.Role)
}
if selinuxOptions.Type != "" {
labels = append(labels, "type:"+selinuxOptions.Type)
}
if selinuxOptions.Level != "" {
labels = append(labels, "level:"+selinuxOptions.Level)
}

labelOpts := fmt.Sprintf("%s:%s:%s:%s",
selinuxOpt.GetUser(),
selinuxOpt.GetRole(),
selinuxOpt.GetType(),
selinuxOpt.GetLevel())
return labels, nil
}

options, err := label.DupSecOpt(labelOpts)
func initLabelsFromOpt(selinuxOpts *runtime.SELinuxOption) (string, string, error) {
labels, err := toLabel(selinuxOpts)
if err != nil {
return "", "", err
}
return label.InitLabels(options)
return label.InitLabels(labels)
}

func checkSelinuxLevel(level string) (bool, error) {
func checkSelinuxLevel(level string) error {
if len(level) == 0 {
return true, nil
return nil
}

matched, err := regexp.MatchString(`^s\d(-s\d)??(:c\d{1,4}((.c\d{1,4})?,c\d{1,4})*(.c\d{1,4})?(,c\d{1,4}(.c\d{1,4})?)*)?$`, level)
if err != nil || !matched {
return false, errors.Wrapf(err, "the format of 'level' %q is not correct", level)
matched, err := regexp.MatchString(`^s\d(-s\d)??(:c\d{1,4}(\.c\d{1,4})?(,c\d{1,4}(\.c\d{1,4})?)*)?$`, level)
if err != nil {
return errors.Wrapf(err, "the format of 'level' %q is not correct", level)
}
return true, nil
if !matched {
return fmt.Errorf("the format of 'level' %q is not correct", level)
}
return nil
}

func (c *criService) apparmorEnabled() bool {
Expand Down
13 changes: 13 additions & 0 deletions pkg/server/sandbox_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
cni "github.com/containerd/go-cni"
"github.com/containerd/typeurl"
"github.com/davecgh/go-spew/spew"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
Expand Down Expand Up @@ -157,6 +158,18 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
return nil, errors.Wrap(err, "failed to generate sandbox container spec")
}
log.G(ctx).Debugf("Sandbox container %q spec: %#+v", id, spew.NewFormatter(spec))
sandbox.ProcessLabel = spec.Process.SelinuxLabel
defer func() {
if retErr != nil {
_ = label.ReleaseLabel(sandbox.ProcessLabel)
crosbymichael marked this conversation as resolved.
Show resolved Hide resolved
}
}()

if config.GetLinux().GetSecurityContext().GetPrivileged() {
// If privileged don't set selinux label, but we still record the MCS label so that
// the unused label can be freed later.
spec.Process.SelinuxLabel = ""
}

// Generate spec options that will be applied to the spec later.
specOpts, err := c.sandboxContainerSpecOpts(config, &image.ImageSpec.Config)
Expand Down
Loading