Add MCS label support

Carry of #1246

Signed-off-by: Darren Shepherd <darren@rancher.com>
Signed-off-by: Michael Crosby <michael@thepasture.io>
This commit is contained in:
Darren Shepherd 2019-08-23 23:58:37 -07:00 committed by Michael Crosby
parent 40071878d7
commit 24209b91bf
23 changed files with 416 additions and 88 deletions

View File

@ -33,6 +33,7 @@ TARBALL := $(TARBALL_PREFIX)-$(VERSION).$(GOOS)-$(GOARCH).tar.gz
ifneq ($(GOOS),windows) ifneq ($(GOOS),windows)
BUILD_TAGS := seccomp apparmor selinux BUILD_TAGS := seccomp apparmor selinux
endif endif
export BUILDTAGS := $(BUILD_TAGS)
# Add `-TEST` suffix to indicate that all binaries built from this repo are for test. # 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 GO_LDFLAGS := -X $(PROJECT)/vendor/github.com/containerd/containerd/version.Version=$(VERSION)-TEST
SOURCES := $(shell find cmd/ pkg/ vendor/ -name '*.go') SOURCES := $(shell find cmd/ pkg/ vendor/ -name '*.go')
@ -91,7 +92,7 @@ test: ## unit test
@echo "$(WHALE) $@" @echo "$(WHALE) $@"
$(GO) test -timeout=10m -race ./pkg/... \ $(GO) test -timeout=10m -race ./pkg/... \
-tags '$(BUILD_TAGS)' \ -tags '$(BUILD_TAGS)' \
-ldflags '$(GO_LDFLAGS)' \ -ldflags '$(GO_LDFLAGS)' \
-gcflags '$(GO_GCFLAGS)' -gcflags '$(GO_GCFLAGS)'
$(BUILD_DIR)/integration.test: $(INTEGRATION_SOURCES) $(BUILD_DIR)/integration.test: $(INTEGRATION_SOURCES)
@ -162,7 +163,7 @@ else
install.deps: .install.deps.linux ## install windows deps on linux install.deps: .install.deps.linux ## install windows deps on linux
endif endif
.install.deps.linux: ## install dependencies of cri (default 'seccomp apparmor' BUILDTAGS for runc build) .install.deps.linux: ## install dependencies of cri
@echo "$(WHALE) $@" @echo "$(WHALE) $@"
@./hack/install/install-deps.sh @./hack/install/install-deps.sh

View File

@ -29,7 +29,7 @@ GOPATH=$(mktemp -d /tmp/cri-install-runc.XXXX)
from-vendor RUNC github.com/opencontainers/runc from-vendor RUNC github.com/opencontainers/runc
checkout_repo ${RUNC_PKG} ${RUNC_VERSION} ${RUNC_REPO} checkout_repo ${RUNC_PKG} ${RUNC_VERSION} ${RUNC_REPO}
cd ${GOPATH}/src/${RUNC_PKG} 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} ${SUDO} make install -e DESTDIR=${RUNC_DIR}
# Clean the tmp GOPATH dir. Use sudo because runc build generates # Clean the tmp GOPATH dir. Use sudo because runc build generates

View File

@ -23,6 +23,15 @@ CONTAINERD_FLAGS="--log-level=debug "
# Use a configuration file for containerd. # Use a configuration file for containerd.
CONTAINERD_CONFIG_FILE=${CONTAINERD_CONFIG_FILE:-""} 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 # CONTAINERD_TEST_SUFFIX is the suffix appended to the root/state directory used
# by test containerd. # by test containerd.
CONTAINERD_TEST_SUFFIX=${CONTAINERD_TEST_SUFFIX:-"-test"} CONTAINERD_TEST_SUFFIX=${CONTAINERD_TEST_SUFFIX:-"-test"}

View File

@ -17,7 +17,7 @@
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/.. ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
# Not from vendor.conf. # Not from vendor.conf.
CRITOOL_VERSION=v1.18.0 CRITOOL_VERSION=89384cc13a27bb9128553c9fe75a7cc07c6a95bb
CRITOOL_PKG=github.com/kubernetes-sigs/cri-tools CRITOOL_PKG=github.com/kubernetes-sigs/cri-tools
CRITOOL_REPO=github.com/kubernetes-sigs/cri-tools CRITOOL_REPO=github.com/kubernetes-sigs/cri-tools

View File

@ -226,7 +226,7 @@ func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*ru
} }
if mount.GetSelinuxRelabel() { 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) return errors.Wrapf(err, "relabel %q with %q failed", src, mountLabel)
} }
} }

View File

@ -28,6 +28,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/net/context" "golang.org/x/net/context"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
@ -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) 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)) log.G(ctx).Debugf("Container %q spec: %#+v", id, spew.NewFormatter(spec))
// Set snapshotter before any other options. // Set snapshotter before any other options.
@ -275,10 +288,9 @@ func (c *criService) volumeMounts(containerRootDir string, criMounts []*runtime.
src := filepath.Join(containerRootDir, "volumes", volumeID) src := filepath.Join(containerRootDir, "volumes", volumeID)
// addOCIBindMounts will create these volumes. // addOCIBindMounts will create these volumes.
mounts = append(mounts, &runtime.Mount{ mounts = append(mounts, &runtime.Mount{
ContainerPath: dst, ContainerPath: dst,
HostPath: src, HostPath: src,
// Use default mount propagation. SelinuxRelabel: true,
// TODO(random-liu): What about selinux relabel?
}) })
} }
return mounts return mounts

View File

@ -31,6 +31,7 @@ import (
"github.com/containerd/containerd/oci" "github.com/containerd/containerd/oci"
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors" "github.com/pkg/errors"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
@ -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, func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint32, netNSPath string, containerName string,
config *runtime.ContainerConfig, sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig, 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{ specOpts := []oci.SpecOpts{
customopts.WithoutRunMount, customopts.WithoutRunMount,
@ -151,11 +152,30 @@ func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint3
specOpts = append(specOpts, oci.WithEnv(env)) specOpts = append(specOpts, oci.WithEnv(env))
securityContext := config.GetLinux().GetSecurityContext() securityContext := config.GetLinux().GetSecurityContext()
selinuxOpt := securityContext.GetSelinuxOptions() labelOptions, err := toLabel(securityContext.GetSelinuxOptions())
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt) 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 { if err != nil {
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions()) 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)) specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel))
if !c.config.DisableProcMount { if !c.config.DisableProcMount {

View File

@ -35,6 +35,7 @@ import (
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/devices"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -233,6 +234,12 @@ func TestContainerCapabilities(t *testing.T) {
containerConfig.Linux.SecurityContext.Capabilities = test.capability containerConfig.Linux.SecurityContext.Capabilities = test.capability
spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime) spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
require.NoError(t, err) 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) specCheck(t, testID, testSandboxID, testPid, spec)
for _, include := range test.includes { for _, include := range test.includes {
assert.Contains(t, spec.Process.Capabilities.Bounding, include) assert.Contains(t, spec.Process.Capabilities.Bounding, include)

View File

@ -19,7 +19,6 @@
package server package server
import ( import (
"strings"
"testing" "testing"
"github.com/opencontainers/selinux/go-selinux" "github.com/opencontainers/selinux/go-selinux"
@ -35,23 +34,23 @@ func TestInitSelinuxOpts(t *testing.T) {
for desc, test := range map[string]struct { for desc, test := range map[string]struct {
selinuxOpt *runtime.SELinuxOption selinuxOpt *runtime.SELinuxOption
processLabel string processLabel string
mountLabels []string mountLabel string
expectErr bool expectErr bool
}{ }{
"Should return empty strings for processLabel and mountLabel when selinuxOpt is nil": { "Should return empty strings for processLabel and mountLabel when selinuxOpt is nil": {
selinuxOpt: nil, selinuxOpt: nil,
processLabel: "", processLabel: ".*:c[0-9]{1,3},c[0-9]{1,3}",
mountLabels: []string{"", ""}, 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{ selinuxOpt: &runtime.SELinuxOption{
User: "", User: "",
Role: "user_r", Role: "user_r",
Type: "", Type: "",
Level: "s0:c1,c2", Level: "s0:c1,c2",
}, },
processLabel: "", processLabel: "system_u:user_r:(container_file_t|svirt_lxc_net_t):s0:c1,c2",
mountLabels: []string{"", ""}, 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": { "Should be resolved correctly when selinuxOpt has been initialized completely": {
selinuxOpt: &runtime.SELinuxOption{ selinuxOpt: &runtime.SELinuxOption{
@ -61,7 +60,7 @@ func TestInitSelinuxOpts(t *testing.T) {
Level: "s0:c1,c2", Level: "s0:c1,c2",
}, },
processLabel: "user_u:user_r:user_t: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=''": { "Should be resolved correctly when selinuxOpt has been initialized with level=''": {
selinuxOpt: &runtime.SELinuxOption{ selinuxOpt: &runtime.SELinuxOption{
@ -70,8 +69,8 @@ func TestInitSelinuxOpts(t *testing.T) {
Type: "user_t", Type: "user_t",
Level: "", Level: "",
}, },
processLabel: "user_u:user_r:user_t:s0", processLabel: "user_u:user_r:user_t:s0:c[0-9]{1,3},c[0-9]{1,3}",
mountLabels: []string{"user_u:object_r:container_file_t:s0", "user_u:object_r:svirt_sandbox_file_t:s0"}, mountLabel: "user_u:object_r:(container_file_t|svirt_sandbox_file_t):s0",
}, },
"Should return error when the format of 'level' is not correct": { "Should return error when the format of 'level' is not correct": {
selinuxOpt: &runtime.SELinuxOption{ selinuxOpt: &runtime.SELinuxOption{
@ -84,20 +83,12 @@ func TestInitSelinuxOpts(t *testing.T) {
}, },
} { } {
t.Run(desc, func(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 { if test.expectErr {
assert.Error(t, err) assert.Error(t, err)
} else { } else {
assert.NoError(t, err) assert.Regexp(t, test.processLabel, processLabel)
if test.selinuxOpt == nil || test.selinuxOpt.Level != "" { assert.Regexp(t, test.mountLabel, mountLabel)
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)
}
} }
}) })
} }
@ -157,13 +148,11 @@ func TestCheckSelinuxLevel(t *testing.T) {
}, },
} { } {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
ok, err := checkSelinuxLevel(test.level) err := checkSelinuxLevel(test.level)
if test.expectNoMatch { if test.expectNoMatch {
assert.NoError(t, err) assert.Error(t, err)
assert.False(t, ok)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, ok)
} }
}) })
} }

View File

@ -93,47 +93,52 @@ func (c *criService) getSandboxDevShm(id string) string {
return filepath.Join(c.getVolatileSandboxRootDir(id), "shm") return filepath.Join(c.getVolatileSandboxRootDir(id), "shm")
} }
func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error) { func toLabel(selinuxOptions *runtime.SELinuxOption) ([]string, error) {
if selinuxOpt == nil { var labels []string
return "", "", nil
if selinuxOptions == nil {
return nil, nil
}
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)
} }
// Should ignored selinuxOpts if they are incomplete. return labels, nil
if selinuxOpt.GetUser() == "" || }
selinuxOpt.GetRole() == "" ||
selinuxOpt.GetType() == "" {
return "", "", nil
}
// make sure the format of "level" is correct. func initLabelsFromOpt(selinuxOpts *runtime.SELinuxOption) (string, string, error) {
ok, err := checkSelinuxLevel(selinuxOpt.GetLevel()) labels, err := toLabel(selinuxOpts)
if err != nil || !ok {
return "", "", err
}
labelOpts := fmt.Sprintf("%s:%s:%s:%s",
selinuxOpt.GetUser(),
selinuxOpt.GetRole(),
selinuxOpt.GetType(),
selinuxOpt.GetLevel())
options, err := label.DupSecOpt(labelOpts)
if err != nil { if err != nil {
return "", "", err 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 { 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) 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 || !matched { if err != nil {
return false, errors.Wrapf(err, "the format of 'level' %q is not correct", level) 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 { func (c *criService) apparmorEnabled() bool {

View File

@ -29,6 +29,7 @@ import (
cni "github.com/containerd/go-cni" cni "github.com/containerd/go-cni"
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -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") return nil, errors.Wrap(err, "failed to generate sandbox container spec")
} }
log.G(ctx).Debugf("Sandbox container %q spec: %#+v", id, spew.NewFormatter(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)
}
}()
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. // Generate spec options that will be applied to the spec later.
specOpts, err := c.sandboxContainerSpecOpts(config, &image.ImageSpec.Config) specOpts, err := c.sandboxContainerSpecOpts(config, &image.ImageSpec.Config)

View File

@ -28,6 +28,7 @@ import (
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
@ -38,7 +39,7 @@ import (
) )
func (c *criService) sandboxContainerSpec(id string, config *runtime.PodSandboxConfig, func (c *criService) sandboxContainerSpec(id string, config *runtime.PodSandboxConfig,
imageConfig *imagespec.ImageConfig, nsPath string, runtimePodAnnotations []string) (*runtimespec.Spec, error) { imageConfig *imagespec.ImageConfig, nsPath string, runtimePodAnnotations []string) (_ *runtimespec.Spec, retErr error) {
// Creates a spec Generator with the default spec. // Creates a spec Generator with the default spec.
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default. // TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
specOpts := []oci.SpecOpts{ specOpts := []oci.SpecOpts{
@ -117,11 +118,15 @@ func (c *criService) sandboxContainerSpec(id string, config *runtime.PodSandboxC
}, },
})) }))
selinuxOpt := securityContext.GetSelinuxOptions() processLabel, mountLabel, err := initLabelsFromOpt(securityContext.GetSelinuxOptions())
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions()) return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions())
} }
defer func() {
if retErr != nil {
_ = label.ReleaseLabel(processLabel)
}
}()
supplementalGroups := securityContext.GetSupplementalGroups() supplementalGroups := securityContext.GetSupplementalGroups()
specOpts = append(specOpts, specOpts = append(specOpts,

View File

@ -25,6 +25,7 @@ import (
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
@ -76,6 +77,11 @@ func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, *imagespec.ImageConf
assert.Contains(t, spec.Annotations, annotations.SandboxLogDir) assert.Contains(t, spec.Annotations, annotations.SandboxLogDir)
assert.EqualValues(t, spec.Annotations[annotations.SandboxLogDir], "test-log-directory") assert.EqualValues(t, spec.Annotations[annotations.SandboxLogDir], "test-log-directory")
if selinux.GetEnabled() {
assert.NotEqual(t, "", spec.Process.SelinuxLabel)
assert.NotEqual(t, "", spec.Linux.MountLabel)
}
} }
return config, imageConfig, specCheck return config, imageConfig, specCheck
} }

View File

@ -25,6 +25,7 @@ import (
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/cri/pkg/store/label"
cni "github.com/containerd/go-cni" cni "github.com/containerd/go-cni"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -99,12 +100,13 @@ type criService struct {
// NewCRIService returns a new instance of CRIService // NewCRIService returns a new instance of CRIService
func NewCRIService(config criconfig.Config, client *containerd.Client) (CRIService, error) { func NewCRIService(config criconfig.Config, client *containerd.Client) (CRIService, error) {
var err error var err error
labels := label.NewStore()
c := &criService{ c := &criService{
config: config, config: config,
client: client, client: client,
os: osinterface.RealOS{}, os: osinterface.RealOS{},
sandboxStore: sandboxstore.NewStore(), sandboxStore: sandboxstore.NewStore(labels),
containerStore: containerstore.NewStore(), containerStore: containerstore.NewStore(labels),
imageStore: imagestore.NewStore(client), imageStore: imagestore.NewStore(client),
snapshotStore: snapshotstore.NewStore(), snapshotStore: snapshotstore.NewStore(),
sandboxNameIndex: registrar.NewRegistrar(), sandboxNameIndex: registrar.NewRegistrar(),

View File

@ -23,6 +23,7 @@ import (
servertesting "github.com/containerd/cri/pkg/server/testing" servertesting "github.com/containerd/cri/pkg/server/testing"
containerstore "github.com/containerd/cri/pkg/store/container" containerstore "github.com/containerd/cri/pkg/store/container"
imagestore "github.com/containerd/cri/pkg/store/image" imagestore "github.com/containerd/cri/pkg/store/image"
"github.com/containerd/cri/pkg/store/label"
sandboxstore "github.com/containerd/cri/pkg/store/sandbox" sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
snapshotstore "github.com/containerd/cri/pkg/store/snapshot" snapshotstore "github.com/containerd/cri/pkg/store/snapshot"
) )
@ -39,6 +40,7 @@ const (
// newTestCRIService creates a fake criService for test. // newTestCRIService creates a fake criService for test.
func newTestCRIService() *criService { func newTestCRIService() *criService {
labels := label.NewStore()
return &criService{ return &criService{
config: criconfig.Config{ config: criconfig.Config{
RootDir: testRootDir, RootDir: testRootDir,
@ -49,11 +51,11 @@ func newTestCRIService() *criService {
}, },
imageFSPath: testImageFSPath, imageFSPath: testImageFSPath,
os: ostesting.NewFakeOS(), os: ostesting.NewFakeOS(),
sandboxStore: sandboxstore.NewStore(), sandboxStore: sandboxstore.NewStore(labels),
imageStore: imagestore.NewStore(nil), imageStore: imagestore.NewStore(nil),
snapshotStore: snapshotstore.NewStore(), snapshotStore: snapshotstore.NewStore(),
sandboxNameIndex: registrar.NewRegistrar(), sandboxNameIndex: registrar.NewRegistrar(),
containerStore: containerstore.NewStore(), containerStore: containerstore.NewStore(labels),
containerNameIndex: registrar.NewRegistrar(), containerNameIndex: registrar.NewRegistrar(),
netPlugin: servertesting.NewFakeCNIPlugin(), netPlugin: servertesting.NewFakeCNIPlugin(),
} }

View File

@ -20,6 +20,7 @@ import (
"sync" "sync"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/cri/pkg/store/label"
"github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/pkg/truncindex"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
@ -101,13 +102,15 @@ type Store struct {
lock sync.RWMutex lock sync.RWMutex
containers map[string]Container containers map[string]Container
idIndex *truncindex.TruncIndex idIndex *truncindex.TruncIndex
labels *label.Store
} }
// NewStore creates a container store. // NewStore creates a container store.
func NewStore() *Store { func NewStore(labels *label.Store) *Store {
return &Store{ return &Store{
containers: make(map[string]Container), containers: make(map[string]Container),
idIndex: truncindex.NewTruncIndex([]string{}), idIndex: truncindex.NewTruncIndex([]string{}),
labels: labels,
} }
} }
@ -119,6 +122,9 @@ func (s *Store) Add(c Container) error {
if _, ok := s.containers[c.ID]; ok { if _, ok := s.containers[c.ID]; ok {
return store.ErrAlreadyExist return store.ErrAlreadyExist
} }
if err := s.labels.Reserve(c.ProcessLabel); err != nil {
return err
}
if err := s.idIndex.Add(c.ID); err != nil { if err := s.idIndex.Add(c.ID); err != nil {
return err return err
} }
@ -165,6 +171,7 @@ func (s *Store) Delete(id string) {
// So we need to return if there are error. // So we need to return if there are error.
return return
} }
s.labels.Release(s.containers[id].ProcessLabel)
s.idIndex.Delete(id) // nolint: errcheck s.idIndex.Delete(id) // nolint: errcheck
delete(s.containers, id) delete(s.containers, id)
} }

View File

@ -17,9 +17,12 @@
package container package container
import ( import (
"strings"
"testing" "testing"
"time" "time"
"github.com/containerd/cri/pkg/store/label"
"github.com/opencontainers/selinux/go-selinux"
assertlib "github.com/stretchr/testify/assert" assertlib "github.com/stretchr/testify/assert"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
@ -39,9 +42,10 @@ func TestContainerStore(t *testing.T) {
Attempt: 1, Attempt: 1,
}, },
}, },
ImageRef: "TestImage-1", ImageRef: "TestImage-1",
StopSignal: "SIGTERM", StopSignal: "SIGTERM",
LogPath: "/test/log/path/1", LogPath: "/test/log/path/1",
ProcessLabel: "junk:junk:junk:c1,c2",
}, },
"2abcd": { "2abcd": {
ID: "2abcd", ID: "2abcd",
@ -53,9 +57,10 @@ func TestContainerStore(t *testing.T) {
Attempt: 2, Attempt: 2,
}, },
}, },
StopSignal: "SIGTERM", StopSignal: "SIGTERM",
ImageRef: "TestImage-2", ImageRef: "TestImage-2",
LogPath: "/test/log/path/2", LogPath: "/test/log/path/2",
ProcessLabel: "junk:junk:junk:c1,c2",
}, },
"4a333": { "4a333": {
ID: "4a333", ID: "4a333",
@ -67,9 +72,10 @@ func TestContainerStore(t *testing.T) {
Attempt: 3, Attempt: 3,
}, },
}, },
StopSignal: "SIGTERM", StopSignal: "SIGTERM",
ImageRef: "TestImage-3", ImageRef: "TestImage-3",
LogPath: "/test/log/path/3", LogPath: "/test/log/path/3",
ProcessLabel: "junk:junk:junk:c1,c3",
}, },
"4abcd": { "4abcd": {
ID: "4abcd", ID: "4abcd",
@ -81,8 +87,9 @@ func TestContainerStore(t *testing.T) {
Attempt: 1, Attempt: 1,
}, },
}, },
StopSignal: "SIGTERM", StopSignal: "SIGTERM",
ImageRef: "TestImage-4abcd", ImageRef: "TestImage-4abcd",
ProcessLabel: "junk:junk:junk:c1,c4",
}, },
} }
statuses := map[string]Status{ statuses := map[string]Status{
@ -136,7 +143,14 @@ func TestContainerStore(t *testing.T) {
containers[id] = container containers[id] = container
} }
s := NewStore() s := NewStore(label.NewStore())
reserved := map[string]bool{}
s.labels.Reserver = func(label string) {
reserved[strings.SplitN(label, ":", 4)[3]] = true
}
s.labels.Releaser = func(label string) {
reserved[strings.SplitN(label, ":", 4)[3]] = false
}
t.Logf("should be able to add container") t.Logf("should be able to add container")
for _, c := range containers { for _, c := range containers {
@ -155,6 +169,15 @@ func TestContainerStore(t *testing.T) {
cs := s.List() cs := s.List()
assert.Len(cs, len(containers)) assert.Len(cs, len(containers))
if selinux.GetEnabled() {
t.Logf("should have reserved labels (requires -tag selinux)")
assert.Equal(map[string]bool{
"c1,c2": true,
"c1,c3": true,
"c1,c4": true,
}, reserved)
}
cntrNum := len(containers) cntrNum := len(containers)
for testID, v := range containers { for testID, v := range containers {
truncID := genTruncIndex(testID) truncID := genTruncIndex(testID)
@ -173,6 +196,15 @@ func TestContainerStore(t *testing.T) {
assert.Equal(Container{}, c) assert.Equal(Container{}, c)
assert.Equal(store.ErrNotExist, err) assert.Equal(store.ErrNotExist, err)
} }
if selinux.GetEnabled() {
t.Logf("should have released all labels (requires -tag selinux)")
assert.Equal(map[string]bool{
"c1,c2": false,
"c1,c3": false,
"c1,c4": false,
}, reserved)
}
} }
func TestWithContainerIO(t *testing.T) { func TestWithContainerIO(t *testing.T) {

View File

@ -61,6 +61,8 @@ type Metadata struct {
// StopSignal is the system call signal that will be sent to the container to exit. // StopSignal is the system call signal that will be sent to the container to exit.
// TODO(random-liu): Add integration test for stop signal. // TODO(random-liu): Add integration test for stop signal.
StopSignal string StopSignal string
// ProcessLabel is the SELinux process label for the container
ProcessLabel string
} }
// MarshalJSON encodes Metadata into bytes in json format. // MarshalJSON encodes Metadata into bytes in json format.

90
pkg/store/label/label.go Normal file
View File

@ -0,0 +1,90 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package label
import (
"sync"
"github.com/opencontainers/selinux/go-selinux"
)
type Store struct {
sync.Mutex
levels map[string]int
Releaser func(string)
Reserver func(string)
}
func NewStore() *Store {
return &Store{
levels: map[string]int{},
Releaser: selinux.ReleaseLabel,
Reserver: selinux.ReserveLabel,
}
}
func (s *Store) Reserve(label string) error {
s.Lock()
defer s.Unlock()
context, err := selinux.NewContext(label)
if err != nil {
return err
}
level := context["level"]
// no reason to count empty
if level == "" {
return nil
}
if _, ok := s.levels[level]; !ok {
s.Reserver(label)
}
s.levels[level]++
return nil
}
func (s *Store) Release(label string) {
s.Lock()
defer s.Unlock()
context, err := selinux.NewContext(label)
if err != nil {
return
}
level := context["level"]
if level == "" {
return
}
count, ok := s.levels[level]
if !ok {
return
}
switch {
case count == 1:
s.Releaser(label)
delete(s.levels, level)
case count < 1:
delete(s.levels, level)
case count > 1:
s.levels[level] = count - 1
}
}

View File

@ -0,0 +1,116 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package label
import (
"testing"
"github.com/opencontainers/selinux/go-selinux"
"github.com/stretchr/testify/assert"
)
func TestAddThenRemove(t *testing.T) {
if !selinux.GetEnabled() {
t.Skip("selinux is not enabled")
}
assert := assert.New(t)
store := NewStore()
releaseCount := 0
reserveCount := 0
store.Releaser = func(label string) {
assert.Contains(label, ":c1,c2")
releaseCount++
assert.Equal(1, releaseCount)
}
store.Reserver = func(label string) {
assert.Contains(label, ":c1,c2")
reserveCount++
assert.Equal(1, reserveCount)
}
t.Log("should count to two level")
assert.NoError(store.Reserve("junk:junk:junk:c1,c2"))
assert.NoError(store.Reserve("junk2:junk2:junk2:c1,c2"))
t.Log("should have one item")
assert.Equal(1, len(store.levels))
t.Log("c1,c2 count should be 2")
assert.Equal(2, store.levels["c1,c2"])
store.Release("junk:junk:junk:c1,c2")
store.Release("junk2:junk2:junk2:c1,c2")
t.Log("should have 0 items")
assert.Equal(0, len(store.levels))
t.Log("should have reserved")
assert.Equal(1, reserveCount)
t.Log("should have released")
assert.Equal(1, releaseCount)
}
func TestJunkData(t *testing.T) {
if !selinux.GetEnabled() {
t.Skip("selinux is not enabled")
}
assert := assert.New(t)
store := NewStore()
releaseCount := 0
store.Releaser = func(label string) {
releaseCount++
}
reserveCount := 0
store.Reserver = func(label string) {
reserveCount++
}
t.Log("should ignore empty label")
assert.NoError(store.Reserve(""))
assert.Equal(0, len(store.levels))
store.Release("")
assert.Equal(0, len(store.levels))
assert.Equal(0, releaseCount)
assert.Equal(0, reserveCount)
t.Log("should fail on bad label")
assert.Error(store.Reserve("junkjunkjunkc1c2"))
assert.Equal(0, len(store.levels))
store.Release("junkjunkjunkc1c2")
assert.Equal(0, len(store.levels))
assert.Equal(0, releaseCount)
assert.Equal(0, reserveCount)
t.Log("should not release unknown label")
store.Release("junk2:junk2:junk2:c1,c2")
assert.Equal(0, len(store.levels))
assert.Equal(0, releaseCount)
assert.Equal(0, reserveCount)
t.Log("should release once even if too many deletes")
assert.NoError(store.Reserve("junk2:junk2:junk2:c1,c2"))
assert.Equal(1, len(store.levels))
assert.Equal(1, store.levels["c1,c2"])
store.Release("junk2:junk2:junk2:c1,c2")
store.Release("junk2:junk2:junk2:c1,c2")
assert.Equal(0, len(store.levels))
assert.Equal(1, releaseCount)
assert.Equal(1, reserveCount)
}

View File

@ -61,6 +61,8 @@ type Metadata struct {
RuntimeHandler string RuntimeHandler string
// CNIresult resulting configuration for attached network namespace interfaces // CNIresult resulting configuration for attached network namespace interfaces
CNIResult *cni.CNIResult CNIResult *cni.CNIResult
// ProcessLabel is the SELinux process label for the container
ProcessLabel string
} }
// MarshalJSON encodes Metadata into bytes in json format. // MarshalJSON encodes Metadata into bytes in json format.

View File

@ -20,6 +20,7 @@ import (
"sync" "sync"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/cri/pkg/store/label"
"github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/pkg/truncindex"
"github.com/containerd/cri/pkg/netns" "github.com/containerd/cri/pkg/netns"
@ -62,13 +63,15 @@ type Store struct {
lock sync.RWMutex lock sync.RWMutex
sandboxes map[string]Sandbox sandboxes map[string]Sandbox
idIndex *truncindex.TruncIndex idIndex *truncindex.TruncIndex
labels *label.Store
} }
// NewStore creates a sandbox store. // NewStore creates a sandbox store.
func NewStore() *Store { func NewStore(labels *label.Store) *Store {
return &Store{ return &Store{
sandboxes: make(map[string]Sandbox), sandboxes: make(map[string]Sandbox),
idIndex: truncindex.NewTruncIndex([]string{}), idIndex: truncindex.NewTruncIndex([]string{}),
labels: labels,
} }
} }
@ -79,6 +82,9 @@ func (s *Store) Add(sb Sandbox) error {
if _, ok := s.sandboxes[sb.ID]; ok { if _, ok := s.sandboxes[sb.ID]; ok {
return store.ErrAlreadyExist return store.ErrAlreadyExist
} }
if err := s.labels.Reserve(sb.ProcessLabel); err != nil {
return err
}
if err := s.idIndex.Add(sb.ID); err != nil { if err := s.idIndex.Add(sb.ID); err != nil {
return err return err
} }
@ -125,6 +131,7 @@ func (s *Store) Delete(id string) {
// So we need to return if there are error. // So we need to return if there are error.
return return
} }
s.labels.Release(s.sandboxes[id].ProcessLabel)
s.idIndex.Delete(id) // nolint: errcheck s.idIndex.Delete(id) // nolint: errcheck
delete(s.sandboxes, id) delete(s.sandboxes, id)
} }

View File

@ -19,6 +19,7 @@ package sandbox
import ( import (
"testing" "testing"
"github.com/containerd/cri/pkg/store/label"
assertlib "github.com/stretchr/testify/assert" assertlib "github.com/stretchr/testify/assert"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
@ -109,7 +110,7 @@ func TestSandboxStore(t *testing.T) {
Status{State: StateUnknown}, Status{State: StateUnknown},
) )
assert := assertlib.New(t) assert := assertlib.New(t)
s := NewStore() s := NewStore(label.NewStore())
t.Logf("should be able to add sandbox") t.Logf("should be able to add sandbox")
for _, sb := range sandboxes { for _, sb := range sandboxes {