Merge pull request #30183 from timstclair/aa-psp

Automatic merge from submit-queue

AppArmor PodSecurityPolicy support

Implements the AppArmor PodSecurityPolicy support based on the alpha API proposed [here](https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/apparmor.md#pod-security-policy)

This implementation deviates from the original proposal in one way: it adds a separate option for specifying a default profile:
```
apparmor.security.alpha.kubernetes.io/defaultProfileName
```
This has several advantages over the original proposal:

- The default is explicit, rather than implicit on the ordering
- The default can be specified without constraining the allowed profiles
- The allowed profiles can be restricted without specifying a default (requires every pod to explicitly set a profile)

The E2E cluster does not currently enable the PodSecurityPolicy, so I will submit E2E tests in a separate PR.

/cc @dchen1107 @pweil- @sttts @jfrazelle @Amey-D
pull/6/head
Kubernetes Submit Queue 2016-08-23 03:06:05 -07:00 committed by GitHub
commit 0b5547f462
11 changed files with 493 additions and 5 deletions

View File

@ -206,4 +206,5 @@ pkg/util/maps
pkg/volume/quobyte
test/integration/discoverysummarizer
test/integration/examples
test/integration/federation
test/integration/federation
pkg/security/podsecuritypolicy/apparmor

View File

@ -30,6 +30,7 @@ import (
apivalidation "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/security/apparmor"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/sets"
@ -552,6 +553,7 @@ var ValidatePodSecurityPolicyName = apivalidation.NameIsDNSSubdomain
func ValidatePodSecurityPolicy(psp *extensions.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&psp.ObjectMeta, false, ValidatePodSecurityPolicyName, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(psp.Annotations, field.NewPath("metadata").Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, field.NewPath("spec"))...)
return allErrs
}
@ -570,6 +572,23 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa
return allErrs
}
func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if p := annotations[apparmor.DefaultProfileAnnotationKey]; p != "" {
if err := apparmor.ValidateProfileFormat(p); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Key(apparmor.DefaultProfileAnnotationKey), p, err.Error()))
}
}
if allowed := annotations[apparmor.AllowedProfilesAnnotationKey]; allowed != "" {
for _, p := range strings.Split(allowed, ",") {
if err := apparmor.ValidateProfileFormat(p); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Key(apparmor.AllowedProfilesAnnotationKey), allowed, err.Error()))
}
}
}
return allErrs
}
// validatePSPSELinux validates the SELinux fields of PodSecurityPolicy.
func validatePSPSELinux(fldPath *field.Path, seLinux *extensions.SELinuxStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}

View File

@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/apparmor"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/validation/field"
@ -1510,7 +1511,9 @@ func TestValidateReplicaSet(t *testing.T) {
func TestValidatePodSecurityPolicy(t *testing.T) {
validPSP := func() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{Name: "foo"},
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: extensions.PodSecurityPolicySpec{
SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyRunAsAny,
@ -1584,6 +1587,15 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
allowedCapListedInRequiredDrop.Spec.RequiredDropCapabilities = []api.Capability{"foo"}
allowedCapListedInRequiredDrop.Spec.AllowedCapabilities = []api.Capability{"foo"}
invalidAppArmorDefault := validPSP()
invalidAppArmorDefault.Annotations = map[string]string{
apparmor.DefaultProfileAnnotationKey: "not-good",
}
invalidAppArmorAllowed := validPSP()
invalidAppArmorAllowed.Annotations = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + ",not-good",
}
errorCases := map[string]struct {
psp *extensions.PodSecurityPolicy
errorType field.ErrorType
@ -1664,6 +1676,16 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
errorType: field.ErrorTypeInvalid,
errorDetail: "capability is listed in allowedCapabilities and requiredDropCapabilities",
},
"invalid AppArmor default profile": {
psp: invalidAppArmorDefault,
errorType: field.ErrorTypeInvalid,
errorDetail: "invalid AppArmor profile name: \"not-good\"",
},
"invalid AppArmor allowed profile": {
psp: invalidAppArmorAllowed,
errorType: field.ErrorTypeInvalid,
errorDetail: "invalid AppArmor profile name: \"not-good\"",
},
}
for k, v := range errorCases {
@ -1700,6 +1722,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
caseInsensitiveAllowedDrop.Spec.RequiredDropCapabilities = []api.Capability{"FOO"}
caseInsensitiveAllowedDrop.Spec.AllowedCapabilities = []api.Capability{"foo"}
validAppArmor := validPSP()
validAppArmor.Annotations = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo",
}
successCases := map[string]struct {
psp *extensions.PodSecurityPolicy
}{
@ -1718,6 +1746,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
"comparison for allowed -> drop is case sensitive": {
psp: caseInsensitiveAllowedDrop,
},
"valid AppArmor annotations": {
psp: validAppArmor,
},
}
for k, v := range successCases {

View File

@ -26,6 +26,10 @@ import (
const (
// The prefix to an annotation key specifying a container profile.
ContainerAnnotationKeyPrefix = "container.apparmor.security.alpha.kubernetes.io/"
// The annotation key specifying the default AppArmor profile.
DefaultProfileAnnotationKey = "apparmor.security.alpha.kubernetes.io/defaultProfileName"
// The annotation key specifying the allowed AppArmor profiles.
AllowedProfilesAnnotationKey = "apparmor.security.alpha.kubernetes.io/allowedProfileNames"
// The profile specifying the runtime default.
ProfileRuntimeDefault = "runtime/default"
@ -47,3 +51,12 @@ func isRequired(pod *api.Pod) bool {
func GetProfileName(pod *api.Pod, containerName string) string {
return pod.Annotations[ContainerAnnotationKeyPrefix+containerName]
}
// Sets the name of the profile to use with the container.
func SetProfileName(pod *api.Pod, containerName, profileName string) error {
if pod.Annotations == nil {
pod.Annotations = map[string]string{}
}
pod.Annotations[ContainerAnnotationKeyPrefix+containerName] = profileName
return nil
}

View File

@ -0,0 +1,110 @@
/*
Copyright 2016 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 apparmor
import (
"fmt"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/util/maps"
"k8s.io/kubernetes/pkg/util/validation/field"
)
// Strategy defines the interface for all AppArmor constraint strategies.
type Strategy interface {
// Generate updates the annotations based on constraint rules. The updates are applied to a copy
// of the annotations, and returned.
Generate(annotations map[string]string, container *api.Container) (map[string]string, error)
// Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod, container *api.Container) field.ErrorList
}
type strategy struct {
defaultProfile string
allowedProfiles map[string]bool
// For printing error messages (preserves order).
allowedProfilesString string
}
var _ Strategy = &strategy{}
// NewStrategy creates a new strategy that enforces AppArmor profile constraints.
func NewStrategy(pspAnnotations map[string]string) Strategy {
var allowedProfiles map[string]bool
if allowed, ok := pspAnnotations[apparmor.AllowedProfilesAnnotationKey]; ok {
profiles := strings.Split(allowed, ",")
allowedProfiles = make(map[string]bool, len(profiles))
for _, p := range profiles {
allowedProfiles[p] = true
}
}
return &strategy{
defaultProfile: pspAnnotations[apparmor.DefaultProfileAnnotationKey],
allowedProfiles: allowedProfiles,
allowedProfilesString: pspAnnotations[apparmor.AllowedProfilesAnnotationKey],
}
}
func (s *strategy) Generate(annotations map[string]string, container *api.Container) (map[string]string, error) {
copy := maps.CopySS(annotations)
if annotations[apparmor.ContainerAnnotationKeyPrefix+container.Name] != "" {
// Profile already set, nothing to do.
return copy, nil
}
if s.defaultProfile == "" {
// No default set.
return copy, nil
}
if copy == nil {
copy = map[string]string{}
}
// Add the default profile.
copy[apparmor.ContainerAnnotationKeyPrefix+container.Name] = s.defaultProfile
return copy, nil
}
func (s *strategy) Validate(pod *api.Pod, container *api.Container) field.ErrorList {
if s.allowedProfiles == nil {
// Unrestricted: allow all.
return nil
}
allErrs := field.ErrorList{}
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(apparmor.ContainerAnnotationKeyPrefix + container.Name)
profile := apparmor.GetProfileName(pod, container.Name)
if profile == "" {
if len(s.allowedProfiles) > 0 {
allErrs = append(allErrs, field.Forbidden(fieldPath, "AppArmor profile must be set"))
return allErrs
}
return nil
}
if !s.allowedProfiles[profile] {
msg := fmt.Sprintf("%s is not an allowed profile. Allowed values: %q", profile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(fieldPath, msg))
}
return allErrs
}

View File

@ -0,0 +1,173 @@
/*
Copyright 2016 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 apparmor
import (
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/util/maps"
)
const (
containerName = "test-c"
)
var (
withoutAppArmor = map[string]string{"foo": "bar"}
withDefault = map[string]string{
"foo": "bar",
apparmor.ContainerAnnotationKeyPrefix + containerName: apparmor.ProfileRuntimeDefault,
}
withLocal = map[string]string{
"foo": "bar",
apparmor.ContainerAnnotationKeyPrefix + containerName: apparmor.ProfileNamePrefix + "foo",
}
withDisallowed = map[string]string{
"foo": "bar",
apparmor.ContainerAnnotationKeyPrefix + containerName: apparmor.ProfileNamePrefix + "bad",
}
noAppArmor = map[string]string{"foo": "bar"}
unconstrainedWithDefault = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
}
constrained = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," +
apparmor.ProfileNamePrefix + "foo",
}
constrainedWithDefault = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," +
apparmor.ProfileNamePrefix + "foo",
}
container = api.Container{
Name: containerName,
Image: "busybox",
}
)
func TestGenerate(t *testing.T) {
type testcase struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expected map[string]string
}
tests := []testcase{{
pspAnnotations: noAppArmor,
podAnnotations: withoutAppArmor,
expected: withoutAppArmor,
}, {
pspAnnotations: unconstrainedWithDefault,
podAnnotations: withoutAppArmor,
expected: withDefault,
}, {
pspAnnotations: constrained,
podAnnotations: withoutAppArmor,
expected: withoutAppArmor,
}, {
pspAnnotations: constrainedWithDefault,
podAnnotations: withoutAppArmor,
expected: withDefault,
}}
// Add unchanging permutations.
for _, podAnnotations := range []map[string]string{withDefault, withLocal} {
for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expected: podAnnotations,
})
}
}
for i, test := range tests {
s := NewStrategy(test.pspAnnotations)
msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)}
actual, err := s.Generate(test.podAnnotations, &container)
assert.NoError(t, err, msgAndArgs...)
assert.Equal(t, test.expected, actual, msgAndArgs...)
}
}
func TestValidate(t *testing.T) {
type testcase struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectErr bool
}
tests := []testcase{}
// Valid combinations
for _, podAnnotations := range []map[string]string{withDefault, withLocal} {
for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expectErr: false,
})
}
}
for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} {
for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expectErr: false,
})
}
}
// Invalid combinations
for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} {
for _, pspAnnotations := range []map[string]string{constrained, constrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expectErr: true,
})
}
}
for i, test := range tests {
s := NewStrategy(test.pspAnnotations)
pod, container := makeTestPod(test.podAnnotations)
msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)}
errs := s.Validate(pod, container)
if test.expectErr {
assert.Len(t, errs, 1, msgAndArgs...)
} else {
assert.Len(t, errs, 0, msgAndArgs...)
}
}
}
func makeTestPod(annotations map[string]string) (*api.Pod, *api.Container) {
return &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "test-pod",
Annotations: maps.CopySS(annotations),
},
Spec: api.PodSpec{
Containers: []api.Container{container},
},
}, &container
}

View File

@ -21,6 +21,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
@ -49,6 +50,11 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
errs = append(errs, err)
}
appArmorStrat, err := createAppArmorStrategy(psp)
if err != nil {
errs = append(errs, err)
}
fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup)
if err != nil {
errs = append(errs, err)
@ -71,6 +77,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
strategies := &ProviderStrategies{
RunAsUserStrategy: userStrat,
SELinuxStrategy: seLinuxStrat,
AppArmorStrategy: appArmorStrat,
FSGroupStrategy: fsGroupStrat,
SupplementalGroupStrategy: supGroupStrat,
CapabilitiesStrategy: capStrat,
@ -105,6 +112,11 @@ func createSELinuxStrategy(opts *extensions.SELinuxStrategyOptions) (selinux.SEL
}
}
// createAppArmorStrategy creates a new AppArmor strategy.
func createAppArmorStrategy(psp *extensions.PodSecurityPolicy) (apparmor.Strategy, error) {
return apparmor.NewStrategy(psp.Annotations), nil
}
// createFSGroupStrategy creates a new fsgroup strategy
func createFSGroupStrategy(opts *extensions.FSGroupStrategyOptions) (group.GroupStrategy, error) {
switch opts.Rule {

View File

@ -139,6 +139,11 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container
sc.SELinuxOptions = seLinux
}
annotations, err := s.strategies.AppArmorStrategy.Generate(annotations, container)
if err != nil {
return nil, nil, err
}
if sc.Privileged == nil {
priv := false
sc.Privileged = &priv
@ -220,6 +225,7 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe
sc := container.SecurityContext
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...)
if !s.psp.Spec.Privileged && *sc.Privileged {
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed"))

View File

@ -22,13 +22,18 @@ import (
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/apparmor"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/diff"
"k8s.io/kubernetes/pkg/util/validation/field"
)
const defaultContainerName = "test-c"
func TestCreatePodSecurityContextNonmutating(t *testing.T) {
// Create a pod with a security context that needs filling in
createPod := func() *api.Pod {
@ -303,6 +308,14 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
Level: "bar",
}
failNilAppArmorPod := defaultPod()
failInvalidAppArmorPod := defaultPod()
apparmor.SetProfileName(failInvalidAppArmorPod, defaultContainerName, apparmor.ProfileNamePrefix+"foo")
failAppArmorPSP := defaultPSP()
failAppArmorPSP.Annotations = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault,
}
failPrivPod := defaultPod()
var priv bool = true
failPrivPod.Spec.Containers[0].SecurityContext.Privileged = &priv
@ -347,6 +360,16 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
psp: failSELinuxPSP,
expectedError: "does not match required level",
},
"failNilAppArmor": {
pod: failNilAppArmorPod,
psp: failAppArmorPSP,
expectedError: "AppArmor profile must be set",
},
"failInvalidAppArmor": {
pod: failInvalidAppArmorPod,
psp: failAppArmorPSP,
expectedError: "localhost/foo is not an allowed profile. Allowed values: \"runtime/default\"",
},
"failPrivPSP": {
pod: failPrivPod,
psp: defaultPSP(),
@ -499,6 +522,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
SecurityContext: &api.PodSecurityContext{},
Containers: []api.Container{
{
Name: defaultContainerName,
SecurityContext: &api.SecurityContext{
// expected to be set by defaulting mechanisms
Privileged: &notPriv,
@ -510,7 +534,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
}
}
// fail user strat
// success user strat
userPSP := defaultPSP()
var uid int64 = 999
userPSP.Spec.RunAsUser = extensions.RunAsUserStrategyOptions{
@ -520,7 +544,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
userPod := defaultPod()
userPod.Spec.Containers[0].SecurityContext.RunAsUser = &uid
// fail selinux strat
// success selinux strat
seLinuxPSP := defaultPSP()
seLinuxPSP.Spec.SELinux = extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyMustRunAs,
@ -533,6 +557,13 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
Level: "foo",
}
appArmorPSP := defaultPSP()
appArmorPSP.Annotations = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault,
}
appArmorPod := defaultPod()
apparmor.SetProfileName(appArmorPod, defaultContainerName, apparmor.ProfileRuntimeDefault)
privPSP := defaultPSP()
privPSP.Spec.Privileged = true
privPod := defaultPod()
@ -591,6 +622,10 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
pod: seLinuxPod,
psp: seLinuxPSP,
},
"pass AppArmor allowed profiles": {
pod: appArmorPod,
psp: appArmorPSP,
},
"pass priv validating PSP": {
pod: privPod,
psp: privPSP,
@ -632,7 +667,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
}
errs := provider.ValidateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0], field.NewPath(""))
if len(errs) != 0 {
t.Errorf("%s expected validation pass but received errors %v", k, errs)
t.Errorf("%s expected validation pass but received errors %v\n%s", k, errs, spew.Sdump(v.pod.ObjectMeta))
continue
}
}
@ -748,6 +783,7 @@ func defaultPod() *api.Pod {
},
Containers: []api.Container{
{
Name: defaultContainerName,
SecurityContext: &api.SecurityContext{
// expected to be set by defaulting mechanisms
Privileged: &notPriv,

View File

@ -19,6 +19,7 @@ package podsecuritypolicy
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
@ -58,6 +59,7 @@ type StrategyFactory interface {
type ProviderStrategies struct {
RunAsUserStrategy user.RunAsUserStrategy
SELinuxStrategy selinux.SELinuxStrategy
AppArmorStrategy apparmor.Strategy
FSGroupStrategy group.GroupStrategy
SupplementalGroupStrategy group.GroupStrategy
CapabilitiesStrategy capabilities.Strategy

View File

@ -22,6 +22,8 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
kadmission "k8s.io/kubernetes/pkg/admission"
kapi "k8s.io/kubernetes/pkg/api"
extensions "k8s.io/kubernetes/pkg/apis/extensions"
@ -29,11 +31,14 @@ import (
"k8s.io/kubernetes/pkg/client/cache"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/security/apparmor"
kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
diff "k8s.io/kubernetes/pkg/util/diff"
)
const defaultContainerName = "test-c"
func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission.Interface {
return &podSecurityPolicyPlugin{
Handler: kadmission.NewHandler(kadmission.Create),
@ -610,6 +615,85 @@ func TestAdmitSELinux(t *testing.T) {
}
}
func TestAdmitAppArmor(t *testing.T) {
createPodWithAppArmor := func(profile string) *kapi.Pod {
pod := goodPod()
apparmor.SetProfileName(pod, defaultContainerName, profile)
return pod
}
unconstrainedPSP := restrictivePSP()
defaultedPSP := restrictivePSP()
defaultedPSP.Annotations = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
}
appArmorPSP := restrictivePSP()
appArmorPSP.Annotations = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault,
}
appArmorDefaultPSP := restrictivePSP()
appArmorDefaultPSP.Annotations = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo",
}
tests := map[string]struct {
pod *kapi.Pod
psp *extensions.PodSecurityPolicy
shouldPass bool
expectedProfile string
}{
"unconstrained with no profile": {
pod: goodPod(),
psp: unconstrainedPSP,
shouldPass: true,
expectedProfile: "",
},
"unconstrained with profile": {
pod: createPodWithAppArmor(apparmor.ProfileRuntimeDefault),
psp: unconstrainedPSP,
shouldPass: true,
expectedProfile: apparmor.ProfileRuntimeDefault,
},
"unconstrained with default profile": {
pod: goodPod(),
psp: defaultedPSP,
shouldPass: true,
expectedProfile: apparmor.ProfileRuntimeDefault,
},
"AppArmor enforced with no profile": {
pod: goodPod(),
psp: appArmorPSP,
shouldPass: false,
},
"AppArmor enforced with default profile": {
pod: goodPod(),
psp: appArmorDefaultPSP,
shouldPass: true,
expectedProfile: apparmor.ProfileRuntimeDefault,
},
"AppArmor enforced with good profile": {
pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "foo"),
psp: appArmorDefaultPSP,
shouldPass: true,
expectedProfile: apparmor.ProfileNamePrefix + "foo",
},
"AppArmor enforced with local profile": {
pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "bar"),
psp: appArmorPSP,
shouldPass: false,
},
}
for k, v := range tests {
testPSPAdmit(k, []*extensions.PodSecurityPolicy{v.psp}, v.pod, v.shouldPass, v.psp.Name, t)
if v.shouldPass {
assert.Equal(t, v.expectedProfile, apparmor.GetProfileName(v.pod, defaultContainerName), k)
}
}
}
func TestAdmitRunAsUser(t *testing.T) {
createPodWithRunAsUser := func(user int64) *kapi.Pod {
pod := goodPod()
@ -1212,6 +1296,7 @@ func goodPod() *kapi.Pod {
SecurityContext: &kapi.PodSecurityContext{},
Containers: []kapi.Container{
{
Name: defaultContainerName,
SecurityContext: &kapi.SecurityContext{},
},
},