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)
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

View File

@ -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

View File

@ -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"}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}
})
}

View File

@ -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 {

View File

@ -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)

View File

@ -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,

View File

@ -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
}

View File

@ -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(),

View File

@ -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(),
}

View File

@ -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)
}

View File

@ -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) {

View File

@ -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
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
// 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.

View File

@ -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)
}

View File

@ -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 {