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)
|
||||
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')
|
||||
@ -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)
|
||||
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
@ -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.
|
||||
@ -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
|
||||
|
@ -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"
|
||||
|
||||
@ -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,
|
||||
@ -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 {
|
||||
|
@ -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"
|
||||
@ -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)
|
||||
|
@ -19,7 +19,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
@ -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{
|
||||
@ -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{
|
||||
@ -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{
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
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.
|
||||
if selinuxOpt.GetUser() == "" ||
|
||||
selinuxOpt.GetRole() == "" ||
|
||||
selinuxOpt.GetType() == "" {
|
||||
return "", "", nil
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
// make sure the format of "level" is correct.
|
||||
ok, err := checkSelinuxLevel(selinuxOpt.GetLevel())
|
||||
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)
|
||||
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 {
|
||||
|
@ -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"
|
||||
@ -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)
|
||||
}
|
||||
}()
|
||||
|
||||
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)
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/containerd/containerd/plugin"
|
||||
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/sys/unix"
|
||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||
@ -38,7 +39,7 @@ import (
|
||||
)
|
||||
|
||||
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.
|
||||
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
|
||||
specOpts := []oci.SpecOpts{
|
||||
@ -117,11 +118,15 @@ func (c *criService) sandboxContainerSpec(id string, config *runtime.PodSandboxC
|
||||
},
|
||||
}))
|
||||
|
||||
selinuxOpt := securityContext.GetSelinuxOptions()
|
||||
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt)
|
||||
processLabel, mountLabel, err := initLabelsFromOpt(securityContext.GetSelinuxOptions())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions())
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
_ = label.ReleaseLabel(processLabel)
|
||||
}
|
||||
}()
|
||||
|
||||
supplementalGroups := securityContext.GetSupplementalGroups()
|
||||
specOpts = append(specOpts,
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
|
||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
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.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
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/cri/pkg/store/label"
|
||||
cni "github.com/containerd/go-cni"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -99,12 +100,13 @@ type criService struct {
|
||||
// NewCRIService returns a new instance of CRIService
|
||||
func NewCRIService(config criconfig.Config, client *containerd.Client) (CRIService, error) {
|
||||
var err error
|
||||
labels := label.NewStore()
|
||||
c := &criService{
|
||||
config: config,
|
||||
client: client,
|
||||
os: osinterface.RealOS{},
|
||||
sandboxStore: sandboxstore.NewStore(),
|
||||
containerStore: containerstore.NewStore(),
|
||||
sandboxStore: sandboxstore.NewStore(labels),
|
||||
containerStore: containerstore.NewStore(labels),
|
||||
imageStore: imagestore.NewStore(client),
|
||||
snapshotStore: snapshotstore.NewStore(),
|
||||
sandboxNameIndex: registrar.NewRegistrar(),
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
servertesting "github.com/containerd/cri/pkg/server/testing"
|
||||
containerstore "github.com/containerd/cri/pkg/store/container"
|
||||
imagestore "github.com/containerd/cri/pkg/store/image"
|
||||
"github.com/containerd/cri/pkg/store/label"
|
||||
sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
|
||||
snapshotstore "github.com/containerd/cri/pkg/store/snapshot"
|
||||
)
|
||||
@ -39,6 +40,7 @@ const (
|
||||
|
||||
// newTestCRIService creates a fake criService for test.
|
||||
func newTestCRIService() *criService {
|
||||
labels := label.NewStore()
|
||||
return &criService{
|
||||
config: criconfig.Config{
|
||||
RootDir: testRootDir,
|
||||
@ -49,11 +51,11 @@ func newTestCRIService() *criService {
|
||||
},
|
||||
imageFSPath: testImageFSPath,
|
||||
os: ostesting.NewFakeOS(),
|
||||
sandboxStore: sandboxstore.NewStore(),
|
||||
sandboxStore: sandboxstore.NewStore(labels),
|
||||
imageStore: imagestore.NewStore(nil),
|
||||
snapshotStore: snapshotstore.NewStore(),
|
||||
sandboxNameIndex: registrar.NewRegistrar(),
|
||||
containerStore: containerstore.NewStore(),
|
||||
containerStore: containerstore.NewStore(labels),
|
||||
containerNameIndex: registrar.NewRegistrar(),
|
||||
netPlugin: servertesting.NewFakeCNIPlugin(),
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/cri/pkg/store/label"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||
|
||||
@ -101,13 +102,15 @@ type Store struct {
|
||||
lock sync.RWMutex
|
||||
containers map[string]Container
|
||||
idIndex *truncindex.TruncIndex
|
||||
labels *label.Store
|
||||
}
|
||||
|
||||
// NewStore creates a container store.
|
||||
func NewStore() *Store {
|
||||
func NewStore(labels *label.Store) *Store {
|
||||
return &Store{
|
||||
containers: make(map[string]Container),
|
||||
idIndex: truncindex.NewTruncIndex([]string{}),
|
||||
labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,6 +122,9 @@ func (s *Store) Add(c Container) error {
|
||||
if _, ok := s.containers[c.ID]; ok {
|
||||
return store.ErrAlreadyExist
|
||||
}
|
||||
if err := s.labels.Reserve(c.ProcessLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.idIndex.Add(c.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -165,6 +171,7 @@ func (s *Store) Delete(id string) {
|
||||
// So we need to return if there are error.
|
||||
return
|
||||
}
|
||||
s.labels.Release(s.containers[id].ProcessLabel)
|
||||
s.idIndex.Delete(id) // nolint: errcheck
|
||||
delete(s.containers, id)
|
||||
}
|
||||
|
@ -17,9 +17,12 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/cri/pkg/store/label"
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||
|
||||
@ -39,9 +42,10 @@ func TestContainerStore(t *testing.T) {
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
ImageRef: "TestImage-1",
|
||||
StopSignal: "SIGTERM",
|
||||
LogPath: "/test/log/path/1",
|
||||
ImageRef: "TestImage-1",
|
||||
StopSignal: "SIGTERM",
|
||||
LogPath: "/test/log/path/1",
|
||||
ProcessLabel: "junk:junk:junk:c1,c2",
|
||||
},
|
||||
"2abcd": {
|
||||
ID: "2abcd",
|
||||
@ -53,9 +57,10 @@ func TestContainerStore(t *testing.T) {
|
||||
Attempt: 2,
|
||||
},
|
||||
},
|
||||
StopSignal: "SIGTERM",
|
||||
ImageRef: "TestImage-2",
|
||||
LogPath: "/test/log/path/2",
|
||||
StopSignal: "SIGTERM",
|
||||
ImageRef: "TestImage-2",
|
||||
LogPath: "/test/log/path/2",
|
||||
ProcessLabel: "junk:junk:junk:c1,c2",
|
||||
},
|
||||
"4a333": {
|
||||
ID: "4a333",
|
||||
@ -67,9 +72,10 @@ func TestContainerStore(t *testing.T) {
|
||||
Attempt: 3,
|
||||
},
|
||||
},
|
||||
StopSignal: "SIGTERM",
|
||||
ImageRef: "TestImage-3",
|
||||
LogPath: "/test/log/path/3",
|
||||
StopSignal: "SIGTERM",
|
||||
ImageRef: "TestImage-3",
|
||||
LogPath: "/test/log/path/3",
|
||||
ProcessLabel: "junk:junk:junk:c1,c3",
|
||||
},
|
||||
"4abcd": {
|
||||
ID: "4abcd",
|
||||
@ -81,8 +87,9 @@ func TestContainerStore(t *testing.T) {
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
StopSignal: "SIGTERM",
|
||||
ImageRef: "TestImage-4abcd",
|
||||
StopSignal: "SIGTERM",
|
||||
ImageRef: "TestImage-4abcd",
|
||||
ProcessLabel: "junk:junk:junk:c1,c4",
|
||||
},
|
||||
}
|
||||
statuses := map[string]Status{
|
||||
@ -136,7 +143,14 @@ func TestContainerStore(t *testing.T) {
|
||||
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")
|
||||
for _, c := range containers {
|
||||
@ -155,6 +169,15 @@ func TestContainerStore(t *testing.T) {
|
||||
cs := s.List()
|
||||
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)
|
||||
for testID, v := range containers {
|
||||
truncID := genTruncIndex(testID)
|
||||
@ -173,6 +196,15 @@ func TestContainerStore(t *testing.T) {
|
||||
assert.Equal(Container{}, c)
|
||||
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) {
|
||||
|
@ -61,6 +61,8 @@ type Metadata struct {
|
||||
// StopSignal is the system call signal that will be sent to the container to exit.
|
||||
// TODO(random-liu): Add integration test for stop signal.
|
||||
StopSignal string
|
||||
// ProcessLabel is the SELinux process label for the container
|
||||
ProcessLabel string
|
||||
}
|
||||
|
||||
// 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
|
||||
// CNIresult resulting configuration for attached network namespace interfaces
|
||||
CNIResult *cni.CNIResult
|
||||
// ProcessLabel is the SELinux process label for the container
|
||||
ProcessLabel string
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Metadata into bytes in json format.
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/cri/pkg/store/label"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
|
||||
"github.com/containerd/cri/pkg/netns"
|
||||
@ -62,13 +63,15 @@ type Store struct {
|
||||
lock sync.RWMutex
|
||||
sandboxes map[string]Sandbox
|
||||
idIndex *truncindex.TruncIndex
|
||||
labels *label.Store
|
||||
}
|
||||
|
||||
// NewStore creates a sandbox store.
|
||||
func NewStore() *Store {
|
||||
func NewStore(labels *label.Store) *Store {
|
||||
return &Store{
|
||||
sandboxes: make(map[string]Sandbox),
|
||||
idIndex: truncindex.NewTruncIndex([]string{}),
|
||||
labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +82,9 @@ func (s *Store) Add(sb Sandbox) error {
|
||||
if _, ok := s.sandboxes[sb.ID]; ok {
|
||||
return store.ErrAlreadyExist
|
||||
}
|
||||
if err := s.labels.Reserve(sb.ProcessLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.idIndex.Add(sb.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -125,6 +131,7 @@ func (s *Store) Delete(id string) {
|
||||
// So we need to return if there are error.
|
||||
return
|
||||
}
|
||||
s.labels.Release(s.sandboxes[id].ProcessLabel)
|
||||
s.idIndex.Delete(id) // nolint: errcheck
|
||||
delete(s.sandboxes, id)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package sandbox
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/cri/pkg/store/label"
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||
|
||||
@ -109,7 +110,7 @@ func TestSandboxStore(t *testing.T) {
|
||||
Status{State: StateUnknown},
|
||||
)
|
||||
assert := assertlib.New(t)
|
||||
s := NewStore()
|
||||
s := NewStore(label.NewStore())
|
||||
|
||||
t.Logf("should be able to add sandbox")
|
||||
for _, sb := range sandboxes {
|
||||
|
Loading…
Reference in New Issue
Block a user