AppArmor PodSecurityPolicy implementation

parent 52b3ef6f
...@@ -206,4 +206,5 @@ pkg/util/maps ...@@ -206,4 +206,5 @@ pkg/util/maps
pkg/volume/quobyte pkg/volume/quobyte
test/integration/discoverysummarizer test/integration/discoverysummarizer
test/integration/examples test/integration/examples
test/integration/federation test/integration/federation
\ No newline at end of file pkg/security/podsecuritypolicy/apparmor
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
apivalidation "k8s.io/kubernetes/pkg/api/validation" apivalidation "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/security/apparmor"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
...@@ -552,6 +553,7 @@ var ValidatePodSecurityPolicyName = apivalidation.NameIsDNSSubdomain ...@@ -552,6 +553,7 @@ var ValidatePodSecurityPolicyName = apivalidation.NameIsDNSSubdomain
func ValidatePodSecurityPolicy(psp *extensions.PodSecurityPolicy) field.ErrorList { func ValidatePodSecurityPolicy(psp *extensions.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&psp.ObjectMeta, false, ValidatePodSecurityPolicyName, field.NewPath("metadata"))...) 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"))...) allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, field.NewPath("spec"))...)
return allErrs return allErrs
} }
...@@ -570,6 +572,23 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa ...@@ -570,6 +572,23 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa
return allErrs 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. // validatePSPSELinux validates the SELinux fields of PodSecurityPolicy.
func validatePSPSELinux(fldPath *field.Path, seLinux *extensions.SELinuxStrategyOptions) field.ErrorList { func validatePSPSELinux(fldPath *field.Path, seLinux *extensions.SELinuxStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
......
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/apparmor"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/validation/field" "k8s.io/kubernetes/pkg/util/validation/field"
...@@ -1510,7 +1511,9 @@ func TestValidateReplicaSet(t *testing.T) { ...@@ -1510,7 +1511,9 @@ func TestValidateReplicaSet(t *testing.T) {
func TestValidatePodSecurityPolicy(t *testing.T) { func TestValidatePodSecurityPolicy(t *testing.T) {
validPSP := func() *extensions.PodSecurityPolicy { validPSP := func() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{ return &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: extensions.PodSecurityPolicySpec{ Spec: extensions.PodSecurityPolicySpec{
SELinux: extensions.SELinuxStrategyOptions{ SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyRunAsAny, Rule: extensions.SELinuxStrategyRunAsAny,
...@@ -1584,6 +1587,15 @@ func TestValidatePodSecurityPolicy(t *testing.T) { ...@@ -1584,6 +1587,15 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
allowedCapListedInRequiredDrop.Spec.RequiredDropCapabilities = []api.Capability{"foo"} allowedCapListedInRequiredDrop.Spec.RequiredDropCapabilities = []api.Capability{"foo"}
allowedCapListedInRequiredDrop.Spec.AllowedCapabilities = []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 { errorCases := map[string]struct {
psp *extensions.PodSecurityPolicy psp *extensions.PodSecurityPolicy
errorType field.ErrorType errorType field.ErrorType
...@@ -1664,6 +1676,16 @@ func TestValidatePodSecurityPolicy(t *testing.T) { ...@@ -1664,6 +1676,16 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
errorType: field.ErrorTypeInvalid, errorType: field.ErrorTypeInvalid,
errorDetail: "capability is listed in allowedCapabilities and requiredDropCapabilities", 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 { for k, v := range errorCases {
...@@ -1700,6 +1722,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) { ...@@ -1700,6 +1722,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
caseInsensitiveAllowedDrop.Spec.RequiredDropCapabilities = []api.Capability{"FOO"} caseInsensitiveAllowedDrop.Spec.RequiredDropCapabilities = []api.Capability{"FOO"}
caseInsensitiveAllowedDrop.Spec.AllowedCapabilities = []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 { successCases := map[string]struct {
psp *extensions.PodSecurityPolicy psp *extensions.PodSecurityPolicy
}{ }{
...@@ -1718,6 +1746,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) { ...@@ -1718,6 +1746,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
"comparison for allowed -> drop is case sensitive": { "comparison for allowed -> drop is case sensitive": {
psp: caseInsensitiveAllowedDrop, psp: caseInsensitiveAllowedDrop,
}, },
"valid AppArmor annotations": {
psp: validAppArmor,
},
} }
for k, v := range successCases { for k, v := range successCases {
......
...@@ -26,6 +26,10 @@ import ( ...@@ -26,6 +26,10 @@ import (
const ( const (
// The prefix to an annotation key specifying a container profile. // The prefix to an annotation key specifying a container profile.
ContainerAnnotationKeyPrefix = "container.apparmor.security.alpha.kubernetes.io/" 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. // The profile specifying the runtime default.
ProfileRuntimeDefault = "runtime/default" ProfileRuntimeDefault = "runtime/default"
...@@ -47,3 +51,12 @@ func isRequired(pod *api.Pod) bool { ...@@ -47,3 +51,12 @@ func isRequired(pod *api.Pod) bool {
func GetProfileName(pod *api.Pod, containerName string) string { func GetProfileName(pod *api.Pod, containerName string) string {
return pod.Annotations[ContainerAnnotationKeyPrefix+containerName] 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
}
/*
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
}
/*
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
}
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "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/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
...@@ -49,6 +50,11 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli ...@@ -49,6 +50,11 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
errs = append(errs, err) errs = append(errs, err)
} }
appArmorStrat, err := createAppArmorStrategy(psp)
if err != nil {
errs = append(errs, err)
}
fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup) fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
...@@ -71,6 +77,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli ...@@ -71,6 +77,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
strategies := &ProviderStrategies{ strategies := &ProviderStrategies{
RunAsUserStrategy: userStrat, RunAsUserStrategy: userStrat,
SELinuxStrategy: seLinuxStrat, SELinuxStrategy: seLinuxStrat,
AppArmorStrategy: appArmorStrat,
FSGroupStrategy: fsGroupStrat, FSGroupStrategy: fsGroupStrat,
SupplementalGroupStrategy: supGroupStrat, SupplementalGroupStrategy: supGroupStrat,
CapabilitiesStrategy: capStrat, CapabilitiesStrategy: capStrat,
...@@ -105,6 +112,11 @@ func createSELinuxStrategy(opts *extensions.SELinuxStrategyOptions) (selinux.SEL ...@@ -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 // createFSGroupStrategy creates a new fsgroup strategy
func createFSGroupStrategy(opts *extensions.FSGroupStrategyOptions) (group.GroupStrategy, error) { func createFSGroupStrategy(opts *extensions.FSGroupStrategyOptions) (group.GroupStrategy, error) {
switch opts.Rule { switch opts.Rule {
......
...@@ -139,6 +139,11 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container ...@@ -139,6 +139,11 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container
sc.SELinuxOptions = seLinux sc.SELinuxOptions = seLinux
} }
annotations, err := s.strategies.AppArmorStrategy.Generate(annotations, container)
if err != nil {
return nil, nil, err
}
if sc.Privileged == nil { if sc.Privileged == nil {
priv := false priv := false
sc.Privileged = &priv sc.Privileged = &priv
...@@ -220,6 +225,7 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe ...@@ -220,6 +225,7 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe
sc := container.SecurityContext sc := container.SecurityContext
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...) allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.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 { if !s.psp.Spec.Privileged && *sc.Privileged {
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed")) allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed"))
......
...@@ -22,13 +22,18 @@ import ( ...@@ -22,13 +22,18 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/davecgh/go-spew/spew"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/apparmor"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/diff" "k8s.io/kubernetes/pkg/util/diff"
"k8s.io/kubernetes/pkg/util/validation/field" "k8s.io/kubernetes/pkg/util/validation/field"
) )
const defaultContainerName = "test-c"
func TestCreatePodSecurityContextNonmutating(t *testing.T) { func TestCreatePodSecurityContextNonmutating(t *testing.T) {
// Create a pod with a security context that needs filling in // Create a pod with a security context that needs filling in
createPod := func() *api.Pod { createPod := func() *api.Pod {
...@@ -303,6 +308,14 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) { ...@@ -303,6 +308,14 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
Level: "bar", 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() failPrivPod := defaultPod()
var priv bool = true var priv bool = true
failPrivPod.Spec.Containers[0].SecurityContext.Privileged = &priv failPrivPod.Spec.Containers[0].SecurityContext.Privileged = &priv
...@@ -347,6 +360,16 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) { ...@@ -347,6 +360,16 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
psp: failSELinuxPSP, psp: failSELinuxPSP,
expectedError: "does not match required level", 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": { "failPrivPSP": {
pod: failPrivPod, pod: failPrivPod,
psp: defaultPSP(), psp: defaultPSP(),
...@@ -499,6 +522,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) { ...@@ -499,6 +522,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
SecurityContext: &api.PodSecurityContext{}, SecurityContext: &api.PodSecurityContext{},
Containers: []api.Container{ Containers: []api.Container{
{ {
Name: defaultContainerName,
SecurityContext: &api.SecurityContext{ SecurityContext: &api.SecurityContext{
// expected to be set by defaulting mechanisms // expected to be set by defaulting mechanisms
Privileged: &notPriv, Privileged: &notPriv,
...@@ -510,7 +534,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) { ...@@ -510,7 +534,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
} }
} }
// fail user strat // success user strat
userPSP := defaultPSP() userPSP := defaultPSP()
var uid int64 = 999 var uid int64 = 999
userPSP.Spec.RunAsUser = extensions.RunAsUserStrategyOptions{ userPSP.Spec.RunAsUser = extensions.RunAsUserStrategyOptions{
...@@ -520,7 +544,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) { ...@@ -520,7 +544,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
userPod := defaultPod() userPod := defaultPod()
userPod.Spec.Containers[0].SecurityContext.RunAsUser = &uid userPod.Spec.Containers[0].SecurityContext.RunAsUser = &uid
// fail selinux strat // success selinux strat
seLinuxPSP := defaultPSP() seLinuxPSP := defaultPSP()
seLinuxPSP.Spec.SELinux = extensions.SELinuxStrategyOptions{ seLinuxPSP.Spec.SELinux = extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyMustRunAs, Rule: extensions.SELinuxStrategyMustRunAs,
...@@ -533,6 +557,13 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) { ...@@ -533,6 +557,13 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
Level: "foo", Level: "foo",
} }
appArmorPSP := defaultPSP()
appArmorPSP.Annotations = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault,
}
appArmorPod := defaultPod()
apparmor.SetProfileName(appArmorPod, defaultContainerName, apparmor.ProfileRuntimeDefault)
privPSP := defaultPSP() privPSP := defaultPSP()
privPSP.Spec.Privileged = true privPSP.Spec.Privileged = true
privPod := defaultPod() privPod := defaultPod()
...@@ -591,6 +622,10 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) { ...@@ -591,6 +622,10 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
pod: seLinuxPod, pod: seLinuxPod,
psp: seLinuxPSP, psp: seLinuxPSP,
}, },
"pass AppArmor allowed profiles": {
pod: appArmorPod,
psp: appArmorPSP,
},
"pass priv validating PSP": { "pass priv validating PSP": {
pod: privPod, pod: privPod,
psp: privPSP, psp: privPSP,
...@@ -632,7 +667,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) { ...@@ -632,7 +667,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
} }
errs := provider.ValidateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0], field.NewPath("")) errs := provider.ValidateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0], field.NewPath(""))
if len(errs) != 0 { 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 continue
} }
} }
...@@ -748,6 +783,7 @@ func defaultPod() *api.Pod { ...@@ -748,6 +783,7 @@ func defaultPod() *api.Pod {
}, },
Containers: []api.Container{ Containers: []api.Container{
{ {
Name: defaultContainerName,
SecurityContext: &api.SecurityContext{ SecurityContext: &api.SecurityContext{
// expected to be set by defaulting mechanisms // expected to be set by defaulting mechanisms
Privileged: &notPriv, Privileged: &notPriv,
......
...@@ -19,6 +19,7 @@ package podsecuritypolicy ...@@ -19,6 +19,7 @@ package podsecuritypolicy
import ( import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "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/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
...@@ -58,6 +59,7 @@ type StrategyFactory interface { ...@@ -58,6 +59,7 @@ type StrategyFactory interface {
type ProviderStrategies struct { type ProviderStrategies struct {
RunAsUserStrategy user.RunAsUserStrategy RunAsUserStrategy user.RunAsUserStrategy
SELinuxStrategy selinux.SELinuxStrategy SELinuxStrategy selinux.SELinuxStrategy
AppArmorStrategy apparmor.Strategy
FSGroupStrategy group.GroupStrategy FSGroupStrategy group.GroupStrategy
SupplementalGroupStrategy group.GroupStrategy SupplementalGroupStrategy group.GroupStrategy
CapabilitiesStrategy capabilities.Strategy CapabilitiesStrategy capabilities.Strategy
......
...@@ -22,6 +22,8 @@ import ( ...@@ -22,6 +22,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
kadmission "k8s.io/kubernetes/pkg/admission" kadmission "k8s.io/kubernetes/pkg/admission"
kapi "k8s.io/kubernetes/pkg/api" kapi "k8s.io/kubernetes/pkg/api"
extensions "k8s.io/kubernetes/pkg/apis/extensions" extensions "k8s.io/kubernetes/pkg/apis/extensions"
...@@ -29,11 +31,14 @@ import ( ...@@ -29,11 +31,14 @@ import (
"k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/client/cache"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/security/apparmor"
kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
diff "k8s.io/kubernetes/pkg/util/diff" diff "k8s.io/kubernetes/pkg/util/diff"
) )
const defaultContainerName = "test-c"
func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission.Interface { func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission.Interface {
return &podSecurityPolicyPlugin{ return &podSecurityPolicyPlugin{
Handler: kadmission.NewHandler(kadmission.Create), Handler: kadmission.NewHandler(kadmission.Create),
...@@ -610,6 +615,85 @@ func TestAdmitSELinux(t *testing.T) { ...@@ -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) { func TestAdmitRunAsUser(t *testing.T) {
createPodWithRunAsUser := func(user int64) *kapi.Pod { createPodWithRunAsUser := func(user int64) *kapi.Pod {
pod := goodPod() pod := goodPod()
...@@ -1212,6 +1296,7 @@ func goodPod() *kapi.Pod { ...@@ -1212,6 +1296,7 @@ func goodPod() *kapi.Pod {
SecurityContext: &kapi.PodSecurityContext{}, SecurityContext: &kapi.PodSecurityContext{},
Containers: []kapi.Container{ Containers: []kapi.Container{
{ {
Name: defaultContainerName,
SecurityContext: &kapi.SecurityContext{}, SecurityContext: &kapi.SecurityContext{},
}, },
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment