Check seccomp enable and add unit test for seccomp/apparmor.

Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
Lantao Liu
2017-09-24 06:27:47 +00:00
parent c83af81403
commit 21233b22be
12 changed files with 1953 additions and 85 deletions

View File

@@ -32,6 +32,7 @@ import (
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runcapparmor "github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/devices"
runcseccomp "github.com/opencontainers/runc/libcontainer/seccomp"
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/runtime-tools/validate"
@@ -58,12 +59,8 @@ const (
appArmorDefaultProfileName = "cri-containerd.apparmor.d"
// unconfinedProfile is a string indicating one should run a pod/containerd without a security profile
unconfinedProfile = "unconfined"
// seccompDefaultSandboxProfile is the default seccomp profile for pods.
seccompDefaultSandboxProfile = dockerDefault
// seccompDefaultContainerProfile is the default seccomp profile for containers.
seccompDefaultContainerProfile = dockerDefault
// seccompEnabled is a flag for globally enabling/disabling seccomp profiles for containers.
seccompEnabled = true // TODO (mikebrow): make these seccomp defaults configurable
// seccompDefaultProfile is the default seccomp profile.
seccompDefaultProfile = dockerDefault
)
func init() {
@@ -201,59 +198,27 @@ func (c *criContainerdService) CreateContainer(ctx context.Context, r *runtime.C
if username := securityContext.GetRunAsUsername(); username != "" {
specOpts = append(specOpts, containerd.WithUsername(username))
}
// Set apparmor profile, (privileged or not) if apparmor is enabled
appArmorProf := securityContext.GetApparmorProfile()
if !runcapparmor.IsEnabled() {
// Should fail loudly if user try to specify apparmor profile
// but we don't support it.
if appArmorProf != "" {
return nil, fmt.Errorf("apparmor is not supported")
}
} else {
switch appArmorProf {
case runtimeDefault:
// TODO (mikebrow): delete created apparmor default profile
specOpts = append(specOpts, apparmor.WithDefaultProfile(appArmorDefaultProfileName))
// TODO(random-liu): Should support "unconfined" after kubernetes#52395 lands.
case "":
// Based on kubernetes#51746, default apparmor profile should be applied
// for non-privileged container when apparmor is not specified.
if !securityContext.GetPrivileged() {
specOpts = append(specOpts, apparmor.WithDefaultProfile(appArmorDefaultProfileName))
}
default:
// Require and Trim default profile name prefix
if !strings.HasPrefix(appArmorProf, profileNamePrefix) {
return nil, fmt.Errorf("invalid apparmor profile %q", appArmorProf)
}
specOpts = append(specOpts, apparmor.WithProfile(strings.TrimPrefix(appArmorProf, profileNamePrefix)))
}
apparmorSpecOpts, err := generateApparmorSpecOpts(
securityContext.GetApparmorProfile(),
securityContext.GetPrivileged(),
runcapparmor.IsEnabled())
if err != nil {
return nil, fmt.Errorf("failed to generate apparmor spec opts: %v", err)
}
if apparmorSpecOpts != nil {
specOpts = append(specOpts, apparmorSpecOpts)
}
// Set seccomp profile
seccompProf := config.GetLinux().GetSecurityContext().GetSeccompProfilePath()
if seccompProf == runtimeDefault || seccompProf == dockerDefault {
// use correct default profile (Eg. if not configured otherwise, the default is docker/default for containers)
seccompProf = seccompDefaultContainerProfile
seccompSpecOpts, err := generateSeccompSpecOpts(
securityContext.GetSeccompProfilePath(),
securityContext.GetPrivileged(),
runcseccomp.IsEnabled())
if err != nil {
return nil, fmt.Errorf("failed to generate seccomp spec opts: %v", err)
}
// Unset the seccomp profile, if seccomp is not enabled, unconfined, unset, or the security context is privileged
if !seccompEnabled ||
seccompProf == unconfinedProfile ||
seccompProf == "" ||
config.GetLinux().GetSecurityContext().GetPrivileged() {
spec.Linux.Seccomp = nil
} else {
switch seccompProf {
case dockerDefault:
// Note: WithDefaultProfile specOpts must be added after capabilities
specOpts = append(specOpts, seccomp.WithDefaultProfile())
default:
// Require and Trim default profile name prefix
if !strings.HasPrefix(seccompProf, profileNamePrefix) {
return nil, fmt.Errorf("invalid seccomp profile %q", seccompProf)
}
specOpts = append(specOpts, seccomp.WithProfile(strings.TrimPrefix(seccompProf, profileNamePrefix)))
}
if seccompSpecOpts != nil {
specOpts = append(specOpts, seccompSpecOpts)
}
opts = append(opts,
@@ -768,6 +733,70 @@ func defaultRuntimeSpec() (*runtimespec.Spec, error) {
return spec, nil
}
// generateSeccompSpecOpts generates containerd SpecOpts for seccomp.
func generateSeccompSpecOpts(seccompProf string, privileged, seccompEnabled bool) (containerd.SpecOpts, error) {
// Set seccomp profile
if seccompProf == runtimeDefault || seccompProf == dockerDefault {
// use correct default profile (Eg. if not configured otherwise, the default is docker/default)
seccompProf = seccompDefaultProfile
}
if !seccompEnabled {
if seccompProf != "" && seccompProf != unconfinedProfile {
return nil, fmt.Errorf("seccomp is not supported")
}
return nil, nil
}
if privileged {
// Do not set seccomp profile when container is privileged
return nil, nil
}
switch seccompProf {
case "", unconfinedProfile:
// Do not set seccomp profile.
return nil, nil
case dockerDefault:
// Note: WithDefaultProfile specOpts must be added after capabilities
return seccomp.WithDefaultProfile(), nil
default:
// Require and Trim default profile name prefix
if !strings.HasPrefix(seccompProf, profileNamePrefix) {
return nil, fmt.Errorf("invalid seccomp profile %q", seccompProf)
}
return seccomp.WithProfile(strings.TrimPrefix(seccompProf, profileNamePrefix)), nil
}
}
// generateApparmorSpecOpts generates containerd SpecOpts for apparmor.
func generateApparmorSpecOpts(apparmorProf string, privileged, apparmorEnabled bool) (containerd.SpecOpts, error) {
if !apparmorEnabled {
// Should fail loudly if user try to specify apparmor profile
// but we don't support it.
if apparmorProf != "" {
return nil, fmt.Errorf("apparmor is not supported")
}
return nil, nil
}
switch apparmorProf {
case runtimeDefault:
// TODO (mikebrow): delete created apparmor default profile
return apparmor.WithDefaultProfile(appArmorDefaultProfileName), nil
// TODO(random-liu): Should support "unconfined" after kubernetes#52395 lands.
case "":
// Based on kubernetes#51746, default apparmor profile should be applied
// for non-privileged container when apparmor is not specified.
if privileged {
return nil, nil
}
return apparmor.WithDefaultProfile(appArmorDefaultProfileName), nil
default:
// Require and Trim default profile name prefix
if !strings.HasPrefix(apparmorProf, profileNamePrefix) {
return nil, fmt.Errorf("invalid apparmor profile %q", apparmorProf)
}
return apparmor.WithProfile(strings.TrimPrefix(apparmorProf, profileNamePrefix)), nil
}
}
// Ensure mount point on which path is mounted, is shared.
func ensureShared(path string, mountInfos []*mount.Info) error {
sourceMount, optionalOpts, err := getSourceMount(path, mountInfos)

View File

@@ -18,8 +18,12 @@ package server
import (
"path/filepath"
"reflect"
"testing"
"github.com/containerd/containerd"
"github.com/containerd/containerd/contrib/apparmor"
"github.com/containerd/containerd/contrib/seccomp"
"github.com/docker/docker/pkg/mount"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
@@ -714,3 +718,115 @@ func TestDefaultRuntimeSpec(t *testing.T) {
assert.NotEqual(t, "/run", mount.Destination)
}
}
func TestGenerateSeccompSpecOpts(t *testing.T) {
for desc, test := range map[string]struct {
profile string
privileged bool
disable bool
specOpts containerd.SpecOpts
expectErr bool
}{
"should return error if seccomp is specified when seccomp is not supported": {
profile: runtimeDefault,
disable: true,
expectErr: true,
},
"should not return error if seccomp is not specified when seccomp is not supported": {
profile: "",
disable: true,
},
"should not return error if seccomp is unconfined when seccomp is not supported": {
profile: unconfinedProfile,
disable: true,
},
"should not set seccomp when privileged is true": {
profile: seccompDefaultProfile,
privileged: true,
},
"should not set seccomp when seccomp is unconfined": {
profile: unconfinedProfile,
},
"should not set seccomp when seccomp is not specified": {
profile: "",
},
"should set default seccomp when seccomp is runtime/default": {
profile: runtimeDefault,
specOpts: seccomp.WithDefaultProfile(),
},
"should set default seccomp when seccomp is docker/default": {
profile: dockerDefault,
specOpts: seccomp.WithDefaultProfile(),
},
"should set specified profile when local profile is specified": {
profile: profileNamePrefix + "test-profile",
specOpts: seccomp.WithProfile("test-profile"),
},
"should return error if specified profile is invalid": {
profile: "test-profile",
expectErr: true,
},
} {
t.Logf("TestCase %q", desc)
specOpts, err := generateSeccompSpecOpts(test.profile, test.privileged, !test.disable)
assert.Equal(t,
reflect.ValueOf(test.specOpts).Pointer(),
reflect.ValueOf(specOpts).Pointer())
if test.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}
func TestGenerateApparmorSpecOpts(t *testing.T) {
for desc, test := range map[string]struct {
profile string
privileged bool
disable bool
specOpts containerd.SpecOpts
expectErr bool
}{
"should return error if apparmor is specified when apparmor is not supported": {
profile: runtimeDefault,
disable: true,
expectErr: true,
},
"should not return error if apparmor is not specified when apparmor is not supported": {
profile: "",
disable: true,
},
"should set default apparmor when apparmor is not specified": {
profile: "",
specOpts: apparmor.WithDefaultProfile(appArmorDefaultProfileName),
},
"should not apparmor when apparmor is not specified and privileged is true": {
profile: "",
privileged: true,
},
"should set default apparmor when apparmor is runtime/default": {
profile: runtimeDefault,
specOpts: apparmor.WithDefaultProfile(appArmorDefaultProfileName),
},
"should set specified profile when local profile is specified": {
profile: profileNamePrefix + "test-profile",
specOpts: apparmor.WithProfile("test-profile"),
},
"should return error if specified profile is invalid": {
profile: "test-profile",
expectErr: true,
},
} {
t.Logf("TestCase %q", desc)
specOpts, err := generateApparmorSpecOpts(test.profile, test.privileged, !test.disable)
assert.Equal(t,
reflect.ValueOf(test.specOpts).Pointer(),
reflect.ValueOf(specOpts).Pointer())
if test.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}

View File

@@ -22,11 +22,11 @@ import (
"strings"
"github.com/containerd/containerd"
"github.com/containerd/containerd/contrib/seccomp"
"github.com/containerd/typeurl"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/golang/glog"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runcseccomp "github.com/opencontainers/runc/libcontainer/seccomp"
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"golang.org/x/net/context"
@@ -76,8 +76,9 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
if err != nil {
return nil, fmt.Errorf("failed to get sandbox image %q: %v", c.config.SandboxImage, err)
}
securityContext := config.GetLinux().GetSecurityContext()
//Create Network Namespace if it is not in host network
hostNet := config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork()
hostNet := securityContext.GetNamespaceOptions().GetHostNetwork()
if !hostNet {
// If it is not in host network namespace then create a namespace and set the sandbox
// handle. NetNSPath in sandbox metadata and NetNS is non empty only for non host network
@@ -125,36 +126,19 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
glog.V(4).Infof("Sandbox container spec: %+v", spec)
var specOpts []containerd.SpecOpts
if uid := config.GetLinux().GetSecurityContext().GetRunAsUser(); uid != nil {
if uid := securityContext.GetRunAsUser(); uid != nil {
specOpts = append(specOpts, containerd.WithUserID(uint32(uid.GetValue())))
}
// Set seccomp profile
seccompProf := config.GetLinux().GetSecurityContext().GetSeccompProfilePath()
if seccompProf == runtimeDefault || seccompProf == dockerDefault {
// use correct default profile (Eg. if not configured otherwise, the default is docker/default for pods)
seccompProf = seccompDefaultSandboxProfile
seccompSpecOpts, err := generateSeccompSpecOpts(
securityContext.GetSeccompProfilePath(),
securityContext.GetPrivileged(),
runcseccomp.IsEnabled())
if err != nil {
return nil, fmt.Errorf("failed to generate seccomp spec opts: %v", err)
}
// TODO (mikebrow): consider a fuction for the logic used in sandbox and container for secccomp
// Unset the seccomp profile, if seccomp is not enabled, unconfined, unset, or the security context is privileged
if !seccompEnabled ||
seccompProf == unconfinedProfile ||
seccompProf == "" ||
config.GetLinux().GetSecurityContext().GetPrivileged() {
spec.Linux.Seccomp = nil
} else {
switch seccompProf {
case dockerDefault:
// Note: WithDefaultProfile specOpts must be added after capabilities
specOpts = append(specOpts, seccomp.WithDefaultProfile())
default:
// Require and Trim default profile name prefix
if !strings.HasPrefix(seccompProf, profileNamePrefix) {
return nil, fmt.Errorf("invalid seccomp profile %q", seccompProf)
}
specOpts = append(specOpts, seccomp.WithProfile(strings.TrimPrefix(seccompProf, profileNamePrefix)))
}
if seccompSpecOpts != nil {
specOpts = append(specOpts, seccompSpecOpts)
}
opts := []containerd.NewContainerOpts{