Merge pull request #65135 from stlaz/psp_group_mayrunas

Add "MayRunAs" value among other GroupStrategies
This commit is contained in:
k8s-ci-robot 2018-09-29 19:44:55 -07:00 committed by GitHub
commit 0b3a5cd64f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 389 additions and 40 deletions

View File

@ -48,12 +48,14 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
supplementalGroupsRules := []policy.SupplementalGroupsStrategyType{
policy.SupplementalGroupsStrategyRunAsAny,
policy.SupplementalGroupsStrategyMayRunAs,
policy.SupplementalGroupsStrategyMustRunAs,
}
psp.SupplementalGroups.Rule = supplementalGroupsRules[c.Rand.Intn(len(supplementalGroupsRules))]
fsGroupRules := []policy.FSGroupStrategyType{
policy.FSGroupStrategyMustRunAs,
policy.FSGroupStrategyMayRunAs,
policy.FSGroupStrategyRunAsAny,
}
psp.FSGroup.Rule = fsGroupRules[c.Rand.Intn(len(fsGroupRules))]

View File

@ -372,6 +372,9 @@ type FSGroupStrategyOptions struct {
type FSGroupStrategyType string
const (
// FSGroupStrategyMayRunAs means that container does not need to have FSGroup of X applied.
// However, when FSGroups are specified, they have to fall in the defined range.
FSGroupStrategyMayRunAs FSGroupStrategyType = "MayRunAs"
// FSGroupStrategyMustRunAs means that container must have FSGroup of X applied.
FSGroupStrategyMustRunAs FSGroupStrategyType = "MustRunAs"
// FSGroupStrategyRunAsAny means that container may make requests for any FSGroup labels.
@ -394,6 +397,9 @@ type SupplementalGroupsStrategyOptions struct {
type SupplementalGroupsStrategyType string
const (
// SupplementalGroupsStrategyMayRunAs means that container does not need to run with a particular gid.
// However, when gids are specified, they have to fall in the defined range.
SupplementalGroupsStrategyMayRunAs SupplementalGroupsStrategyType = "MayRunAs"
// SupplementalGroupsStrategyMustRunAs means that container must run as a particular gid.
SupplementalGroupsStrategyMustRunAs SupplementalGroupsStrategyType = "MustRunAs"
// SupplementalGroupsStrategyRunAsAny means that container may make requests for any gid.

View File

@ -239,6 +239,7 @@ func validatePSPFSGroup(fldPath *field.Path, groupOptions *policy.FSGroupStrateg
supportedRules := sets.NewString(
string(policy.FSGroupStrategyMustRunAs),
string(policy.FSGroupStrategyMayRunAs),
string(policy.FSGroupStrategyRunAsAny),
)
if !supportedRules.Has(string(groupOptions.Rule)) {
@ -257,6 +258,7 @@ func validatePSPSupplementalGroup(fldPath *field.Path, groupOptions *policy.Supp
supportedRules := sets.NewString(
string(policy.SupplementalGroupsStrategyRunAsAny),
string(policy.SupplementalGroupsStrategyMayRunAs),
string(policy.SupplementalGroupsStrategyMustRunAs),
)
if !supportedRules.Has(string(groupOptions.Rule)) {

View File

@ -390,12 +390,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
"no fsgroup options": {
psp: noFSGroupOptions,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
errorDetail: `supported values: "MayRunAs", "MustRunAs", "RunAsAny"`,
},
"no sup group options": {
psp: noSupplementalGroupsOptions,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
errorDetail: `supported values: "MayRunAs", "MustRunAs", "RunAsAny"`,
},
"invalid user strategy type": {
psp: invalidUserStratType,
@ -410,12 +410,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
"invalid sup group strategy type": {
psp: invalidSupGroupStratType,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
errorDetail: `supported values: "MayRunAs", "MustRunAs", "RunAsAny"`,
},
"invalid fs group strategy type": {
psp: invalidFSGroupStratType,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
errorDetail: `supported values: "MayRunAs", "MustRunAs", "RunAsAny"`,
},
"invalid uid": {
psp: invalidUIDPSP,

View File

@ -138,6 +138,8 @@ func createFSGroupStrategy(opts *policy.FSGroupStrategyOptions) (group.GroupStra
switch opts.Rule {
case policy.FSGroupStrategyRunAsAny:
return group.NewRunAsAny()
case policy.FSGroupStrategyMayRunAs:
return group.NewMayRunAs(opts.Ranges)
case policy.FSGroupStrategyMustRunAs:
return group.NewMustRunAs(opts.Ranges)
default:
@ -150,6 +152,8 @@ func createSupplementalGroupStrategy(opts *policy.SupplementalGroupsStrategyOpti
switch opts.Rule {
case policy.SupplementalGroupsStrategyRunAsAny:
return group.NewRunAsAny()
case policy.SupplementalGroupsStrategyMayRunAs:
return group.NewMayRunAs(opts.Ranges)
case policy.SupplementalGroupsStrategyMustRunAs:
return group.NewMustRunAs(opts.Ranges)
default:

View File

@ -10,6 +10,8 @@ go_library(
name = "go_default_library",
srcs = [
"doc.go",
"helpers.go",
"mayrunas.go",
"mustrunas.go",
"runasany.go",
"types.go",
@ -26,6 +28,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"mayrunas_test.go",
"mustrunas_test.go",
"runasany_test.go",
],

View File

@ -0,0 +1,46 @@
/*
Copyright 2018 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 group
import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/policy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
)
func ValidateGroupsInRanges(fldPath *field.Path, ranges []policy.IDRange, groups []int64) field.ErrorList {
allErrs := field.ErrorList{}
for _, group := range groups {
if !isGroupInRanges(group, ranges) {
detail := fmt.Sprintf("group %d must be in the ranges: %v", group, ranges)
allErrs = append(allErrs, field.Invalid(fldPath, groups, detail))
}
}
return allErrs
}
func isGroupInRanges(group int64, ranges []policy.IDRange) bool {
for _, rng := range ranges {
if psputil.GroupFallsInRange(group, rng) {
return true
}
}
return false
}

View File

@ -0,0 +1,59 @@
/*
Copyright 2018 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 group
import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
// mayRunAs implements the GroupStrategy interface.
type mayRunAs struct {
ranges []policy.IDRange
}
var _ GroupStrategy = &mayRunAs{}
// NewMayRunAs provides a new MayRunAs strategy.
func NewMayRunAs(ranges []policy.IDRange) (GroupStrategy, error) {
if len(ranges) == 0 {
return nil, fmt.Errorf("ranges must be supplied for MayRunAs")
}
return &mayRunAs{
ranges: ranges,
}, nil
}
// Generate creates the group based on policy rules. This strategy returns an empty slice.
func (s *mayRunAs) Generate(_ *api.Pod) ([]int64, error) {
return nil, nil
}
// Generate a single value to be applied. This is used for FSGroup. This strategy returns nil.
func (s *mayRunAs) GenerateSingle(_ *api.Pod) (*int64, error) {
return nil, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
// Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and
// supplemental groups).
func (s *mayRunAs) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) field.ErrorList {
return ValidateGroupsInRanges(fldPath, s.ranges, groups)
}

View File

@ -0,0 +1,185 @@
/*
Copyright 2018 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 group
import (
"fmt"
"strings"
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/policy"
)
func TestMayRunAsOptions(t *testing.T) {
tests := map[string]struct {
ranges []policy.IDRange
pass bool
}{
"empty": {
ranges: []policy.IDRange{},
},
"ranges": {
ranges: []policy.IDRange{
{Min: 1, Max: 1},
},
pass: true,
},
}
for k, v := range tests {
_, err := NewMayRunAs(v.ranges)
if v.pass && err != nil {
t.Errorf("error creating strategy for %s: %v", k, err)
}
if !v.pass && err == nil {
t.Errorf("expected error for %s but got none", k)
}
}
}
func TestMayRunAsValidate(t *testing.T) {
tests := map[string]struct {
ranges []policy.IDRange
groups []int64
expectedErrors []string
}{
"empty groups": {
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
},
"not in range": {
groups: []int64{5},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
{Min: 4, Max: 4},
},
expectedErrors: []string{"group 5 must be in the ranges: [{1 3} {4 4}]"},
},
"not in ranges - multiple groups": {
groups: []int64{5, 10, 2020},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
{Min: 15, Max: 70},
},
expectedErrors: []string{
"group 5 must be in the ranges: [{1 3} {15 70}]",
"group 10 must be in the ranges: [{1 3} {15 70}]",
"group 2020 must be in the ranges: [{1 3} {15 70}]",
},
},
"not in ranges - one of multiple groups does not match": {
groups: []int64{5, 10, 2020},
ranges: []policy.IDRange{
{Min: 1, Max: 5},
{Min: 8, Max: 12},
{Min: 15, Max: 70},
},
expectedErrors: []string{
"group 2020 must be in the ranges: [{1 5} {8 12} {15 70}]",
},
},
"in range 1": {
groups: []int64{2},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
},
"in range boundary min": {
groups: []int64{1},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
},
"in range boundary max": {
groups: []int64{3},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
},
"singular range": {
groups: []int64{4},
ranges: []policy.IDRange{
{Min: 4, Max: 4},
},
},
"in one of multiple ranges": {
groups: []int64{4},
ranges: []policy.IDRange{
{Min: 1, Max: 4},
{Min: 10, Max: 15},
},
},
"multiple groups matches one range": {
groups: []int64{4, 8, 12},
ranges: []policy.IDRange{
{Min: 1, Max: 20},
},
},
"multiple groups match multiple ranges": {
groups: []int64{4, 8, 12},
ranges: []policy.IDRange{
{Min: 1, Max: 4},
{Min: 200, Max: 2000},
{Min: 7, Max: 11},
{Min: 5, Max: 7},
{Min: 17, Max: 53},
{Min: 12, Max: 71},
},
},
}
for k, v := range tests {
s, err := NewMayRunAs(v.ranges)
if err != nil {
t.Errorf("error creating strategy for %s: %v", k, err)
}
errs := s.Validate(field.NewPath(""), nil, v.groups)
if len(v.expectedErrors) != len(errs) {
// number of expected errors is different from actual, includes cases when we expected errors and they appeared or vice versa
t.Errorf("number of expected errors for '%s' does not match with errors received:\n"+
"expected:\n%s\nbut got:\n%s",
k, concatenateStrings(v.expectedErrors), concatenateErrors(errs))
} else if len(v.expectedErrors) > 0 {
// check that the errors received match the expectations
for i, s := range v.expectedErrors {
if !strings.Contains(errs[i].Error(), s) {
t.Errorf("expected errors in particular order for '%s':\n%s\nbut got:\n%s",
k, concatenateStrings(v.expectedErrors), concatenateErrors(errs))
break
}
}
}
}
}
func concatenateErrors(errs field.ErrorList) string {
var errStrings []string
for _, e := range errs {
errStrings = append(errStrings, e.Error())
}
return concatenateStrings(errStrings)
}
func concatenateStrings(ss []string) string {
var ret string
for i, v := range ss {
ret += fmt.Sprintf("%d: %s\n", i+1, v)
}
return ret
}

View File

@ -22,7 +22,6 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
)
// mustRunAs implements the GroupStrategy interface
@ -66,21 +65,7 @@ func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, groups []int64) fi
allErrs = append(allErrs, field.Invalid(fldPath, groups, "unable to validate empty groups against required ranges"))
}
for _, group := range groups {
if !s.isGroupValid(group) {
detail := fmt.Sprintf("group %d must be in the ranges: %v", group, s.ranges)
allErrs = append(allErrs, field.Invalid(fldPath, groups, detail))
}
}
allErrs = append(allErrs, ValidateGroupsInRanges(fldPath, s.ranges, groups)...)
return allErrs
}
func (s *mustRunAs) isGroupValid(group int64) bool {
for _, rng := range s.ranges {
if psputil.GroupFallsInRange(group, rng) {
return true
}
}
return false
}

View File

@ -186,24 +186,41 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
failSupplementalGroupPod := defaultPod()
failSupplementalGroupPod.Spec.SecurityContext.SupplementalGroups = []int64{999}
failSupplementalGroupPSP := defaultPSP()
failSupplementalGroupPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{
failSupplementalGroupMustPSP := defaultPSP()
failSupplementalGroupMustPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyMustRunAs,
Ranges: []policy.IDRange{
{Min: 1, Max: 1},
},
}
failSupplementalGroupMayPSP := defaultPSP()
failSupplementalGroupMayPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyMayRunAs,
Ranges: []policy.IDRange{
{Min: 50, Max: 50},
{Min: 55, Max: 998},
{Min: 1000, Max: 1000},
},
}
failFSGroupPod := defaultPod()
fsGroup := int64(999)
failFSGroupPod.Spec.SecurityContext.FSGroup = &fsGroup
failFSGroupPSP := defaultPSP()
failFSGroupPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{
failFSGroupMustPSP := defaultPSP()
failFSGroupMustPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyMustRunAs,
Ranges: []policy.IDRange{
{Min: 1, Max: 1},
},
}
failFSGroupMayPSP := defaultPSP()
failFSGroupMayPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyMayRunAs,
Ranges: []policy.IDRange{
{Min: 10, Max: 20},
{Min: 1000, Max: 1001},
},
}
failNilSELinuxPod := defaultPod()
failSELinuxPSP := defaultPSP()
@ -334,24 +351,34 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
psp: defaultPSP(),
expectedError: "Host IPC is not allowed to be used",
},
"failSupplementalGroupOutOfRange": {
"failSupplementalGroupOutOfMustRange": {
pod: failSupplementalGroupPod,
psp: failSupplementalGroupPSP,
psp: failSupplementalGroupMustPSP,
expectedError: "group 999 must be in the ranges: [{1 1}]",
},
"failSupplementalGroupEmpty": {
"failSupplementalGroupOutOfMayRange": {
pod: failSupplementalGroupPod,
psp: failSupplementalGroupMayPSP,
expectedError: "group 999 must be in the ranges: [{50 50} {55 998} {1000 1000}]",
},
"failSupplementalGroupMustEmpty": {
pod: defaultPod(),
psp: failSupplementalGroupPSP,
psp: failSupplementalGroupMustPSP,
expectedError: "unable to validate empty groups against required ranges",
},
"failFSGroupOutOfRange": {
"failFSGroupOutOfMustRange": {
pod: failFSGroupPod,
psp: failFSGroupPSP,
psp: failFSGroupMustPSP,
expectedError: "group 999 must be in the ranges: [{1 1}]",
},
"failFSGroupEmpty": {
"failFSGroupOutOfMayRange": {
pod: failFSGroupPod,
psp: failFSGroupMayPSP,
expectedError: "group 999 must be in the ranges: [{10 20} {1000 1001}]",
},
"failFSGroupMustEmpty": {
pod: defaultPod(),
psp: failFSGroupPSP,
psp: failFSGroupMustPSP,
expectedError: "unable to validate empty groups against required ranges",
},
"failNilSELinux": {
@ -616,23 +643,37 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
hostIPCPod := defaultPod()
hostIPCPod.Spec.SecurityContext.HostIPC = true
supGroupPSP := defaultPSP()
supGroupPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{
supGroupMustPSP := defaultPSP()
supGroupMustPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyMustRunAs,
Ranges: []policy.IDRange{
{Min: 1, Max: 5},
},
}
supGroupMayPSP := defaultPSP()
supGroupMayPSP.Spec.SupplementalGroups = policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyMayRunAs,
Ranges: []policy.IDRange{
{Min: 1, Max: 5},
},
}
supGroupPod := defaultPod()
supGroupPod.Spec.SecurityContext.SupplementalGroups = []int64{3}
fsGroupPSP := defaultPSP()
fsGroupPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{
fsGroupMustPSP := defaultPSP()
fsGroupMustPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyMustRunAs,
Ranges: []policy.IDRange{
{Min: 1, Max: 5},
},
}
fsGroupMayPSP := defaultPSP()
fsGroupMayPSP.Spec.FSGroup = policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyMayRunAs,
Ranges: []policy.IDRange{
{Min: 1, Max: 5},
},
}
fsGroupPod := defaultPod()
fsGroup := int64(3)
fsGroupPod.Spec.SecurityContext.FSGroup = &fsGroup
@ -793,13 +834,29 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
pod: hostIPCPod,
psp: hostIPCPSP,
},
"pass supplemental group validating PSP": {
"pass required supplemental group validating PSP": {
pod: supGroupPod,
psp: supGroupPSP,
psp: supGroupMustPSP,
},
"pass fs group validating PSP": {
"pass optional supplemental group validation PSP": {
pod: supGroupPod,
psp: supGroupMayPSP,
},
"pass optional supplemental group validation PSP - no pod group specified": {
pod: defaultPod(),
psp: supGroupMayPSP,
},
"pass required fs group validating PSP": {
pod: fsGroupPod,
psp: fsGroupPSP,
psp: fsGroupMustPSP,
},
"pass optional fs group validating PSP": {
pod: fsGroupPod,
psp: fsGroupMayPSP,
},
"pass optional fs group validating PSP - no pod group specified": {
pod: defaultPod(),
psp: fsGroupMayPSP,
},
"pass selinux validating PSP": {
pod: seLinuxPod,