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:
parent
40071878d7
commit
24209b91bf
5
Makefile
5
Makefile
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
90
pkg/store/label/label.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
116
pkg/store/label/label_test.go
Normal file
116
pkg/store/label/label_test.go
Normal 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)
|
||||||
|
}
|
@ -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.
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user