diff --git a/pkg/server/container_create.go b/pkg/server/container_create.go index 1f737a355..564c6b42f 100644 --- a/pkg/server/container_create.go +++ b/pkg/server/container_create.go @@ -28,7 +28,9 @@ import ( "github.com/opencontainers/runc/libcontainer/devices" runtimespec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/runtime-tools/validate" "github.com/opencontainers/selinux/go-selinux/label" + "github.com/syndtr/gocapability/capability" "golang.org/x/net/context" "golang.org/x/sys/unix" "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" @@ -526,21 +528,58 @@ func setOCILinuxResource(g *generate.Generator, resources *runtime.LinuxContaine g.SetProcessOOMScoreAdj(int(resources.GetOomScoreAdj())) } +// getOCICapabilitiesList returns a list of all available capabilities. +func getOCICapabilitiesList() []string { + var caps []string + for _, cap := range capability.List() { + if cap > validate.LastCap() { + continue + } + caps = append(caps, "CAP_"+strings.ToUpper(cap.String())) + } + return caps +} + // setOCICapabilities adds/drops process capabilities. func setOCICapabilities(g *generate.Generator, capabilities *runtime.Capability) error { if capabilities == nil { return nil } - // Capabilities in CRI doesn't have `CAP_` prefix, so add it. + // Add/drop all capabilities if "all" is specified, so that + // following individual add/drop could still work. E.g. + // AddCapabilities: []string{"ALL"}, DropCapabilities: []string{"CHOWN"} + // will be all capabilities without `CAP_CHOWN`. + if util.InStringSlice(capabilities.GetAddCapabilities(), "ALL") { + for _, c := range getOCICapabilitiesList() { + if err := g.AddProcessCapability(c); err != nil { + return err + } + } + } + if util.InStringSlice(capabilities.GetDropCapabilities(), "ALL") { + for _, c := range getOCICapabilitiesList() { + if err := g.DropProcessCapability(c); err != nil { + return err + } + } + } + for _, c := range capabilities.GetAddCapabilities() { - if err := g.AddProcessCapability("CAP_" + c); err != nil { + if strings.ToUpper(c) == "ALL" { + continue + } + // Capabilities in CRI doesn't have `CAP_` prefix, so add it. + if err := g.AddProcessCapability("CAP_" + strings.ToUpper(c)); err != nil { return err } } for _, c := range capabilities.GetDropCapabilities() { - if err := g.DropProcessCapability("CAP_" + c); err != nil { + if strings.ToUpper(c) == "ALL" { + continue + } + if err := g.DropProcessCapability("CAP_" + strings.ToUpper(c)); err != nil { return err } } diff --git a/pkg/server/container_create_test.go b/pkg/server/container_create_test.go index 4ebf79e16..32dd582f9 100644 --- a/pkg/server/container_create_test.go +++ b/pkg/server/container_create_test.go @@ -25,6 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" + + "github.com/kubernetes-incubator/cri-containerd/pkg/util" ) func checkMount(t *testing.T, mounts []runtimespec.Mount, src, dest, typ string, @@ -87,10 +89,6 @@ func getCreateContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandbox OomScoreAdj: 500, }, SecurityContext: &runtime.LinuxContainerSecurityContext{ - Capabilities: &runtime.Capability{ - AddCapabilities: []string{"SYS_ADMIN"}, - DropCapabilities: []string{"CHOWN"}, - }, SupplementalGroups: []int64{1111, 2222}, NoNewPrivs: true, }, @@ -134,18 +132,6 @@ func getCreateContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandbox assert.EqualValues(t, *spec.Linux.Resources.Memory.Limit, 400) assert.EqualValues(t, *spec.Process.OOMScoreAdj, 500) - t.Logf("Check capabilities") - assert.Contains(t, spec.Process.Capabilities.Bounding, "CAP_SYS_ADMIN") - assert.Contains(t, spec.Process.Capabilities.Effective, "CAP_SYS_ADMIN") - assert.Contains(t, spec.Process.Capabilities.Inheritable, "CAP_SYS_ADMIN") - assert.Contains(t, spec.Process.Capabilities.Permitted, "CAP_SYS_ADMIN") - assert.Contains(t, spec.Process.Capabilities.Ambient, "CAP_SYS_ADMIN") - assert.NotContains(t, spec.Process.Capabilities.Bounding, "CAP_CHOWN") - assert.NotContains(t, spec.Process.Capabilities.Effective, "CAP_CHOWN") - assert.NotContains(t, spec.Process.Capabilities.Inheritable, "CAP_CHOWN") - assert.NotContains(t, spec.Process.Capabilities.Permitted, "CAP_CHOWN") - assert.NotContains(t, spec.Process.Capabilities.Ambient, "CAP_CHOWN") - t.Logf("Check supplemental groups") assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111)) assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222)) @@ -183,6 +169,74 @@ func TestGeneralContainerSpec(t *testing.T) { specCheck(t, testID, testPid, spec) } +func TestContainerCapabilities(t *testing.T) { + testID := "test-id" + testPid := uint32(1234) + config, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData() + c := newTestCRIContainerdService() + for desc, test := range map[string]struct { + capability *runtime.Capability + includes []string + excludes []string + }{ + "should be able to add/drop capabilities": { + capability: &runtime.Capability{ + AddCapabilities: []string{"SYS_ADMIN"}, + DropCapabilities: []string{"CHOWN"}, + }, + includes: []string{"CAP_SYS_ADMIN"}, + excludes: []string{"CAP_CHOWN"}, + }, + "should be able to add all capabilities": { + capability: &runtime.Capability{ + AddCapabilities: []string{"ALL"}, + }, + includes: getOCICapabilitiesList(), + }, + "should be able to drop all capabilities": { + capability: &runtime.Capability{ + DropCapabilities: []string{"ALL"}, + }, + excludes: getOCICapabilitiesList(), + }, + "should be able to drop capabilities with add all": { + capability: &runtime.Capability{ + AddCapabilities: []string{"ALL"}, + DropCapabilities: []string{"CHOWN"}, + }, + includes: util.SubstractStringSlice(getOCICapabilitiesList(), "CAP_CHOWN"), + excludes: []string{"CAP_CHOWN"}, + }, + "should be able to add capabilities with drop all": { + capability: &runtime.Capability{ + AddCapabilities: []string{"SYS_ADMIN"}, + DropCapabilities: []string{"ALL"}, + }, + includes: []string{"CAP_SYS_ADMIN"}, + excludes: util.SubstractStringSlice(getOCICapabilitiesList(), "CAP_SYS_ADMIN"), + }, + } { + t.Logf("TestCase %q", desc) + config.Linux.SecurityContext.Capabilities = test.capability + spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, nil) + assert.NoError(t, err) + specCheck(t, testID, testPid, spec) + t.Log(spec.Process.Capabilities.Bounding) + for _, include := range test.includes { + assert.Contains(t, spec.Process.Capabilities.Bounding, include) + assert.Contains(t, spec.Process.Capabilities.Effective, include) + assert.Contains(t, spec.Process.Capabilities.Inheritable, include) + assert.Contains(t, spec.Process.Capabilities.Permitted, include) + } + for _, exclude := range test.excludes { + assert.NotContains(t, spec.Process.Capabilities.Bounding, exclude) + assert.NotContains(t, spec.Process.Capabilities.Effective, exclude) + assert.NotContains(t, spec.Process.Capabilities.Inheritable, exclude) + assert.NotContains(t, spec.Process.Capabilities.Permitted, exclude) + } + } +} + func TestContainerSpecTty(t *testing.T) { testID := "test-id" testPid := uint32(1234) diff --git a/pkg/util/strings.go b/pkg/util/strings.go new file mode 100644 index 000000000..5791e449c --- /dev/null +++ b/pkg/util/strings.go @@ -0,0 +1,43 @@ +/* +Copyright 2017 The Kubernetes 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 util + +import "strings" + +// InStringSlice checks whether a string is inside a string slice. +// Comparison is case insensitive. +func InStringSlice(ss []string, str string) bool { + for _, s := range ss { + if strings.ToLower(s) == strings.ToLower(str) { + return true + } + } + return false +} + +// SubstractStringSlice substracts string from string slice. +// Comparison is case insensitive. +func SubstractStringSlice(ss []string, str string) []string { + var res []string + for _, s := range ss { + if strings.ToLower(s) == strings.ToLower(str) { + continue + } + res = append(res, s) + } + return res +} diff --git a/pkg/util/strings_test.go b/pkg/util/strings_test.go new file mode 100644 index 000000000..625427076 --- /dev/null +++ b/pkg/util/strings_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2017 The Kubernetes 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 util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInStringSlice(t *testing.T) { + ss := []string{"ABC", "def", "ghi"} + + assert.True(t, InStringSlice(ss, "ABC")) + assert.True(t, InStringSlice(ss, "abc")) + assert.True(t, InStringSlice(ss, "def")) + assert.True(t, InStringSlice(ss, "DEF")) + assert.False(t, InStringSlice(ss, "hij")) + assert.False(t, InStringSlice(ss, "HIJ")) + assert.False(t, InStringSlice(nil, "HIJ")) +} + +func TestSubstractStringSlice(t *testing.T) { + ss := []string{"ABC", "def", "ghi"} + + assert.Equal(t, []string{"def", "ghi"}, SubstractStringSlice(ss, "abc")) + assert.Equal(t, []string{"def", "ghi"}, SubstractStringSlice(ss, "ABC")) + assert.Equal(t, []string{"ABC", "ghi"}, SubstractStringSlice(ss, "def")) + assert.Equal(t, []string{"ABC", "ghi"}, SubstractStringSlice(ss, "DEF")) + assert.Equal(t, []string{"ABC", "def", "ghi"}, SubstractStringSlice(ss, "hij")) + assert.Equal(t, []string{"ABC", "def", "ghi"}, SubstractStringSlice(ss, "HIJ")) + assert.Empty(t, SubstractStringSlice(nil, "hij")) + assert.Empty(t, SubstractStringSlice([]string{}, "hij")) +}