Check seccomp enable and add unit test for seccomp/apparmor.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user