diff --git a/pkg/kubelet/dockershim/BUILD b/pkg/kubelet/dockershim/BUILD index 292b5d18980..915b6b8485d 100644 --- a/pkg/kubelet/dockershim/BUILD +++ b/pkg/kubelet/dockershim/BUILD @@ -82,6 +82,7 @@ go_test( "docker_legacy_test.go", "docker_sandbox_test.go", "docker_service_test.go", + "helpers_linux_test.go", "helpers_test.go", "naming_test.go", "security_context_test.go", diff --git a/pkg/kubelet/dockershim/helpers.go b/pkg/kubelet/dockershim/helpers.go index 8bd9f0562da..22a976ed157 100644 --- a/pkg/kubelet/dockershim/helpers.go +++ b/pkg/kubelet/dockershim/helpers.go @@ -17,11 +17,7 @@ limitations under the License. package dockershim import ( - "bytes" - "crypto/md5" - "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "regexp" @@ -34,7 +30,6 @@ import ( dockernat "github.com/docker/go-connections/nat" "github.com/golang/glog" - "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/credentialprovider" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/types" @@ -202,59 +197,6 @@ func makePortsAndBindings(pm []*runtimeapi.PortMapping) (map[dockernat.Port]stru return exposedPorts, portBindings } -func getSeccompDockerOpts(annotations map[string]string, ctrName, profileRoot string) ([]dockerOpt, error) { - profile, profileOK := annotations[v1.SeccompContainerAnnotationKeyPrefix+ctrName] - if !profileOK { - // try the pod profile - profile, profileOK = annotations[v1.SeccompPodAnnotationKey] - if !profileOK { - // return early the default - return defaultSeccompOpt, nil - } - } - - if profile == "unconfined" { - // return early the default - return defaultSeccompOpt, nil - } - - if profile == "docker/default" { - // return nil so docker will load the default seccomp profile - return nil, nil - } - - if !strings.HasPrefix(profile, "localhost/") { - return nil, fmt.Errorf("unknown seccomp profile option: %s", profile) - } - - name := strings.TrimPrefix(profile, "localhost/") // by pod annotation validation, name is a valid subpath - fname := filepath.Join(profileRoot, filepath.FromSlash(name)) - file, err := ioutil.ReadFile(fname) - if err != nil { - return nil, fmt.Errorf("cannot load seccomp profile %q: %v", name, err) - } - - b := bytes.NewBuffer(nil) - if err := json.Compact(b, file); err != nil { - return nil, err - } - // Rather than the full profile, just put the filename & md5sum in the event log. - msg := fmt.Sprintf("%s(md5:%x)", name, md5.Sum(file)) - - return []dockerOpt{{"seccomp", b.String(), msg}}, nil -} - -// getSeccompSecurityOpts gets container seccomp options from container and sandbox -// config, currently from sandbox annotations. -// It is an experimental feature and may be promoted to official runtime api in the future. -func getSeccompSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, seccompProfileRoot string, separator rune) ([]string, error) { - seccompOpts, err := getSeccompDockerOpts(sandboxConfig.GetAnnotations(), containerName, seccompProfileRoot) - if err != nil { - return nil, err - } - return fmtDockerOpts(seccompOpts, separator), nil -} - // getApparmorSecurityOpts gets apparmor options from container config. func getApparmorSecurityOpts(sc *runtimeapi.LinuxContainerSecurityContext, separator rune) ([]string, error) { if sc == nil || sc.ApparmorProfile == "" { diff --git a/pkg/kubelet/dockershim/helpers_linux.go b/pkg/kubelet/dockershim/helpers_linux.go index 34a862f153e..fd4f87fe4dc 100644 --- a/pkg/kubelet/dockershim/helpers_linux.go +++ b/pkg/kubelet/dockershim/helpers_linux.go @@ -19,11 +19,18 @@ limitations under the License. package dockershim import ( + "bytes" + "crypto/md5" + "encoding/json" "fmt" + "io/ioutil" + "path/filepath" + "strings" "github.com/blang/semver" dockertypes "github.com/docker/engine-api/types" dockercontainer "github.com/docker/engine-api/types/container" + "k8s.io/api/core/v1" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" ) @@ -41,6 +48,59 @@ func (ds *dockerService) getSecurityOpts(containerName string, sandboxConfig *ru return seccompSecurityOpts, nil } +func getSeccompDockerOpts(annotations map[string]string, ctrName, profileRoot string) ([]dockerOpt, error) { + profile, profileOK := annotations[v1.SeccompContainerAnnotationKeyPrefix+ctrName] + if !profileOK { + // try the pod profile + profile, profileOK = annotations[v1.SeccompPodAnnotationKey] + if !profileOK { + // return early the default + return defaultSeccompOpt, nil + } + } + + if profile == "unconfined" { + // return early the default + return defaultSeccompOpt, nil + } + + if profile == "docker/default" { + // return nil so docker will load the default seccomp profile + return nil, nil + } + + if !strings.HasPrefix(profile, "localhost/") { + return nil, fmt.Errorf("unknown seccomp profile option: %s", profile) + } + + name := strings.TrimPrefix(profile, "localhost/") // by pod annotation validation, name is a valid subpath + fname := filepath.Join(profileRoot, filepath.FromSlash(name)) + file, err := ioutil.ReadFile(fname) + if err != nil { + return nil, fmt.Errorf("cannot load seccomp profile %q: %v", name, err) + } + + b := bytes.NewBuffer(nil) + if err := json.Compact(b, file); err != nil { + return nil, err + } + // Rather than the full profile, just put the filename & md5sum in the event log. + msg := fmt.Sprintf("%s(md5:%x)", name, md5.Sum(file)) + + return []dockerOpt{{"seccomp", b.String(), msg}}, nil +} + +// getSeccompSecurityOpts gets container seccomp options from container and sandbox +// config, currently from sandbox annotations. +// It is an experimental feature and may be promoted to official runtime api in the future. +func getSeccompSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, seccompProfileRoot string, separator rune) ([]string, error) { + seccompOpts, err := getSeccompDockerOpts(sandboxConfig.GetAnnotations(), containerName, seccompProfileRoot) + if err != nil { + return nil, err + } + return fmtDockerOpts(seccompOpts, separator), nil +} + func (ds *dockerService) updateCreateConfig( createConfig *dockertypes.ContainerCreateConfig, config *runtimeapi.ContainerConfig, diff --git a/pkg/kubelet/dockershim/helpers_linux_test.go b/pkg/kubelet/dockershim/helpers_linux_test.go new file mode 100644 index 00000000000..899db1c9242 --- /dev/null +++ b/pkg/kubelet/dockershim/helpers_linux_test.go @@ -0,0 +1,123 @@ +// +build linux + +/* +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 dockershim + +import ( + "fmt" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/api/core/v1" + + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" +) + +func TestGetSeccompSecurityOpts(t *testing.T) { + containerName := "bar" + makeConfig := func(annotations map[string]string) *runtimeapi.PodSandboxConfig { + return makeSandboxConfigWithLabelsAndAnnotations("pod", "ns", "1234", 1, nil, annotations) + } + + tests := []struct { + msg string + config *runtimeapi.PodSandboxConfig + expectedOpts []string + }{{ + msg: "No security annotations", + config: makeConfig(nil), + expectedOpts: []string{"seccomp=unconfined"}, + }, { + msg: "Seccomp unconfined", + config: makeConfig(map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + containerName: "unconfined", + }), + expectedOpts: []string{"seccomp=unconfined"}, + }, { + msg: "Seccomp default", + config: makeConfig(map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + containerName: "docker/default", + }), + expectedOpts: nil, + }, { + msg: "Seccomp pod default", + config: makeConfig(map[string]string{ + v1.SeccompPodAnnotationKey: "docker/default", + }), + expectedOpts: nil, + }} + + for i, test := range tests { + opts, err := getSeccompSecurityOpts(containerName, test.config, "test/seccomp/profile/root", '=') + assert.NoError(t, err, "TestCase[%d]: %s", i, test.msg) + assert.Len(t, opts, len(test.expectedOpts), "TestCase[%d]: %s", i, test.msg) + for _, opt := range test.expectedOpts { + assert.Contains(t, opts, opt, "TestCase[%d]: %s", i, test.msg) + } + } +} + +func TestLoadSeccompLocalhostProfiles(t *testing.T) { + containerName := "bar" + makeConfig := func(annotations map[string]string) *runtimeapi.PodSandboxConfig { + return makeSandboxConfigWithLabelsAndAnnotations("pod", "ns", "1234", 1, nil, annotations) + } + + tests := []struct { + msg string + config *runtimeapi.PodSandboxConfig + expectedOpts []string + expectErr bool + }{{ + msg: "Seccomp localhost/test profile", + config: makeConfig(map[string]string{ + v1.SeccompPodAnnotationKey: "localhost/test", + }), + expectedOpts: []string{`seccomp={"foo":"bar"}`}, + expectErr: false, + }, { + msg: "Seccomp localhost/sub/subtest profile", + config: makeConfig(map[string]string{ + v1.SeccompPodAnnotationKey: "localhost/sub/subtest", + }), + expectedOpts: []string{`seccomp={"abc":"def"}`}, + expectErr: false, + }, { + msg: "Seccomp non-existent", + config: makeConfig(map[string]string{ + v1.SeccompPodAnnotationKey: "localhost/non-existent", + }), + expectedOpts: nil, + expectErr: true, + }} + + profileRoot := path.Join("fixtures", "seccomp") + for i, test := range tests { + opts, err := getSeccompSecurityOpts(containerName, test.config, profileRoot, '=') + if test.expectErr { + assert.Error(t, err, fmt.Sprintf("TestCase[%d]: %s", i, test.msg)) + continue + } + assert.NoError(t, err, "TestCase[%d]: %s", i, test.msg) + assert.Len(t, opts, len(test.expectedOpts), "TestCase[%d]: %s", i, test.msg) + for _, opt := range test.expectedOpts { + assert.Contains(t, opts, opt, "TestCase[%d]: %s", i, test.msg) + } + } +} diff --git a/pkg/kubelet/dockershim/helpers_test.go b/pkg/kubelet/dockershim/helpers_test.go index 0e8e4f9f44d..4e0615834e5 100644 --- a/pkg/kubelet/dockershim/helpers_test.go +++ b/pkg/kubelet/dockershim/helpers_test.go @@ -21,7 +21,6 @@ import ( "fmt" "io/ioutil" "os" - "path" "path/filepath" "testing" @@ -31,8 +30,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/api/core/v1" - runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/security/apparmor" @@ -50,99 +47,6 @@ func TestLabelsAndAnnotationsRoundTrip(t *testing.T) { assert.Equal(t, expectedAnnotations, actualAnnotations) } -func TestGetSeccompSecurityOpts(t *testing.T) { - containerName := "bar" - makeConfig := func(annotations map[string]string) *runtimeapi.PodSandboxConfig { - return makeSandboxConfigWithLabelsAndAnnotations("pod", "ns", "1234", 1, nil, annotations) - } - - tests := []struct { - msg string - config *runtimeapi.PodSandboxConfig - expectedOpts []string - }{{ - msg: "No security annotations", - config: makeConfig(nil), - expectedOpts: []string{"seccomp=unconfined"}, - }, { - msg: "Seccomp unconfined", - config: makeConfig(map[string]string{ - v1.SeccompContainerAnnotationKeyPrefix + containerName: "unconfined", - }), - expectedOpts: []string{"seccomp=unconfined"}, - }, { - msg: "Seccomp default", - config: makeConfig(map[string]string{ - v1.SeccompContainerAnnotationKeyPrefix + containerName: "docker/default", - }), - expectedOpts: nil, - }, { - msg: "Seccomp pod default", - config: makeConfig(map[string]string{ - v1.SeccompPodAnnotationKey: "docker/default", - }), - expectedOpts: nil, - }} - - for i, test := range tests { - opts, err := getSeccompSecurityOpts(containerName, test.config, "test/seccomp/profile/root", '=') - assert.NoError(t, err, "TestCase[%d]: %s", i, test.msg) - assert.Len(t, opts, len(test.expectedOpts), "TestCase[%d]: %s", i, test.msg) - for _, opt := range test.expectedOpts { - assert.Contains(t, opts, opt, "TestCase[%d]: %s", i, test.msg) - } - } -} - -func TestLoadSeccompLocalhostProfiles(t *testing.T) { - containerName := "bar" - makeConfig := func(annotations map[string]string) *runtimeapi.PodSandboxConfig { - return makeSandboxConfigWithLabelsAndAnnotations("pod", "ns", "1234", 1, nil, annotations) - } - - tests := []struct { - msg string - config *runtimeapi.PodSandboxConfig - expectedOpts []string - expectErr bool - }{{ - msg: "Seccomp localhost/test profile", - config: makeConfig(map[string]string{ - v1.SeccompPodAnnotationKey: "localhost/test", - }), - expectedOpts: []string{`seccomp={"foo":"bar"}`}, - expectErr: false, - }, { - msg: "Seccomp localhost/sub/subtest profile", - config: makeConfig(map[string]string{ - v1.SeccompPodAnnotationKey: "localhost/sub/subtest", - }), - expectedOpts: []string{`seccomp={"abc":"def"}`}, - expectErr: false, - }, { - msg: "Seccomp non-existent", - config: makeConfig(map[string]string{ - v1.SeccompPodAnnotationKey: "localhost/non-existent", - }), - expectedOpts: nil, - expectErr: true, - }} - - profileRoot := path.Join("fixtures", "seccomp") - for i, test := range tests { - opts, err := getSeccompSecurityOpts(containerName, test.config, profileRoot, '=') - if test.expectErr { - assert.Error(t, err, fmt.Sprintf("TestCase[%d]: %s", i, test.msg)) - continue - } - assert.NoError(t, err, "TestCase[%d]: %s", i, test.msg) - assert.Len(t, opts, len(test.expectedOpts), "TestCase[%d]: %s", i, test.msg) - for _, opt := range test.expectedOpts { - assert.Contains(t, opts, opt, "TestCase[%d]: %s", i, test.msg) - } - } -} - // TestGetApparmorSecurityOpts tests the logic of generating container apparmor options from sandbox annotations. func TestGetApparmorSecurityOpts(t *testing.T) { makeConfig := func(profile string) *runtimeapi.LinuxContainerSecurityContext {