mirror of https://github.com/k3s-io/k3s
Promote sysctl annotations to API fields
parent
c178c7fd65
commit
ab616a88b9
|
@ -56,20 +56,6 @@ const (
|
|||
// in the Annotations of a Node.
|
||||
PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"
|
||||
|
||||
// SysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
|
||||
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
|
||||
// key-value pairs. Only a limited set of whitelisted and isolated sysctls is supported by
|
||||
// the kubelet. Pods with other sysctls will fail to launch.
|
||||
SysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/sysctls"
|
||||
|
||||
// UnsafeSysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
|
||||
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
|
||||
// key-value pairs. Unsafe sysctls must be explicitly enabled for a kubelet. They are properly
|
||||
// namespaced to a pod or a container, but their isolation is usually unclear or weak. Their use
|
||||
// is at-your-own-risk. Pods that attempt to set an unsafe sysctl that is not enabled for a kubelet
|
||||
// will fail to launch.
|
||||
UnsafeSysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/unsafe-sysctls"
|
||||
|
||||
// ObjectTTLAnnotations represents a suggestion for kubelet for how long it can cache
|
||||
// an object (e.g. secret, config map) before fetching it again from apiserver.
|
||||
// This annotation can be attached to node.
|
||||
|
|
|
@ -499,54 +499,6 @@ func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]core.Taint,
|
|||
return taints, nil
|
||||
}
|
||||
|
||||
// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls
|
||||
// and a slice of unsafe Sysctls. This is only a convenience wrapper around
|
||||
// SysctlsFromPodAnnotation.
|
||||
func SysctlsFromPodAnnotations(a map[string]string) ([]core.Sysctl, []core.Sysctl, error) {
|
||||
safe, err := SysctlsFromPodAnnotation(a[core.SysctlsPodAnnotationKey])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
unsafe, err := SysctlsFromPodAnnotation(a[core.UnsafeSysctlsPodAnnotationKey])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return safe, unsafe, nil
|
||||
}
|
||||
|
||||
// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls.
|
||||
func SysctlsFromPodAnnotation(annotation string) ([]core.Sysctl, error) {
|
||||
if len(annotation) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
kvs := strings.Split(annotation, ",")
|
||||
sysctls := make([]core.Sysctl, len(kvs))
|
||||
for i, kv := range kvs {
|
||||
cs := strings.Split(kv, "=")
|
||||
if len(cs) != 2 || len(cs[0]) == 0 {
|
||||
return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv)
|
||||
}
|
||||
sysctls[i].Name = cs[0]
|
||||
sysctls[i].Value = cs[1]
|
||||
}
|
||||
return sysctls, nil
|
||||
}
|
||||
|
||||
// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls.
|
||||
func PodAnnotationsFromSysctls(sysctls []core.Sysctl) string {
|
||||
if len(sysctls) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
kvs := make([]string, len(sysctls))
|
||||
for i := range sysctls {
|
||||
kvs[i] = fmt.Sprintf("%s=%s", sysctls[i].Name, sysctls[i].Value)
|
||||
}
|
||||
return strings.Join(kvs, ",")
|
||||
}
|
||||
|
||||
// GetPersistentVolumeClass returns StorageClassName.
|
||||
func GetPersistentVolumeClass(volume *core.PersistentVolume) string {
|
||||
// Use beta annotation first
|
||||
|
|
|
@ -239,53 +239,6 @@ func TestNodeSelectorRequirementsAsSelector(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSysctlsFromPodAnnotation(t *testing.T) {
|
||||
type Test struct {
|
||||
annotation string
|
||||
expectValue []core.Sysctl
|
||||
expectErr bool
|
||||
}
|
||||
for i, test := range []Test{
|
||||
{
|
||||
annotation: "",
|
||||
expectValue: nil,
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
annotation: "=123",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=",
|
||||
expectValue: []core.Sysctl{{Name: "foo.bar", Value: ""}},
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=42",
|
||||
expectValue: []core.Sysctl{{Name: "foo.bar", Value: "42"}},
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=42,",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=42,abc.def=1",
|
||||
expectValue: []core.Sysctl{{Name: "foo.bar", Value: "42"}, {Name: "abc.def", Value: "1"}},
|
||||
},
|
||||
} {
|
||||
sysctls, err := SysctlsFromPodAnnotation(test.annotation)
|
||||
if test.expectErr && err == nil {
|
||||
t.Errorf("[%v]expected error but got none", i)
|
||||
} else if !test.expectErr && err != nil {
|
||||
t.Errorf("[%v]did not expect error but got: %v", i, err)
|
||||
} else if !reflect.DeepEqual(sysctls, test.expectValue) {
|
||||
t.Errorf("[%v]expect value %v but got %v", i, test.expectValue, sysctls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsHugePageResourceName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name core.ResourceName
|
||||
|
|
|
@ -2657,6 +2657,10 @@ type PodSecurityContext struct {
|
|||
// If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
||||
// +optional
|
||||
FSGroup *int64
|
||||
// Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported
|
||||
// sysctls (by the container runtime) might fail to launch.
|
||||
// +optional
|
||||
Sysctls []Sysctl
|
||||
}
|
||||
|
||||
// PodQOSClass defines the supported qos classes of Pods.
|
||||
|
|
|
@ -495,6 +495,14 @@ func Convert_core_PodSecurityContext_To_v1_PodSecurityContext(in *core.PodSecuri
|
|||
out.RunAsGroup = in.RunAsGroup
|
||||
out.RunAsNonRoot = in.RunAsNonRoot
|
||||
out.FSGroup = in.FSGroup
|
||||
if in.Sysctls != nil {
|
||||
out.Sysctls = make([]v1.Sysctl, len(in.Sysctls))
|
||||
for i, sysctl := range in.Sysctls {
|
||||
if err := Convert_core_Sysctl_To_v1_Sysctl(&sysctl, &out.Sysctls[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -512,6 +520,15 @@ func Convert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecurity
|
|||
out.RunAsGroup = in.RunAsGroup
|
||||
out.RunAsNonRoot = in.RunAsNonRoot
|
||||
out.FSGroup = in.FSGroup
|
||||
if in.Sysctls != nil {
|
||||
out.Sysctls = make([]core.Sysctl, len(in.Sysctls))
|
||||
for i, sysctl := range in.Sysctls {
|
||||
if err := Convert_v1_Sysctl_To_core_Sysctl(&sysctl, &out.Sysctls[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -462,54 +462,6 @@ func GetAvoidPodsFromNodeAnnotations(annotations map[string]string) (v1.AvoidPod
|
|||
return avoidPods, nil
|
||||
}
|
||||
|
||||
// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls
|
||||
// and a slice of unsafe Sysctls. This is only a convenience wrapper around
|
||||
// SysctlsFromPodAnnotation.
|
||||
func SysctlsFromPodAnnotations(a map[string]string) ([]v1.Sysctl, []v1.Sysctl, error) {
|
||||
safe, err := SysctlsFromPodAnnotation(a[v1.SysctlsPodAnnotationKey])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
unsafe, err := SysctlsFromPodAnnotation(a[v1.UnsafeSysctlsPodAnnotationKey])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return safe, unsafe, nil
|
||||
}
|
||||
|
||||
// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls.
|
||||
func SysctlsFromPodAnnotation(annotation string) ([]v1.Sysctl, error) {
|
||||
if len(annotation) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
kvs := strings.Split(annotation, ",")
|
||||
sysctls := make([]v1.Sysctl, len(kvs))
|
||||
for i, kv := range kvs {
|
||||
cs := strings.Split(kv, "=")
|
||||
if len(cs) != 2 || len(cs[0]) == 0 {
|
||||
return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv)
|
||||
}
|
||||
sysctls[i].Name = cs[0]
|
||||
sysctls[i].Value = cs[1]
|
||||
}
|
||||
return sysctls, nil
|
||||
}
|
||||
|
||||
// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls.
|
||||
func PodAnnotationsFromSysctls(sysctls []v1.Sysctl) string {
|
||||
if len(sysctls) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
kvs := make([]string, len(sysctls))
|
||||
for i := range sysctls {
|
||||
kvs[i] = fmt.Sprintf("%s=%s", sysctls[i].Name, sysctls[i].Value)
|
||||
}
|
||||
return strings.Join(kvs, ",")
|
||||
}
|
||||
|
||||
// GetPersistentVolumeClass returns StorageClassName.
|
||||
func GetPersistentVolumeClass(volume *v1.PersistentVolume) string {
|
||||
// Use beta annotation first
|
||||
|
|
|
@ -582,53 +582,6 @@ func TestGetAvoidPodsFromNode(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSysctlsFromPodAnnotation(t *testing.T) {
|
||||
type Test struct {
|
||||
annotation string
|
||||
expectValue []v1.Sysctl
|
||||
expectErr bool
|
||||
}
|
||||
for i, test := range []Test{
|
||||
{
|
||||
annotation: "",
|
||||
expectValue: nil,
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
annotation: "=123",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=",
|
||||
expectValue: []v1.Sysctl{{Name: "foo.bar", Value: ""}},
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=42",
|
||||
expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}},
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=42,",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
annotation: "foo.bar=42,abc.def=1",
|
||||
expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}, {Name: "abc.def", Value: "1"}},
|
||||
},
|
||||
} {
|
||||
sysctls, err := SysctlsFromPodAnnotation(test.annotation)
|
||||
if test.expectErr && err == nil {
|
||||
t.Errorf("[%v]expected error but got none", i)
|
||||
} else if !test.expectErr && err != nil {
|
||||
t.Errorf("[%v]did not expect error but got: %v", i, err)
|
||||
} else if !reflect.DeepEqual(sysctls, test.expectValue) {
|
||||
t.Errorf("[%v]expect value %v but got %v", i, test.expectValue, sysctls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchNodeSelectorTerms(t *testing.T) {
|
||||
type args struct {
|
||||
nodeSelectorTerms []v1.NodeSelectorTerm
|
||||
|
|
|
@ -129,23 +129,6 @@ func ValidatePodSpecificAnnotations(annotations map[string]string, spec *core.Po
|
|||
allErrs = append(allErrs, ValidateSeccompPodAnnotations(annotations, fldPath)...)
|
||||
allErrs = append(allErrs, ValidateAppArmorPodAnnotations(annotations, spec, fldPath)...)
|
||||
|
||||
sysctls, err := helper.SysctlsFromPodAnnotation(annotations[core.SysctlsPodAnnotationKey])
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Key(core.SysctlsPodAnnotationKey), annotations[core.SysctlsPodAnnotationKey], err.Error()))
|
||||
} else {
|
||||
allErrs = append(allErrs, validateSysctls(sysctls, fldPath.Key(core.SysctlsPodAnnotationKey))...)
|
||||
}
|
||||
unsafeSysctls, err := helper.SysctlsFromPodAnnotation(annotations[core.UnsafeSysctlsPodAnnotationKey])
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Key(core.UnsafeSysctlsPodAnnotationKey), annotations[core.UnsafeSysctlsPodAnnotationKey], err.Error()))
|
||||
} else {
|
||||
allErrs = append(allErrs, validateSysctls(unsafeSysctls, fldPath.Key(core.UnsafeSysctlsPodAnnotationKey))...)
|
||||
}
|
||||
inBoth := sysctlIntersection(sysctls, unsafeSysctls)
|
||||
if len(inBoth) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Key(core.UnsafeSysctlsPodAnnotationKey), strings.Join(inBoth, ", "), "can not be safe and unsafe"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@ -3364,12 +3347,16 @@ func IsValidSysctlName(name string) bool {
|
|||
|
||||
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
names := make(map[string]struct{})
|
||||
for i, s := range sysctls {
|
||||
if len(s.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), ""))
|
||||
} else if !IsValidSysctlName(s.Name) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, SysctlFmt)))
|
||||
} else if _, ok := names[s.Name]; ok {
|
||||
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("name"), s.Name))
|
||||
}
|
||||
names[s.Name] = struct{}{}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
@ -3408,6 +3395,10 @@ func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *
|
|||
allErrs = append(allErrs, field.Invalid(fldPath.Child("shareProcessNamespace"), *securityContext.ShareProcessNamespace, "ShareProcessNamespace and HostPID cannot both be enabled"))
|
||||
}
|
||||
}
|
||||
|
||||
if len(securityContext.Sysctls) != 0 {
|
||||
allErrs = append(allErrs, validateSysctls(securityContext.Sysctls, fldPath.Child("sysctls"))...)
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
@ -5279,20 +5270,6 @@ func ValidateLoadBalancerStatus(status *core.LoadBalancerStatus, fldPath *field.
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func sysctlIntersection(a []core.Sysctl, b []core.Sysctl) []string {
|
||||
lookup := make(map[string]struct{}, len(a))
|
||||
result := []string{}
|
||||
for i := range a {
|
||||
lookup[a[i].Name] = struct{}{}
|
||||
}
|
||||
for i := range b {
|
||||
if _, found := lookup[b[i].Name]; found {
|
||||
result = append(result, b[i].Name)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// validateVolumeNodeAffinity tests that the PersistentVolume.NodeAffinity has valid data
|
||||
// returns:
|
||||
// - true if volumeNodeAffinity is set
|
||||
|
|
|
@ -6778,12 +6778,28 @@ func TestValidatePod(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
core.SysctlsPodAnnotationKey: "kernel.shmmni=32768,kernel.shmmax=1000000000",
|
||||
core.UnsafeSysctlsPodAnnotationKey: "knet.ipv4.route.min_pmtu=1000",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
Sysctls: []core.Sysctl{
|
||||
{
|
||||
Name: "kernel.shmmni",
|
||||
Value: "32768",
|
||||
},
|
||||
{
|
||||
Name: "kernel.shmmax",
|
||||
Value: "1000000000",
|
||||
},
|
||||
{
|
||||
Name: "knet.ipv4.route.min_pmtu",
|
||||
Value: "1000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
{ // valid extended resources for init container
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
|
||||
|
@ -7464,59 +7480,6 @@ func TestValidatePod(t *testing.T) {
|
|||
Spec: validPodSpec(nil),
|
||||
},
|
||||
},
|
||||
"invalid sysctl annotation": {
|
||||
expectedError: "metadata.annotations[security.alpha.kubernetes.io/sysctls]",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
core.SysctlsPodAnnotationKey: "foo:",
|
||||
},
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
},
|
||||
"invalid comma-separated sysctl annotation": {
|
||||
expectedError: "not of the format sysctl_name=value",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
core.SysctlsPodAnnotationKey: "kernel.msgmax,",
|
||||
},
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
},
|
||||
"invalid unsafe sysctl annotation": {
|
||||
expectedError: "metadata.annotations[security.alpha.kubernetes.io/sysctls]",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
core.SysctlsPodAnnotationKey: "foo:",
|
||||
},
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
},
|
||||
"intersecting safe sysctls and unsafe sysctls annotations": {
|
||||
expectedError: "can not be safe and unsafe",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
core.SysctlsPodAnnotationKey: "kernel.shmmax=10000000",
|
||||
core.UnsafeSysctlsPodAnnotationKey: "kernel.shmmax=10000000",
|
||||
},
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
},
|
||||
"invalid extended resource requirement: request must be == limit": {
|
||||
expectedError: "must be equal to example.com/a",
|
||||
spec: core.Pod{
|
||||
|
@ -12805,6 +12768,11 @@ func TestValidateSysctls(t *testing.T) {
|
|||
"_invalid",
|
||||
}
|
||||
|
||||
duplicates := []string{
|
||||
"kernel.shmmax",
|
||||
"kernel.shmmax",
|
||||
}
|
||||
|
||||
sysctls := make([]core.Sysctl, len(valid))
|
||||
for i, sysctl := range valid {
|
||||
sysctls[i].Name = sysctl
|
||||
|
@ -12829,6 +12797,17 @@ func TestValidateSysctls(t *testing.T) {
|
|||
t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
sysctls = make([]core.Sysctl, len(duplicates))
|
||||
for i, sysctl := range duplicates {
|
||||
sysctls[i].Name = sysctl
|
||||
}
|
||||
errs = validateSysctls(sysctls, field.NewPath("foo"))
|
||||
if len(errs) != 1 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
} else if errs[0].Type != field.ErrorTypeDuplicate {
|
||||
t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
|
||||
}
|
||||
}
|
||||
|
||||
func newNodeNameEndpoint(nodeName string) *core.Endpoints {
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
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 policy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SysctlsFromPodSecurityPolicyAnnotation parses an annotation value of the key
|
||||
// SysctlsSecurityPolicyAnnotationKey into a slice of sysctls. An empty slice
|
||||
// is returned if annotation is the empty string.
|
||||
func SysctlsFromPodSecurityPolicyAnnotation(annotation string) ([]string, error) {
|
||||
if len(annotation) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return strings.Split(annotation, ","), nil
|
||||
}
|
||||
|
||||
// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls.
|
||||
func PodAnnotationsFromSysctls(sysctls []string) string {
|
||||
return strings.Join(sysctls, ",")
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
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 policy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPodAnnotationsFromSysctls(t *testing.T) {
|
||||
type Test struct {
|
||||
sysctls []string
|
||||
expectedValue string
|
||||
}
|
||||
for _, test := range []Test{
|
||||
{sysctls: []string{"a.b"}, expectedValue: "a.b"},
|
||||
{sysctls: []string{"a.b", "c.d"}, expectedValue: "a.b,c.d"},
|
||||
{sysctls: []string{"a.b", "a.b"}, expectedValue: "a.b,a.b"},
|
||||
{sysctls: []string{}, expectedValue: ""},
|
||||
{sysctls: nil, expectedValue: ""},
|
||||
} {
|
||||
a := PodAnnotationsFromSysctls(test.sysctls)
|
||||
if a != test.expectedValue {
|
||||
t.Errorf("wrong value for %v: got=%q wanted=%q", test.sysctls, a, test.expectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysctlsFromPodSecurityPolicyAnnotation(t *testing.T) {
|
||||
type Test struct {
|
||||
expectedValue []string
|
||||
annotation string
|
||||
}
|
||||
for _, test := range []Test{
|
||||
{annotation: "a.b", expectedValue: []string{"a.b"}},
|
||||
{annotation: "a.b,c.d", expectedValue: []string{"a.b", "c.d"}},
|
||||
{annotation: "a.b,a.b", expectedValue: []string{"a.b", "a.b"}},
|
||||
{annotation: "", expectedValue: []string{}},
|
||||
} {
|
||||
sysctls, err := SysctlsFromPodSecurityPolicyAnnotation(test.annotation)
|
||||
if err != nil {
|
||||
t.Errorf("error for %q: %v", test.annotation, err)
|
||||
}
|
||||
if !reflect.DeepEqual(sysctls, test.expectedValue) {
|
||||
t.Errorf("wrong value for %q: got=%v wanted=%v", test.annotation, sysctls, test.expectedValue)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,13 +22,6 @@ import (
|
|||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
const (
|
||||
// SysctlsPodSecurityPolicyAnnotationKey represents the key of a whitelist of
|
||||
// allowed safe and unsafe sysctls in a pod spec. It's a comma-separated list of plain sysctl
|
||||
// names or sysctl patterns (which end in *). The string "*" matches all sysctls.
|
||||
SysctlsPodSecurityPolicyAnnotationKey string = "security.alpha.kubernetes.io/sysctls"
|
||||
)
|
||||
|
||||
// PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.
|
||||
type PodDisruptionBudgetSpec struct {
|
||||
// An eviction is allowed if at least "minAvailable" pods selected by
|
||||
|
@ -215,6 +208,25 @@ type PodSecurityPolicySpec struct {
|
|||
// is allowed in the "Volumes" field.
|
||||
// +optional
|
||||
AllowedFlexVolumes []AllowedFlexVolume
|
||||
// AllowedUnsafeSysctls is a list of explicitly allowed unsafe sysctls, defaults to none.
|
||||
// Each entry is either a plain sysctl name or ends in "*" in which case it is considered
|
||||
// as a prefix of allowed sysctls. Single * means all unsafe sysctls are allowed.
|
||||
// Kubelet has to whitelist all allowed unsafe sysctls explicitly to avoid rejection.
|
||||
//
|
||||
// Examples:
|
||||
// e.g. "foo/*" allows "foo/bar", "foo/baz", etc.
|
||||
// e.g. "foo.*" allows "foo.bar", "foo.baz", etc.
|
||||
// +optional
|
||||
AllowedUnsafeSysctls []string
|
||||
// ForbiddenSysctls is a list of explicitly forbidden sysctls, defaults to none.
|
||||
// Each entry is either a plain sysctl name or ends in "*" in which case it is considered
|
||||
// as a prefix of forbidden sysctls. Single * means all sysctls are forbidden.
|
||||
//
|
||||
// Examples:
|
||||
// e.g. "foo/*" forbids "foo/bar", "foo/baz", etc.
|
||||
// e.g. "foo.*" forbids "foo.bar", "foo.baz", etc.
|
||||
// +optional
|
||||
ForbiddenSysctls []string
|
||||
}
|
||||
|
||||
// AllowedHostPath defines the host volume conditions that will be enabled by a policy
|
||||
|
|
|
@ -118,6 +118,9 @@ func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, fldPath *
|
|||
allErrs = append(allErrs, validatePSPDefaultAllowPrivilegeEscalation(fldPath.Child("defaultAllowPrivilegeEscalation"), spec.DefaultAllowPrivilegeEscalation, spec.AllowPrivilegeEscalation)...)
|
||||
allErrs = append(allErrs, validatePSPAllowedHostPaths(fldPath.Child("allowedHostPaths"), spec.AllowedHostPaths)...)
|
||||
allErrs = append(allErrs, validatePSPAllowedFlexVolumes(fldPath.Child("allowedFlexVolumes"), spec.AllowedFlexVolumes)...)
|
||||
allErrs = append(allErrs, validatePodSecurityPolicySysctls(fldPath.Child("allowedUnsafeSysctls"), spec.AllowedUnsafeSysctls)...)
|
||||
allErrs = append(allErrs, validatePodSecurityPolicySysctls(fldPath.Child("forbiddenSysctls"), spec.ForbiddenSysctls)...)
|
||||
allErrs = append(allErrs, validatePodSecurityPolicySysctlListsDoNotOverlap(fldPath.Child("allowedUnsafeSysctls"), fldPath.Child("forbiddenSysctls"), spec.AllowedUnsafeSysctls, spec.ForbiddenSysctls)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
@ -138,15 +141,6 @@ func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string,
|
|||
}
|
||||
}
|
||||
|
||||
sysctlAnnotation := annotations[policy.SysctlsPodSecurityPolicyAnnotationKey]
|
||||
sysctlFldPath := fldPath.Key(policy.SysctlsPodSecurityPolicyAnnotationKey)
|
||||
sysctls, err := policy.SysctlsFromPodSecurityPolicyAnnotation(sysctlAnnotation)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(sysctlFldPath, sysctlAnnotation, err.Error()))
|
||||
} else {
|
||||
allErrs = append(allErrs, validatePodSecurityPolicySysctls(sysctlFldPath, sysctls)...)
|
||||
}
|
||||
|
||||
if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" {
|
||||
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...)
|
||||
}
|
||||
|
@ -307,11 +301,55 @@ func IsValidSysctlPattern(name string) bool {
|
|||
return sysctlPatternRegexp.MatchString(name)
|
||||
}
|
||||
|
||||
func validatePodSecurityPolicySysctlListsDoNotOverlap(allowedSysctlsFldPath, forbiddenSysctlsFldPath *field.Path, allowedUnsafeSysctls, forbiddenSysctls []string) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i, allowedSysctl := range allowedUnsafeSysctls {
|
||||
isAllowedSysctlPattern := false
|
||||
allowedSysctlPrefix := ""
|
||||
if strings.HasSuffix(allowedSysctl, "*") {
|
||||
isAllowedSysctlPattern = true
|
||||
allowedSysctlPrefix = strings.TrimSuffix(allowedSysctl, "*")
|
||||
}
|
||||
for j, forbiddenSysctl := range forbiddenSysctls {
|
||||
isForbiddenSysctlPattern := false
|
||||
forbiddenSysctlPrefix := ""
|
||||
if strings.HasSuffix(forbiddenSysctl, "*") {
|
||||
isForbiddenSysctlPattern = true
|
||||
forbiddenSysctlPrefix = strings.TrimSuffix(forbiddenSysctl, "*")
|
||||
}
|
||||
switch {
|
||||
case isAllowedSysctlPattern && isForbiddenSysctlPattern:
|
||||
if strings.HasPrefix(allowedSysctlPrefix, forbiddenSysctlPrefix) {
|
||||
allErrs = append(allErrs, field.Invalid(allowedSysctlsFldPath.Index(i), allowedUnsafeSysctls[i], fmt.Sprintf("sysctl overlaps with %v", forbiddenSysctl)))
|
||||
} else if strings.HasPrefix(forbiddenSysctlPrefix, allowedSysctlPrefix) {
|
||||
allErrs = append(allErrs, field.Invalid(forbiddenSysctlsFldPath.Index(j), forbiddenSysctls[j], fmt.Sprintf("sysctl overlaps with %v", allowedSysctl)))
|
||||
}
|
||||
case isAllowedSysctlPattern:
|
||||
if strings.HasPrefix(forbiddenSysctl, allowedSysctlPrefix) {
|
||||
allErrs = append(allErrs, field.Invalid(forbiddenSysctlsFldPath.Index(j), forbiddenSysctls[j], fmt.Sprintf("sysctl overlaps with %v", allowedSysctl)))
|
||||
}
|
||||
case isForbiddenSysctlPattern:
|
||||
if strings.HasPrefix(allowedSysctl, forbiddenSysctlPrefix) {
|
||||
allErrs = append(allErrs, field.Invalid(allowedSysctlsFldPath.Index(i), allowedUnsafeSysctls[i], fmt.Sprintf("sysctl overlaps with %v", forbiddenSysctl)))
|
||||
}
|
||||
default:
|
||||
if allowedSysctl == forbiddenSysctl {
|
||||
allErrs = append(allErrs, field.Invalid(allowedSysctlsFldPath.Index(i), allowedUnsafeSysctls[i], fmt.Sprintf("sysctl overlaps with %v", forbiddenSysctl)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePodSecurityPolicySysctls validates the sysctls fields of PodSecurityPolicy.
|
||||
func validatePodSecurityPolicySysctls(fldPath *field.Path, sysctls []string) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
coversAll := false
|
||||
for i, s := range sysctls {
|
||||
if !IsValidSysctlPattern(string(s)) {
|
||||
if len(s) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), sysctls[i], fmt.Sprintf("empty sysctl not allowed")))
|
||||
} else if !IsValidSysctlPattern(string(s)) {
|
||||
allErrs = append(
|
||||
allErrs,
|
||||
field.Invalid(fldPath.Index(i), sysctls[i], fmt.Sprintf("must have at most %d characters and match regex %s",
|
||||
|
@ -319,9 +357,15 @@ func validatePodSecurityPolicySysctls(fldPath *field.Path, sysctls []string) fie
|
|||
SysctlPatternFmt,
|
||||
)),
|
||||
)
|
||||
} else if s[0] == '*' {
|
||||
coversAll = true
|
||||
}
|
||||
}
|
||||
|
||||
if coversAll && len(sysctls) > 1 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("items"), fmt.Sprintf("if '*' is present, must not specify other sysctls")))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
|
|
@ -323,8 +323,19 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + ",not-good",
|
||||
}
|
||||
|
||||
invalidSysctlPattern := validPSP()
|
||||
invalidSysctlPattern.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "a.*.b"
|
||||
invalidAllowedUnsafeSysctlPattern := validPSP()
|
||||
invalidAllowedUnsafeSysctlPattern.Spec.AllowedUnsafeSysctls = []string{"a.*.b"}
|
||||
|
||||
invalidForbiddenSysctlPattern := validPSP()
|
||||
invalidForbiddenSysctlPattern.Spec.ForbiddenSysctls = []string{"a.*.b"}
|
||||
|
||||
invalidOverlappingSysctls := validPSP()
|
||||
invalidOverlappingSysctls.Spec.ForbiddenSysctls = []string{"kernel.*", "net.ipv4.ip_local_port_range"}
|
||||
invalidOverlappingSysctls.Spec.AllowedUnsafeSysctls = []string{"kernel.shmmax", "net.ipv4.ip_local_port_range"}
|
||||
|
||||
invalidDuplicatedSysctls := validPSP()
|
||||
invalidDuplicatedSysctls.Spec.ForbiddenSysctls = []string{"net.ipv4.ip_local_port_range"}
|
||||
invalidDuplicatedSysctls.Spec.AllowedUnsafeSysctls = []string{"net.ipv4.ip_local_port_range"}
|
||||
|
||||
invalidSeccompDefault := validPSP()
|
||||
invalidSeccompDefault.Annotations = map[string]string{
|
||||
|
@ -456,11 +467,26 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: "invalid AppArmor profile name: \"not-good\"",
|
||||
},
|
||||
"invalid sysctl pattern": {
|
||||
psp: invalidSysctlPattern,
|
||||
"invalid allowed unsafe sysctl pattern": {
|
||||
psp: invalidAllowedUnsafeSysctlPattern,
|
||||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
|
||||
},
|
||||
"invalid forbidden sysctl pattern": {
|
||||
psp: invalidForbiddenSysctlPattern,
|
||||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
|
||||
},
|
||||
"invalid overlapping sysctl pattern": {
|
||||
psp: invalidOverlappingSysctls,
|
||||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: fmt.Sprintf("sysctl overlaps with %s", invalidOverlappingSysctls.Spec.ForbiddenSysctls[0]),
|
||||
},
|
||||
"invalid duplicated sysctls": {
|
||||
psp: invalidDuplicatedSysctls,
|
||||
errorType: field.ErrorTypeInvalid,
|
||||
errorDetail: fmt.Sprintf("sysctl overlaps with %s", invalidDuplicatedSysctls.Spec.AllowedUnsafeSysctls[0]),
|
||||
},
|
||||
"invalid seccomp default profile": {
|
||||
psp: invalidSeccompDefault,
|
||||
errorType: field.ErrorTypeInvalid,
|
||||
|
@ -561,8 +587,11 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo",
|
||||
}
|
||||
|
||||
withSysctl := validPSP()
|
||||
withSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "net.*"
|
||||
withForbiddenSysctl := validPSP()
|
||||
withForbiddenSysctl.Spec.ForbiddenSysctls = []string{"net.*"}
|
||||
|
||||
withAllowedUnsafeSysctl := validPSP()
|
||||
withAllowedUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"net.ipv4.tcp_max_syn_backlog"}
|
||||
|
||||
validSeccomp := validPSP()
|
||||
validSeccomp.Annotations = map[string]string{
|
||||
|
@ -607,8 +636,11 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||
"valid AppArmor annotations": {
|
||||
psp: validAppArmor,
|
||||
},
|
||||
"with network sysctls": {
|
||||
psp: withSysctl,
|
||||
"with network sysctls forbidden": {
|
||||
psp: withForbiddenSysctl,
|
||||
},
|
||||
"with unsafe net.ipv4.tcp_max_syn_backlog sysctl allowed": {
|
||||
psp: withAllowedUnsafeSysctl,
|
||||
},
|
||||
"valid seccomp annotations": {
|
||||
psp: validSeccomp,
|
||||
|
|
|
@ -100,6 +100,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/kubelet/volumemanager"
|
||||
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
sysctlwhitelist "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
|
||||
utildbus "k8s.io/kubernetes/pkg/util/dbus"
|
||||
kubeio "k8s.io/kubernetes/pkg/util/io"
|
||||
utilipt "k8s.io/kubernetes/pkg/util/iptables"
|
||||
|
@ -837,20 +838,16 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
safeWhitelist, err := sysctl.NewWhitelist(sysctl.SafeSysctlWhitelist(), v1.SysctlsPodAnnotationKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Safe, whitelisted sysctls can always be used as unsafe sysctls in the spec
|
||||
|
||||
// Safe, whitelisted sysctls can always be used as unsafe sysctls in the spec.
|
||||
// Hence, we concatenate those two lists.
|
||||
safeAndUnsafeSysctls := append(sysctl.SafeSysctlWhitelist(), allowedUnsafeSysctls...)
|
||||
unsafeWhitelist, err := sysctl.NewWhitelist(safeAndUnsafeSysctls, v1.UnsafeSysctlsPodAnnotationKey)
|
||||
safeAndUnsafeSysctls := append(sysctlwhitelist.SafeSysctlWhitelist(), allowedUnsafeSysctls...)
|
||||
sysctlsWhitelist, err := sysctl.NewWhitelist(safeAndUnsafeSysctls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klet.admitHandlers.AddPodAdmitHandler(runtimeSupport)
|
||||
klet.admitHandlers.AddPodAdmitHandler(safeWhitelist)
|
||||
klet.admitHandlers.AddPodAdmitHandler(unsafeWhitelist)
|
||||
klet.admitHandlers.AddPodAdmitHandler(sysctlsWhitelist)
|
||||
|
||||
// enable active deadline handler
|
||||
activeDeadlineHandler, err := newActiveDeadlineHandler(klet.statusManager, kubeDeps.Recorder, klet.clock)
|
||||
|
|
|
@ -26,7 +26,6 @@ import (
|
|||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
|
@ -191,24 +190,6 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus) *kubecontainer.Runtim
|
|||
return &kubecontainer.RuntimeStatus{Conditions: conditions}
|
||||
}
|
||||
|
||||
// getSysctlsFromAnnotations gets sysctls and unsafeSysctls from annotations.
|
||||
func getSysctlsFromAnnotations(annotations map[string]string) (map[string]string, error) {
|
||||
apiSysctls, apiUnsafeSysctls, err := v1helper.SysctlsFromPodAnnotations(annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sysctls := make(map[string]string)
|
||||
for _, c := range apiSysctls {
|
||||
sysctls[c.Name] = c.Value
|
||||
}
|
||||
for _, c := range apiUnsafeSysctls {
|
||||
sysctls[c.Name] = c.Value
|
||||
}
|
||||
|
||||
return sysctls, nil
|
||||
}
|
||||
|
||||
// getSeccompProfileFromAnnotations gets seccomp profile from annotations.
|
||||
// It gets pod's profile if containerName is empty.
|
||||
func (m *kubeGenericRuntimeManager) getSeccompProfileFromAnnotations(annotations map[string]string, containerName string) string {
|
||||
|
|
|
@ -56,46 +56,6 @@ func TestStableKey(t *testing.T) {
|
|||
assert.NotEqual(t, oldKey, newKey)
|
||||
}
|
||||
|
||||
// TestGetSystclsFromAnnotations tests the logic of getting sysctls from annotations.
|
||||
func TestGetSystclsFromAnnotations(t *testing.T) {
|
||||
tests := []struct {
|
||||
annotations map[string]string
|
||||
expectedSysctls map[string]string
|
||||
}{{
|
||||
annotations: map[string]string{
|
||||
v1.SysctlsPodAnnotationKey: "kernel.shmmni=32768,kernel.shmmax=1000000000",
|
||||
v1.UnsafeSysctlsPodAnnotationKey: "knet.ipv4.route.min_pmtu=1000",
|
||||
},
|
||||
expectedSysctls: map[string]string{
|
||||
"kernel.shmmni": "32768",
|
||||
"kernel.shmmax": "1000000000",
|
||||
"knet.ipv4.route.min_pmtu": "1000",
|
||||
},
|
||||
}, {
|
||||
annotations: map[string]string{
|
||||
v1.SysctlsPodAnnotationKey: "kernel.shmmni=32768,kernel.shmmax=1000000000",
|
||||
},
|
||||
expectedSysctls: map[string]string{
|
||||
"kernel.shmmni": "32768",
|
||||
"kernel.shmmax": "1000000000",
|
||||
},
|
||||
}, {
|
||||
annotations: map[string]string{
|
||||
v1.UnsafeSysctlsPodAnnotationKey: "knet.ipv4.route.min_pmtu=1000",
|
||||
},
|
||||
expectedSysctls: map[string]string{
|
||||
"knet.ipv4.route.min_pmtu": "1000",
|
||||
},
|
||||
}}
|
||||
|
||||
for i, test := range tests {
|
||||
actualSysctls, err := getSysctlsFromAnnotations(test.annotations)
|
||||
assert.NoError(t, err, "TestCase[%d]", i)
|
||||
assert.Len(t, actualSysctls, len(test.expectedSysctls), "TestCase[%d]", i)
|
||||
assert.Equal(t, test.expectedSysctls, actualSysctls, "TestCase[%d]", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToKubeContainer(t *testing.T) {
|
||||
c := &runtimeapi.Container{
|
||||
Id: "test-id",
|
||||
|
|
|
@ -134,10 +134,13 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(pod *v1.Pod) (
|
|||
},
|
||||
}
|
||||
|
||||
sysctls, err := getSysctlsFromAnnotations(pod.Annotations)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get sysctls from annotations %v for pod %q: %v", pod.Annotations, format.Pod(pod), err)
|
||||
sysctls := make(map[string]string)
|
||||
if pod.Spec.SecurityContext != nil {
|
||||
for _, c := range pod.Spec.SecurityContext.Sysctls {
|
||||
sysctls[c.Name] = c.Value
|
||||
}
|
||||
}
|
||||
|
||||
lc.Sysctls = sysctls
|
||||
|
||||
if pod.Spec.SecurityContext != nil {
|
||||
|
|
|
@ -19,7 +19,6 @@ package sysctl
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||
)
|
||||
|
@ -83,17 +82,11 @@ func NewRuntimeAdmitHandler(runtime container.Runtime) (*runtimeAdmitHandler, er
|
|||
|
||||
// Admit checks whether the runtime supports sysctls.
|
||||
func (w *runtimeAdmitHandler) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
|
||||
sysctls, unsafeSysctls, err := v1helper.SysctlsFromPodAnnotations(attrs.Pod.Annotations)
|
||||
if err != nil {
|
||||
return lifecycle.PodAdmitResult{
|
||||
Admit: false,
|
||||
Reason: AnnotationInvalidReason,
|
||||
Message: fmt.Sprintf("invalid sysctl annotation: %v", err),
|
||||
}
|
||||
}
|
||||
if attrs.Pod.Spec.SecurityContext != nil {
|
||||
|
||||
if len(sysctls)+len(unsafeSysctls) > 0 {
|
||||
return w.result
|
||||
if len(attrs.Pod.Spec.SecurityContext.Sysctls) > 0 {
|
||||
return w.result
|
||||
}
|
||||
}
|
||||
|
||||
return lifecycle.PodAdmitResult{
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
policyvalidation "k8s.io/kubernetes/pkg/apis/policy/validation"
|
||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||
|
@ -31,36 +30,21 @@ const (
|
|||
ForbiddenReason = "SysctlForbidden"
|
||||
)
|
||||
|
||||
// SafeSysctlWhitelist returns the whitelist of safe sysctls and safe sysctl patterns (ending in *).
|
||||
//
|
||||
// A sysctl is called safe iff
|
||||
// - it is namespaced in the container or the pod
|
||||
// - it is isolated, i.e. has no influence on any other pod on the same node.
|
||||
func SafeSysctlWhitelist() []string {
|
||||
return []string{
|
||||
"kernel.shm_rmid_forced",
|
||||
"net.ipv4.ip_local_port_range",
|
||||
"net.ipv4.tcp_syncookies",
|
||||
}
|
||||
}
|
||||
|
||||
// patternWhitelist takes a list of sysctls or sysctl patterns (ending in *) and
|
||||
// checks validity via a sysctl and prefix map, rejecting those which are not known
|
||||
// to be namespaced.
|
||||
type patternWhitelist struct {
|
||||
sysctls map[string]Namespace
|
||||
prefixes map[string]Namespace
|
||||
annotationKey string
|
||||
sysctls map[string]Namespace
|
||||
prefixes map[string]Namespace
|
||||
}
|
||||
|
||||
var _ lifecycle.PodAdmitHandler = &patternWhitelist{}
|
||||
|
||||
// NewWhitelist creates a new Whitelist from a list of sysctls and sysctl pattern (ending in *).
|
||||
func NewWhitelist(patterns []string, annotationKey string) (*patternWhitelist, error) {
|
||||
func NewWhitelist(patterns []string) (*patternWhitelist, error) {
|
||||
w := &patternWhitelist{
|
||||
sysctls: map[string]Namespace{},
|
||||
prefixes: map[string]Namespace{},
|
||||
annotationKey: annotationKey,
|
||||
sysctls: map[string]Namespace{},
|
||||
prefixes: map[string]Namespace{},
|
||||
}
|
||||
|
||||
for _, s := range patterns {
|
||||
|
@ -121,32 +105,22 @@ func (w *patternWhitelist) validateSysctl(sysctl string, hostNet, hostIPC bool)
|
|||
return fmt.Errorf("%q not whitelisted", sysctl)
|
||||
}
|
||||
|
||||
// Admit checks that all sysctls given in annotations v1.SysctlsPodAnnotationKey and v1.UnsafeSysctlsPodAnnotationKey
|
||||
// Admit checks that all sysctls given in pod's security context
|
||||
// are valid according to the whitelist.
|
||||
func (w *patternWhitelist) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
|
||||
pod := attrs.Pod
|
||||
a := pod.Annotations[w.annotationKey]
|
||||
if a == "" {
|
||||
if pod.Spec.SecurityContext == nil || len(pod.Spec.SecurityContext.Sysctls) == 0 {
|
||||
return lifecycle.PodAdmitResult{
|
||||
Admit: true,
|
||||
}
|
||||
}
|
||||
|
||||
sysctls, err := v1helper.SysctlsFromPodAnnotation(a)
|
||||
if err != nil {
|
||||
return lifecycle.PodAdmitResult{
|
||||
Admit: false,
|
||||
Reason: AnnotationInvalidReason,
|
||||
Message: fmt.Sprintf("invalid %s annotation: %v", w.annotationKey, err),
|
||||
}
|
||||
}
|
||||
|
||||
var hostNet, hostIPC bool
|
||||
if pod.Spec.SecurityContext != nil {
|
||||
hostNet = pod.Spec.HostNetwork
|
||||
hostIPC = pod.Spec.HostIPC
|
||||
}
|
||||
for _, s := range sysctls {
|
||||
for _, s := range pod.Spec.SecurityContext.Sysctls {
|
||||
if err := w.validateSysctl(s.Name, hostNet, hostIPC); err != nil {
|
||||
return lifecycle.PodAdmitResult{
|
||||
Admit: false,
|
||||
|
|
|
@ -19,7 +19,7 @@ package sysctl
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
|
||||
)
|
||||
|
||||
func TestNewWhitelist(t *testing.T) {
|
||||
|
@ -35,7 +35,7 @@ func TestNewWhitelist(t *testing.T) {
|
|||
{sysctls: []string{"net.*.foo"}, err: true},
|
||||
{sysctls: []string{"foo"}, err: true},
|
||||
} {
|
||||
_, err := NewWhitelist(append(SafeSysctlWhitelist(), test.sysctls...), v1.SysctlsPodAnnotationKey)
|
||||
_, err := NewWhitelist(append(sysctl.SafeSysctlWhitelist(), test.sysctls...))
|
||||
if test.err && err == nil {
|
||||
t.Errorf("expected an error creating a whitelist for %v", test.sysctls)
|
||||
} else if !test.err && err != nil {
|
||||
|
@ -65,7 +65,7 @@ func TestWhitelist(t *testing.T) {
|
|||
{sysctl: "kernel.sem", hostIPC: true},
|
||||
}
|
||||
|
||||
w, err := NewWhitelist(append(SafeSysctlWhitelist(), "kernel.msg*", "kernel.sem"), v1.SysctlsPodAnnotationKey)
|
||||
w, err := NewWhitelist(append(sysctl.SafeSysctlWhitelist(), "kernel.msg*", "kernel.sem"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create whitelist: %v", err)
|
||||
}
|
||||
|
|
|
@ -77,15 +77,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *policy.PodSecurityPolicy,
|
|||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
var unsafeSysctls []string
|
||||
if ann, found := psp.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey]; found {
|
||||
var err error
|
||||
unsafeSysctls, err = policy.SysctlsFromPodSecurityPolicyAnnotation(ann)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
sysctlsStrat := createSysctlsStrategy(unsafeSysctls)
|
||||
sysctlsStrat := createSysctlsStrategy(sysctl.SafeSysctlWhitelist(), psp.Spec.AllowedUnsafeSysctls, psp.Spec.ForbiddenSysctls)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.NewAggregate(errs)
|
||||
|
@ -170,7 +162,7 @@ func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []
|
|||
return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps)
|
||||
}
|
||||
|
||||
// createSysctlsStrategy creates a new unsafe sysctls strategy.
|
||||
func createSysctlsStrategy(sysctlsPatterns []string) sysctl.SysctlsStrategy {
|
||||
return sysctl.NewMustMatchPatterns(sysctlsPatterns)
|
||||
// createSysctlsStrategy creates a new sysctls strategy.
|
||||
func createSysctlsStrategy(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls []string) sysctl.SysctlsStrategy {
|
||||
return sysctl.NewMustMatchPatterns(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls)
|
||||
}
|
||||
|
|
|
@ -267,17 +267,34 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
failOtherSysctlsAllowedPSP := defaultPSP()
|
||||
failOtherSysctlsAllowedPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "bar,abc"
|
||||
failSysctlDisallowedPSP := defaultPSP()
|
||||
failSysctlDisallowedPSP.Spec.ForbiddenSysctls = []string{"kernel.shm_rmid_forced"}
|
||||
|
||||
failNoSysctlAllowedPSP := defaultPSP()
|
||||
failNoSysctlAllowedPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = ""
|
||||
failNoSafeSysctlAllowedPSP := defaultPSP()
|
||||
failNoSafeSysctlAllowedPSP.Spec.ForbiddenSysctls = []string{"*"}
|
||||
|
||||
failSafeSysctlFooPod := defaultPod()
|
||||
failSafeSysctlFooPod.Annotations[api.SysctlsPodAnnotationKey] = "foo=1"
|
||||
failAllUnsafeSysctlsPSP := defaultPSP()
|
||||
failAllUnsafeSysctlsPSP.Spec.AllowedUnsafeSysctls = []string{}
|
||||
|
||||
failUnsafeSysctlFooPod := defaultPod()
|
||||
failUnsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
|
||||
failSafeSysctlKernelPod := defaultPod()
|
||||
failSafeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||
Sysctls: []api.Sysctl{
|
||||
{
|
||||
Name: "kernel.shm_rmid_forced",
|
||||
Value: "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
failUnsafeSysctlPod := defaultPod()
|
||||
failUnsafeSysctlPod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||
Sysctls: []api.Sysctl{
|
||||
{
|
||||
Name: "kernel.sem",
|
||||
Value: "32000",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
failSeccompProfilePod := defaultPod()
|
||||
failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"}
|
||||
|
@ -359,25 +376,20 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||
psp: failHostPathReadOnlyPSP,
|
||||
expectedError: "must be read-only",
|
||||
},
|
||||
"failSafeSysctlFooPod with failNoSysctlAllowedSCC": {
|
||||
pod: failSafeSysctlFooPod,
|
||||
psp: failNoSysctlAllowedPSP,
|
||||
expectedError: "sysctls are not allowed",
|
||||
"failSafeSysctlKernelPod with failNoSafeSysctlAllowedPSP": {
|
||||
pod: failSafeSysctlKernelPod,
|
||||
psp: failNoSafeSysctlAllowedPSP,
|
||||
expectedError: "sysctl \"kernel.shm_rmid_forced\" is not allowed",
|
||||
},
|
||||
"failUnsafeSysctlFooPod with failNoSysctlAllowedSCC": {
|
||||
pod: failUnsafeSysctlFooPod,
|
||||
psp: failNoSysctlAllowedPSP,
|
||||
expectedError: "sysctls are not allowed",
|
||||
"failSafeSysctlKernelPod with failSysctlDisallowedPSP": {
|
||||
pod: failSafeSysctlKernelPod,
|
||||
psp: failSysctlDisallowedPSP,
|
||||
expectedError: "sysctl \"kernel.shm_rmid_forced\" is not allowed",
|
||||
},
|
||||
"failSafeSysctlFooPod with failOtherSysctlsAllowedSCC": {
|
||||
pod: failSafeSysctlFooPod,
|
||||
psp: failOtherSysctlsAllowedPSP,
|
||||
expectedError: "sysctl \"foo\" is not allowed",
|
||||
},
|
||||
"failUnsafeSysctlFooPod with failOtherSysctlsAllowedSCC": {
|
||||
pod: failUnsafeSysctlFooPod,
|
||||
psp: failOtherSysctlsAllowedPSP,
|
||||
expectedError: "sysctl \"foo\" is not allowed",
|
||||
"failUnsafeSysctlPod with failAllUnsafeSysctlsPSP": {
|
||||
pod: failUnsafeSysctlPod,
|
||||
psp: failAllUnsafeSysctlsPSP,
|
||||
expectedError: "unsafe sysctl \"kernel.sem\" is not allowed",
|
||||
},
|
||||
"failInvalidSeccomp": {
|
||||
pod: failSeccompProfilePod,
|
||||
|
@ -707,14 +719,29 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
|
|||
{PathPrefix: "/foo"},
|
||||
}
|
||||
|
||||
sysctlAllowFooPSP := defaultPSP()
|
||||
sysctlAllowFooPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "foo"
|
||||
sysctlAllowAllPSP := defaultPSP()
|
||||
sysctlAllowAllPSP.Spec.ForbiddenSysctls = []string{}
|
||||
sysctlAllowAllPSP.Spec.AllowedUnsafeSysctls = []string{"*"}
|
||||
|
||||
safeSysctlFooPod := defaultPod()
|
||||
safeSysctlFooPod.Annotations[api.SysctlsPodAnnotationKey] = "foo=1"
|
||||
safeSysctlKernelPod := defaultPod()
|
||||
safeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||
Sysctls: []api.Sysctl{
|
||||
{
|
||||
Name: "kernel.shm_rmid_forced",
|
||||
Value: "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
unsafeSysctlFooPod := defaultPod()
|
||||
unsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
|
||||
unsafeSysctlKernelPod := defaultPod()
|
||||
unsafeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||
Sysctls: []api.Sysctl{
|
||||
{
|
||||
Name: "kernel.sem",
|
||||
Value: "32000",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
seccompPSP := defaultPSP()
|
||||
seccompPSP.Annotations = map[string]string{
|
||||
|
@ -766,21 +793,13 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
|
|||
pod: seLinuxPod,
|
||||
psp: seLinuxPSP,
|
||||
},
|
||||
"pass sysctl specific profile with safe sysctl": {
|
||||
pod: safeSysctlFooPod,
|
||||
psp: sysctlAllowFooPSP,
|
||||
"pass sysctl specific profile with safe kernel sysctl": {
|
||||
pod: safeSysctlKernelPod,
|
||||
psp: sysctlAllowAllPSP,
|
||||
},
|
||||
"pass sysctl specific profile with unsafe sysctl": {
|
||||
pod: unsafeSysctlFooPod,
|
||||
psp: sysctlAllowFooPSP,
|
||||
},
|
||||
"pass empty profile with safe sysctl": {
|
||||
pod: safeSysctlFooPod,
|
||||
psp: defaultPSP(),
|
||||
},
|
||||
"pass empty profile with unsafe sysctl": {
|
||||
pod: unsafeSysctlFooPod,
|
||||
psp: defaultPSP(),
|
||||
"pass sysctl specific profile with unsafe kernel sysctl": {
|
||||
pod: unsafeSysctlKernelPod,
|
||||
psp: sysctlAllowAllPSP,
|
||||
},
|
||||
"pass hostDir allowed directory validating PSP": {
|
||||
pod: hostPathDirPod,
|
||||
|
|
|
@ -22,12 +22,26 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
)
|
||||
|
||||
// SafeSysctlWhitelist returns the whitelist of safe sysctls and safe sysctl patterns (ending in *).
|
||||
//
|
||||
// A sysctl is called safe iff
|
||||
// - it is namespaced in the container or the pod
|
||||
// - it is isolated, i.e. has no influence on any other pod on the same node.
|
||||
func SafeSysctlWhitelist() []string {
|
||||
return []string{
|
||||
"kernel.shm_rmid_forced",
|
||||
"net.ipv4.ip_local_port_range",
|
||||
"net.ipv4.tcp_syncookies",
|
||||
}
|
||||
}
|
||||
|
||||
// mustMatchPatterns implements the SysctlsStrategy interface
|
||||
type mustMatchPatterns struct {
|
||||
patterns []string
|
||||
safeWhitelist []string
|
||||
allowedUnsafeSysctls []string
|
||||
forbiddenSysctls []string
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -38,56 +52,75 @@ var (
|
|||
|
||||
// NewMustMatchPatterns creates a new mustMatchPatterns strategy that will provide validation.
|
||||
// Passing nil means the default pattern, passing an empty list means to disallow all sysctls.
|
||||
func NewMustMatchPatterns(patterns []string) SysctlsStrategy {
|
||||
if patterns == nil {
|
||||
patterns = defaultSysctlsPatterns
|
||||
}
|
||||
func NewMustMatchPatterns(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls []string) SysctlsStrategy {
|
||||
return &mustMatchPatterns{
|
||||
patterns: patterns,
|
||||
safeWhitelist: safeWhitelist,
|
||||
allowedUnsafeSysctls: allowedUnsafeSysctls,
|
||||
forbiddenSysctls: forbiddenSysctls,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *mustMatchPatterns) isForbidden(sysctlName string) bool {
|
||||
// Is the sysctl forbidden?
|
||||
for _, s := range s.forbiddenSysctls {
|
||||
if strings.HasSuffix(s, "*") {
|
||||
prefix := strings.TrimSuffix(s, "*")
|
||||
if strings.HasPrefix(sysctlName, prefix) {
|
||||
return true
|
||||
}
|
||||
} else if sysctlName == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *mustMatchPatterns) isSafe(sysctlName string) bool {
|
||||
for _, ws := range s.safeWhitelist {
|
||||
if sysctlName == ws {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *mustMatchPatterns) isAllowedUnsafe(sysctlName string) bool {
|
||||
for _, s := range s.allowedUnsafeSysctls {
|
||||
if strings.HasSuffix(s, "*") {
|
||||
prefix := strings.TrimSuffix(s, "*")
|
||||
if strings.HasPrefix(sysctlName, prefix) {
|
||||
return true
|
||||
}
|
||||
} else if sysctlName == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate ensures that the specified values fall within the range of the strategy.
|
||||
func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, s.validateAnnotation(pod, api.SysctlsPodAnnotationKey)...)
|
||||
allErrs = append(allErrs, s.validateAnnotation(pod, api.UnsafeSysctlsPodAnnotationKey)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (s *mustMatchPatterns) validateAnnotation(pod *api.Pod, key string) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(key)
|
||||
|
||||
sysctls, err := helper.SysctlsFromPodAnnotation(pod.Annotations[key])
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, pod.Annotations[key], err.Error()))
|
||||
var sysctls []api.Sysctl
|
||||
if pod.Spec.SecurityContext != nil {
|
||||
sysctls = pod.Spec.SecurityContext.Sysctls
|
||||
}
|
||||
|
||||
if len(sysctls) > 0 {
|
||||
if len(s.patterns) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, pod.Annotations[key], "sysctls are not allowed"))
|
||||
} else {
|
||||
for i, sysctl := range sysctls {
|
||||
allErrs = append(allErrs, s.ValidateSysctl(sysctl.Name, fieldPath.Index(i))...)
|
||||
}
|
||||
fieldPath := field.NewPath("pod", "spec", "securityContext").Child("sysctls")
|
||||
|
||||
for i, sysctl := range sysctls {
|
||||
switch {
|
||||
case s.isForbidden(sysctl.Name):
|
||||
allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("sysctl %q is not allowed", sysctl.Name))}...)
|
||||
case s.isSafe(sysctl.Name):
|
||||
continue
|
||||
case s.isAllowedUnsafe(sysctl.Name):
|
||||
continue
|
||||
default:
|
||||
allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("unsafe sysctl %q is not allowed", sysctl.Name))}...)
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (s *mustMatchPatterns) ValidateSysctl(sysctlName string, fldPath *field.Path) field.ErrorList {
|
||||
for _, s := range s.patterns {
|
||||
if s[len(s)-1] == '*' {
|
||||
prefix := s[:len(s)-1]
|
||||
if strings.HasPrefix(sysctlName, string(prefix)) {
|
||||
return nil
|
||||
}
|
||||
} else if sysctlName == s {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return field.ErrorList{field.Forbidden(fldPath, fmt.Sprintf("sysctl %q is not allowed", sysctlName))}
|
||||
}
|
||||
|
|
|
@ -17,48 +17,47 @@ limitations under the License.
|
|||
package sysctl
|
||||
|
||||
import (
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
patterns []string
|
||||
allowed []string
|
||||
disallowed []string
|
||||
whitelist []string
|
||||
forbiddenSafe []string
|
||||
allowedUnsafe []string
|
||||
allowed []string
|
||||
disallowed []string
|
||||
}{
|
||||
// no container requests
|
||||
"nil": {
|
||||
patterns: nil,
|
||||
allowed: []string{"foo"},
|
||||
"with allow all": {
|
||||
whitelist: []string{"foo"},
|
||||
allowed: []string{"foo"},
|
||||
},
|
||||
"empty": {
|
||||
patterns: []string{},
|
||||
disallowed: []string{"foo"},
|
||||
whitelist: []string{"foo"},
|
||||
forbiddenSafe: []string{"*"},
|
||||
disallowed: []string{"foo"},
|
||||
},
|
||||
"without wildcard": {
|
||||
patterns: []string{"a", "a.b"},
|
||||
whitelist: []string{"a", "a.b"},
|
||||
allowed: []string{"a", "a.b"},
|
||||
disallowed: []string{"b"},
|
||||
},
|
||||
"with catch-all wildcard": {
|
||||
patterns: []string{"*"},
|
||||
allowed: []string{"a", "a.b"},
|
||||
},
|
||||
"with catch-all wildcard and non-wildcard": {
|
||||
patterns: []string{"a.b.c", "*"},
|
||||
allowed: []string{"a", "a.b", "a.b.c", "b"},
|
||||
allowedUnsafe: []string{"a.b.c", "*"},
|
||||
allowed: []string{"a", "a.b", "a.b.c", "b"},
|
||||
},
|
||||
"without catch-all wildcard": {
|
||||
patterns: []string{"a.*", "b.*", "c.d.e", "d.e.f.*"},
|
||||
allowed: []string{"a.b", "b.c", "c.d.e", "d.e.f.g.h"},
|
||||
disallowed: []string{"a", "b", "c", "c.d", "d.e", "d.e.f"},
|
||||
allowedUnsafe: []string{"a.*", "b.*", "c.d.e", "d.e.f.*"},
|
||||
allowed: []string{"a.b", "b.c", "c.d.e", "d.e.f.g.h"},
|
||||
disallowed: []string{"a", "b", "c", "c.d", "d.e", "d.e.f"},
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range tests {
|
||||
strategy := NewMustMatchPatterns(v.patterns)
|
||||
strategy := NewMustMatchPatterns(v.whitelist, v.allowedUnsafe, v.forbiddenSafe)
|
||||
|
||||
pod := &api.Pod{}
|
||||
errs := strategy.Validate(pod)
|
||||
|
@ -66,37 +65,40 @@ func TestValidate(t *testing.T) {
|
|||
t.Errorf("%s: unexpected validaton errors for empty sysctls: %v", k, errs)
|
||||
}
|
||||
|
||||
sysctls := []api.Sysctl{}
|
||||
for _, s := range v.allowed {
|
||||
sysctls = append(sysctls, api.Sysctl{
|
||||
Name: s,
|
||||
Value: "dummy",
|
||||
})
|
||||
}
|
||||
testAllowed := func(key string, category string) {
|
||||
pod.Annotations = map[string]string{
|
||||
key: helper.PodAnnotationsFromSysctls(sysctls),
|
||||
testAllowed := func() {
|
||||
sysctls := []api.Sysctl{}
|
||||
for _, s := range v.allowed {
|
||||
sysctls = append(sysctls, api.Sysctl{
|
||||
Name: s,
|
||||
Value: "dummy",
|
||||
})
|
||||
}
|
||||
pod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||
Sysctls: sysctls,
|
||||
}
|
||||
errs = strategy.Validate(pod)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("%s: unexpected validaton errors for %s sysctls: %v", k, category, errs)
|
||||
t.Errorf("%s: unexpected validaton errors for sysctls: %v", k, errs)
|
||||
}
|
||||
}
|
||||
testDisallowed := func(key string, category string) {
|
||||
testDisallowed := func() {
|
||||
for _, s := range v.disallowed {
|
||||
pod.Annotations = map[string]string{
|
||||
key: helper.PodAnnotationsFromSysctls([]api.Sysctl{{Name: s, Value: "dummy"}}),
|
||||
pod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||
Sysctls: []api.Sysctl{
|
||||
{
|
||||
Name: s,
|
||||
Value: "dummy",
|
||||
},
|
||||
},
|
||||
}
|
||||
errs = strategy.Validate(pod)
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("%s: expected error for %s sysctl %q", k, category, s)
|
||||
t.Errorf("%s: expected error for sysctl %q", k, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testAllowed(api.SysctlsPodAnnotationKey, "safe")
|
||||
testAllowed(api.UnsafeSysctlsPodAnnotationKey, "unsafe")
|
||||
testDisallowed(api.SysctlsPodAnnotationKey, "safe")
|
||||
testDisallowed(api.UnsafeSysctlsPodAnnotationKey, "unsafe")
|
||||
testAllowed()
|
||||
testDisallowed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ import (
|
|||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
kapi "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
|
@ -1608,37 +1607,40 @@ func TestAdmitSysctls(t *testing.T) {
|
|||
}
|
||||
return sysctls
|
||||
}
|
||||
pod.Annotations[kapi.SysctlsPodAnnotationKey] = helper.PodAnnotationsFromSysctls(dummySysctls(safeSysctls))
|
||||
pod.Annotations[kapi.UnsafeSysctlsPodAnnotationKey] = helper.PodAnnotationsFromSysctls(dummySysctls(unsafeSysctls))
|
||||
pod.Spec.SecurityContext = &kapi.PodSecurityContext{
|
||||
Sysctls: dummySysctls(append(safeSysctls, unsafeSysctls...)),
|
||||
}
|
||||
|
||||
return pod
|
||||
}
|
||||
|
||||
noSysctls := restrictivePSP()
|
||||
noSysctls.Name = "no sysctls"
|
||||
safeSysctls := restrictivePSP()
|
||||
safeSysctls.Name = "no sysctls"
|
||||
|
||||
emptySysctls := restrictivePSP()
|
||||
emptySysctls.Name = "empty sysctls"
|
||||
emptySysctls.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = ""
|
||||
noSysctls := restrictivePSP()
|
||||
noSysctls.Name = "empty sysctls"
|
||||
noSysctls.Spec.ForbiddenSysctls = []string{"*"}
|
||||
|
||||
mixedSysctls := restrictivePSP()
|
||||
mixedSysctls.Name = "wildcard sysctls"
|
||||
mixedSysctls.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "a.*,b.*,c,d.e.f"
|
||||
mixedSysctls.Spec.ForbiddenSysctls = []string{"net.*"}
|
||||
mixedSysctls.Spec.AllowedUnsafeSysctls = []string{"a.*", "b.*"}
|
||||
|
||||
aSysctl := restrictivePSP()
|
||||
aSysctl.Name = "a sysctl"
|
||||
aSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "a"
|
||||
aUnsafeSysctl := restrictivePSP()
|
||||
aUnsafeSysctl.Name = "a sysctl"
|
||||
aUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"a"}
|
||||
|
||||
bSysctl := restrictivePSP()
|
||||
bSysctl.Name = "b sysctl"
|
||||
bSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "b"
|
||||
bUnsafeSysctl := restrictivePSP()
|
||||
bUnsafeSysctl.Name = "b sysctl"
|
||||
bUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"b"}
|
||||
|
||||
cSysctl := restrictivePSP()
|
||||
cSysctl.Name = "c sysctl"
|
||||
cSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "c"
|
||||
cUnsafeSysctl := restrictivePSP()
|
||||
cUnsafeSysctl.Name = "c sysctl"
|
||||
cUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"c"}
|
||||
|
||||
catchallSysctls := restrictivePSP()
|
||||
catchallSysctls.Name = "catchall sysctl"
|
||||
catchallSysctls.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "*"
|
||||
catchallSysctls.Spec.AllowedUnsafeSysctls = []string{"*"}
|
||||
|
||||
tests := map[string]struct {
|
||||
pod *kapi.Pod
|
||||
|
@ -1647,148 +1649,102 @@ func TestAdmitSysctls(t *testing.T) {
|
|||
shouldPassValidate bool
|
||||
expectedPSP string
|
||||
}{
|
||||
"pod without unsafe sysctls request allowed under noSysctls PSP": {
|
||||
"pod without any sysctls request allowed under safeSysctls PSP": {
|
||||
pod: goodPod(),
|
||||
psps: []*policy.PodSecurityPolicy{safeSysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: safeSysctls.Name,
|
||||
},
|
||||
"pod without any sysctls request allowed under noSysctls PSP": {
|
||||
pod: goodPod(),
|
||||
psps: []*policy.PodSecurityPolicy{noSysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: noSysctls.Name,
|
||||
},
|
||||
"pod without any sysctls request allowed under emptySysctls PSP": {
|
||||
pod: goodPod(),
|
||||
psps: []*policy.PodSecurityPolicy{emptySysctls},
|
||||
"pod with safe sysctls request allowed under safeSysctls PSP": {
|
||||
pod: podWithSysctls([]string{"kernel.shm_rmid_forced", "net.ipv4.tcp_syncookies"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{safeSysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: emptySysctls.Name,
|
||||
expectedPSP: safeSysctls.Name,
|
||||
},
|
||||
"pod with safe sysctls request allowed under noSysctls PSP": {
|
||||
pod: podWithSysctls([]string{"a", "b"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{noSysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: noSysctls.Name,
|
||||
},
|
||||
"pod with unsafe sysctls request allowed under noSysctls PSP": {
|
||||
"pod with unsafe sysctls request disallowed under noSysctls PSP": {
|
||||
pod: podWithSysctls([]string{}, []string{"a", "b"}),
|
||||
psps: []*policy.PodSecurityPolicy{noSysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
expectedPSP: noSysctls.Name,
|
||||
},
|
||||
"pod with safe sysctls request disallowed under emptySysctls PSP": {
|
||||
pod: podWithSysctls([]string{"a", "b"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{emptySysctls},
|
||||
"pod with unsafe sysctls a, b request disallowed under aUnsafeSysctl SCC": {
|
||||
pod: podWithSysctls([]string{}, []string{"a", "b"}),
|
||||
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with unsafe sysctls a, b request disallowed under aSysctls SCC": {
|
||||
pod: podWithSysctls([]string{}, []string{"a", "b"}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with unsafe sysctls b request disallowed under aSysctls SCC": {
|
||||
"pod with unsafe sysctls b request disallowed under aUnsafeSysctl SCC": {
|
||||
pod: podWithSysctls([]string{}, []string{"b"}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
||||
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with unsafe sysctls a request allowed under aSysctls SCC": {
|
||||
"pod with unsafe sysctls a request allowed under aUnsafeSysctl SCC": {
|
||||
pod: podWithSysctls([]string{}, []string{"a"}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
||||
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: aSysctl.Name,
|
||||
expectedPSP: aUnsafeSysctl.Name,
|
||||
},
|
||||
"pod with safe sysctls a, b request disallowed under aSysctls SCC": {
|
||||
pod: podWithSysctls([]string{"a", "b"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with safe sysctls b request disallowed under aSysctls SCC": {
|
||||
pod: podWithSysctls([]string{"b"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with safe sysctls a request allowed under aSysctls SCC": {
|
||||
pod: podWithSysctls([]string{"a"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
||||
"pod with safe net sysctl request allowed under aUnsafeSysctl SCC": {
|
||||
pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: aSysctl.Name,
|
||||
expectedPSP: aUnsafeSysctl.Name,
|
||||
},
|
||||
"pod with unsafe sysctls request disallowed under emptySysctls PSP": {
|
||||
pod: podWithSysctls([]string{}, []string{"a", "b"}),
|
||||
psps: []*policy.PodSecurityPolicy{emptySysctls},
|
||||
"pod with safe sysctls request disallowed under noSysctls PSP": {
|
||||
pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{noSysctls},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with matching sysctls request allowed under mixedSysctls PSP": {
|
||||
pod: podWithSysctls([]string{"a.b", "b.c"}, []string{"c", "d.e.f"}),
|
||||
pod: podWithSysctls([]string{"kernel.shm_rmid_forced"}, []string{"a.b", "b.a"}),
|
||||
psps: []*policy.PodSecurityPolicy{mixedSysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: mixedSysctls.Name,
|
||||
},
|
||||
"pod with not-matching unsafe sysctls request disallowed under mixedSysctls PSP": {
|
||||
pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f"}, []string{"e"}),
|
||||
pod: podWithSysctls([]string{}, []string{"e"}),
|
||||
psps: []*policy.PodSecurityPolicy{mixedSysctls},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with not-matching safe sysctls request disallowed under mixedSysctls PSP": {
|
||||
pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f", "e"}, []string{}),
|
||||
pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{mixedSysctls},
|
||||
shouldPassAdmit: false,
|
||||
shouldPassValidate: false,
|
||||
},
|
||||
"pod with sysctls request allowed under catchallSysctls PSP": {
|
||||
pod: podWithSysctls([]string{"e"}, []string{"f"}),
|
||||
pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{"f"}),
|
||||
psps: []*policy.PodSecurityPolicy{catchallSysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: catchallSysctls.Name,
|
||||
},
|
||||
"pod with sysctls request allowed under catchallSysctls PSP, not under mixedSysctls or emptySysctls PSP": {
|
||||
pod: podWithSysctls([]string{"e"}, []string{"f"}),
|
||||
psps: []*policy.PodSecurityPolicy{mixedSysctls, catchallSysctls, emptySysctls},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: catchallSysctls.Name,
|
||||
},
|
||||
"pod with safe c sysctl request allowed under cSysctl PSP, not under aSysctl or bSysctl PSP": {
|
||||
pod: podWithSysctls([]string{}, []string{"c"}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl, bSysctl, cSysctl},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: cSysctl.Name,
|
||||
},
|
||||
"pod with unsafe c sysctl request allowed under cSysctl PSP, not under aSysctl or bSysctl PSP": {
|
||||
pod: podWithSysctls([]string{"c"}, []string{}),
|
||||
psps: []*policy.PodSecurityPolicy{aSysctl, bSysctl, cSysctl},
|
||||
shouldPassAdmit: true,
|
||||
shouldPassValidate: true,
|
||||
expectedPSP: cSysctl.Name,
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range tests {
|
||||
origSafeSysctls, origUnsafeSysctls, err := helper.SysctlsFromPodAnnotations(v.pod.Annotations)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid sysctl annotation: %v", err)
|
||||
}
|
||||
origSysctl := v.pod.Spec.SecurityContext.Sysctls
|
||||
|
||||
testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t)
|
||||
|
||||
if v.shouldPassAdmit {
|
||||
safeSysctls, unsafeSysctls, _ := helper.SysctlsFromPodAnnotations(v.pod.Annotations)
|
||||
if !reflect.DeepEqual(safeSysctls, origSafeSysctls) {
|
||||
t.Errorf("%s: wrong safe sysctls: expected=%v, got=%v", k, origSafeSysctls, safeSysctls)
|
||||
}
|
||||
if !reflect.DeepEqual(unsafeSysctls, origUnsafeSysctls) {
|
||||
t.Errorf("%s: wrong unsafe sysctls: expected=%v, got=%v", k, origSafeSysctls, safeSysctls)
|
||||
if !reflect.DeepEqual(v.pod.Spec.SecurityContext.Sysctls, origSysctl) {
|
||||
t.Errorf("%s: wrong sysctls: expected=%v, got=%v", k, origSysctl, v.pod.Spec.SecurityContext.Sysctls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,20 +56,6 @@ const (
|
|||
// in the Annotations of a Node.
|
||||
PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"
|
||||
|
||||
// SysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
|
||||
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
|
||||
// key-value pairs. Only a limited set of whitelisted and isolated sysctls is supported by
|
||||
// the kubelet. Pods with other sysctls will fail to launch.
|
||||
SysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/sysctls"
|
||||
|
||||
// UnsafeSysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
|
||||
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
|
||||
// key-value pairs. Unsafe sysctls must be explicitly enabled for a kubelet. They are properly
|
||||
// namespaced to a pod or a container, but their isolation is usually unclear or weak. Their use
|
||||
// is at-your-own-risk. Pods that attempt to set an unsafe sysctl that is not enabled for a kubelet
|
||||
// will fail to launch.
|
||||
UnsafeSysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/unsafe-sysctls"
|
||||
|
||||
// ObjectTTLAnnotations represents a suggestion for kubelet for how long it can cache
|
||||
// an object (e.g. secret, config map) before fetching it again from apiserver.
|
||||
// This annotation can be attached to node.
|
||||
|
|
|
@ -2919,6 +2919,10 @@ type PodSecurityContext struct {
|
|||
// If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
||||
// +optional
|
||||
FSGroup *int64 `json:"fsGroup,omitempty" protobuf:"varint,5,opt,name=fsGroup"`
|
||||
// Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported
|
||||
// sysctls (by the container runtime) might fail to launch.
|
||||
// +optional
|
||||
Sysctls []Sysctl `json:"sysctls,omitempty" protobuf:"bytes,7,rep,name=sysctls"`
|
||||
}
|
||||
|
||||
// PodQOSClass defines the supported qos classes of Pods.
|
||||
|
@ -5203,9 +5207,9 @@ const (
|
|||
// Sysctl defines a kernel parameter to be set
|
||||
type Sysctl struct {
|
||||
// Name of a property to set
|
||||
Name string `protobuf:"bytes,1,opt,name=name"`
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
// Value of a property to set
|
||||
Value string `protobuf:"bytes,2,opt,name=value"`
|
||||
Value string `json:"value" protobuf:"bytes,2,opt,name=value"`
|
||||
}
|
||||
|
||||
// NodeResources is an object for conveying resource information about a node.
|
||||
|
|
|
@ -946,6 +946,25 @@ type PodSecurityPolicySpec struct {
|
|||
// is allowed in the "volumes" field.
|
||||
// +optional
|
||||
AllowedFlexVolumes []AllowedFlexVolume `json:"allowedFlexVolumes,omitempty" protobuf:"bytes,18,rep,name=allowedFlexVolumes"`
|
||||
// allowedUnsafeSysctls is a list of explicitly allowed unsafe sysctls, defaults to none.
|
||||
// Each entry is either a plain sysctl name or ends in "*" in which case it is considered
|
||||
// as a prefix of allowed sysctls. Single * means all unsafe sysctls are allowed.
|
||||
// Kubelet has to whitelist all allowed unsafe sysctls explicitly to avoid rejection.
|
||||
//
|
||||
// Examples:
|
||||
// e.g. "foo/*" allows "foo/bar", "foo/baz", etc.
|
||||
// e.g. "foo.*" allows "foo.bar", "foo.baz", etc.
|
||||
// +optional
|
||||
AllowedUnsafeSysctls []string `json:"allowedUnsafeSysctls,omitempty" protobuf:"bytes,19,rep,name=allowedUnsafeSysctls"`
|
||||
// forbiddenSysctls is a list of explicitly forbidden sysctls, defaults to none.
|
||||
// Each entry is either a plain sysctl name or ends in "*" in which case it is considered
|
||||
// as a prefix of forbidden sysctls. Single * means all sysctls are forbidden.
|
||||
//
|
||||
// Examples:
|
||||
// e.g. "foo/*" forbids "foo/bar", "foo/baz", etc.
|
||||
// e.g. "foo.*" forbids "foo.bar", "foo.baz", etc.
|
||||
// +optional
|
||||
ForbiddenSysctls []string `json:"forbiddenSysctls,omitempty" protobuf:"bytes,20,rep,name=forbiddenSysctls"`
|
||||
}
|
||||
|
||||
// AllowedHostPath defines the host volume conditions that will be enabled by a policy
|
||||
|
|
|
@ -201,6 +201,25 @@ type PodSecurityPolicySpec struct {
|
|||
// is allowed in the "volumes" field.
|
||||
// +optional
|
||||
AllowedFlexVolumes []AllowedFlexVolume `json:"allowedFlexVolumes,omitempty" protobuf:"bytes,18,rep,name=allowedFlexVolumes"`
|
||||
// allowedUnsafeSysctls is a list of explicitly allowed unsafe sysctls, defaults to none.
|
||||
// Each entry is either a plain sysctl name or ends in "*" in which case it is considered
|
||||
// as a prefix of allowed sysctls. Single * means all unsafe sysctls are allowed.
|
||||
// Kubelet has to whitelist all allowed unsafe sysctls explicitly to avoid rejection.
|
||||
//
|
||||
// Examples:
|
||||
// e.g. "foo/*" allows "foo/bar", "foo/baz", etc.
|
||||
// e.g. "foo.*" allows "foo.bar", "foo.baz", etc.
|
||||
// +optional
|
||||
AllowedUnsafeSysctls []string `json:"allowedUnsafeSysctls,omitempty" protobuf:"bytes,19,rep,name=allowedUnsafeSysctls"`
|
||||
// forbiddenSysctls is a list of explicitly forbidden sysctls, defaults to none.
|
||||
// Each entry is either a plain sysctl name or ends in "*" in which case it is considered
|
||||
// as a prefix of forbidden sysctls. Single * means all sysctls are forbidden.
|
||||
//
|
||||
// Examples:
|
||||
// e.g. "foo/*" forbids "foo/bar", "foo/baz", etc.
|
||||
// e.g. "foo.*" forbids "foo.bar", "foo.baz", etc.
|
||||
// +optional
|
||||
ForbiddenSysctls []string `json:"forbiddenSysctls,omitempty" protobuf:"bytes,20,rep,name=forbiddenSysctls"`
|
||||
}
|
||||
|
||||
// AllowedHostPath defines the host volume conditions that will be enabled by a policy
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
|
||||
|
@ -59,12 +58,14 @@ var _ = framework.KubeDescribe("Sysctls [NodeFeature:Sysctls]", func() {
|
|||
|
||||
It("should support sysctls", func() {
|
||||
pod := testPod()
|
||||
pod.Annotations[v1.SysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
||||
{
|
||||
Name: "kernel.shm_rmid_forced",
|
||||
Value: "1",
|
||||
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||
Sysctls: []v1.Sysctl{
|
||||
{
|
||||
Name: "kernel.shm_rmid_forced",
|
||||
Value: "1",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
pod.Spec.Containers[0].Command = []string{"/bin/sysctl", "kernel.shm_rmid_forced"}
|
||||
|
||||
By("Creating a pod with the kernel.shm_rmid_forced sysctl")
|
||||
|
@ -100,12 +101,14 @@ var _ = framework.KubeDescribe("Sysctls [NodeFeature:Sysctls]", func() {
|
|||
|
||||
It("should support unsafe sysctls which are actually whitelisted", func() {
|
||||
pod := testPod()
|
||||
pod.Annotations[v1.UnsafeSysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
||||
{
|
||||
Name: "kernel.shm_rmid_forced",
|
||||
Value: "1",
|
||||
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||
Sysctls: []v1.Sysctl{
|
||||
{
|
||||
Name: "kernel.shm_rmid_forced",
|
||||
Value: "1",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
pod.Spec.Containers[0].Command = []string{"/bin/sysctl", "kernel.shm_rmid_forced"}
|
||||
|
||||
By("Creating a pod with the kernel.shm_rmid_forced sysctl")
|
||||
|
@ -141,34 +144,27 @@ var _ = framework.KubeDescribe("Sysctls [NodeFeature:Sysctls]", func() {
|
|||
|
||||
It("should reject invalid sysctls", func() {
|
||||
pod := testPod()
|
||||
pod.Annotations[v1.SysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
||||
{
|
||||
Name: "foo-",
|
||||
Value: "bar",
|
||||
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||
Sysctls: []v1.Sysctl{
|
||||
// Safe parameters
|
||||
{
|
||||
Name: "foo-",
|
||||
Value: "bar",
|
||||
},
|
||||
{
|
||||
Name: "kernel.shmmax",
|
||||
Value: "100000000",
|
||||
},
|
||||
{
|
||||
Name: "safe-and-unsafe",
|
||||
Value: "100000000",
|
||||
},
|
||||
{
|
||||
Name: "bar..",
|
||||
Value: "42",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "kernel.shmmax",
|
||||
Value: "100000000",
|
||||
},
|
||||
{
|
||||
Name: "safe-and-unsafe",
|
||||
Value: "100000000",
|
||||
},
|
||||
})
|
||||
pod.Annotations[v1.UnsafeSysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
||||
{
|
||||
Name: "kernel.shmall",
|
||||
Value: "100000000",
|
||||
},
|
||||
{
|
||||
Name: "bar..",
|
||||
Value: "42",
|
||||
},
|
||||
{
|
||||
Name: "safe-and-unsafe",
|
||||
Value: "100000000",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
By("Creating a pod with one valid and two invalid sysctls")
|
||||
client := f.ClientSet.CoreV1().Pods(f.Namespace.Name)
|
||||
|
@ -177,18 +173,20 @@ var _ = framework.KubeDescribe("Sysctls [NodeFeature:Sysctls]", func() {
|
|||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring(`Invalid value: "foo-"`))
|
||||
Expect(err.Error()).To(ContainSubstring(`Invalid value: "bar.."`))
|
||||
Expect(err.Error()).To(ContainSubstring(`safe-and-unsafe`))
|
||||
Expect(err.Error()).NotTo(ContainSubstring(`safe-and-unsafe`))
|
||||
Expect(err.Error()).NotTo(ContainSubstring("kernel.shmmax"))
|
||||
})
|
||||
|
||||
It("should not launch unsafe, but not explicitly enabled sysctls on the node", func() {
|
||||
pod := testPod()
|
||||
pod.Annotations[v1.SysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
||||
{
|
||||
Name: "kernel.msgmax",
|
||||
Value: "10000000000",
|
||||
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||
Sysctls: []v1.Sysctl{
|
||||
{
|
||||
Name: "kernel.msgmax",
|
||||
Value: "10000000000",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
By("Creating a pod with a greylisted, but not whitelisted sysctl on the node")
|
||||
pod = podClient.Create(pod)
|
||||
|
|
|
@ -71,6 +71,7 @@ func PrivilegedPSP(name string) *extensionsv1beta1.PodSecurityPolicy {
|
|||
Rule: extensionsv1beta1.FSGroupStrategyRunAsAny,
|
||||
},
|
||||
ReadOnlyRootFilesystem: false,
|
||||
AllowedUnsafeSysctls: []string{"*"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
||||
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
|
@ -123,9 +122,6 @@ func sysctlTestPod(name string, sysctls map[string]string) *v1.Pod {
|
|||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{
|
||||
v1.SysctlsPodAnnotationKey: v1helper.PodAnnotationsFromSysctls(sysctlList),
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
|
@ -136,6 +132,9 @@ func sysctlTestPod(name string, sysctls map[string]string) *v1.Pod {
|
|||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
SecurityContext: &v1.PodSecurityContext{
|
||||
Sysctls: sysctlList,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue