mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
Promote sysctl annotations to API fields
This commit is contained in:
parent
c178c7fd65
commit
ab616a88b9
@ -56,20 +56,6 @@ const (
|
|||||||
// in the Annotations of a Node.
|
// in the Annotations of a Node.
|
||||||
PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"
|
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
|
// 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.
|
// an object (e.g. secret, config map) before fetching it again from apiserver.
|
||||||
// This annotation can be attached to node.
|
// This annotation can be attached to node.
|
||||||
|
@ -499,54 +499,6 @@ func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]core.Taint,
|
|||||||
return taints, nil
|
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.
|
// GetPersistentVolumeClass returns StorageClassName.
|
||||||
func GetPersistentVolumeClass(volume *core.PersistentVolume) string {
|
func GetPersistentVolumeClass(volume *core.PersistentVolume) string {
|
||||||
// Use beta annotation first
|
// 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) {
|
func TestIsHugePageResourceName(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name core.ResourceName
|
name core.ResourceName
|
||||||
|
@ -2657,6 +2657,10 @@ type PodSecurityContext struct {
|
|||||||
// If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
// If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
||||||
// +optional
|
// +optional
|
||||||
FSGroup *int64
|
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.
|
// 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.RunAsGroup = in.RunAsGroup
|
||||||
out.RunAsNonRoot = in.RunAsNonRoot
|
out.RunAsNonRoot = in.RunAsNonRoot
|
||||||
out.FSGroup = in.FSGroup
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,6 +520,15 @@ func Convert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecurity
|
|||||||
out.RunAsGroup = in.RunAsGroup
|
out.RunAsGroup = in.RunAsGroup
|
||||||
out.RunAsNonRoot = in.RunAsNonRoot
|
out.RunAsNonRoot = in.RunAsNonRoot
|
||||||
out.FSGroup = in.FSGroup
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,54 +462,6 @@ func GetAvoidPodsFromNodeAnnotations(annotations map[string]string) (v1.AvoidPod
|
|||||||
return avoidPods, nil
|
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.
|
// GetPersistentVolumeClass returns StorageClassName.
|
||||||
func GetPersistentVolumeClass(volume *v1.PersistentVolume) string {
|
func GetPersistentVolumeClass(volume *v1.PersistentVolume) string {
|
||||||
// Use beta annotation first
|
// 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) {
|
func TestMatchNodeSelectorTerms(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
nodeSelectorTerms []v1.NodeSelectorTerm
|
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, ValidateSeccompPodAnnotations(annotations, fldPath)...)
|
||||||
allErrs = append(allErrs, ValidateAppArmorPodAnnotations(annotations, spec, 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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3364,12 +3347,16 @@ func IsValidSysctlName(name string) bool {
|
|||||||
|
|
||||||
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList {
|
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
names := make(map[string]struct{})
|
||||||
for i, s := range sysctls {
|
for i, s := range sysctls {
|
||||||
if len(s.Name) == 0 {
|
if len(s.Name) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), ""))
|
||||||
} else if !IsValidSysctlName(s.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)))
|
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
|
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"))
|
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
|
return allErrs
|
||||||
@ -5279,20 +5270,6 @@ func ValidateLoadBalancerStatus(status *core.LoadBalancerStatus, fldPath *field.
|
|||||||
return allErrs
|
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
|
// validateVolumeNodeAffinity tests that the PersistentVolume.NodeAffinity has valid data
|
||||||
// returns:
|
// returns:
|
||||||
// - true if volumeNodeAffinity is set
|
// - true if volumeNodeAffinity is set
|
||||||
|
@ -6778,12 +6778,28 @@ func TestValidatePod(t *testing.T) {
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "123",
|
Name: "123",
|
||||||
Namespace: "ns",
|
Namespace: "ns",
|
||||||
Annotations: map[string]string{
|
},
|
||||||
core.SysctlsPodAnnotationKey: "kernel.shmmni=32768,kernel.shmmax=1000000000",
|
Spec: core.PodSpec{
|
||||||
core.UnsafeSysctlsPodAnnotationKey: "knet.ipv4.route.min_pmtu=1000",
|
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
|
{ // valid extended resources for init container
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
|
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
|
||||||
@ -7464,59 +7480,6 @@ func TestValidatePod(t *testing.T) {
|
|||||||
Spec: validPodSpec(nil),
|
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": {
|
"invalid extended resource requirement: request must be == limit": {
|
||||||
expectedError: "must be equal to example.com/a",
|
expectedError: "must be equal to example.com/a",
|
||||||
spec: core.Pod{
|
spec: core.Pod{
|
||||||
@ -12805,6 +12768,11 @@ func TestValidateSysctls(t *testing.T) {
|
|||||||
"_invalid",
|
"_invalid",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
duplicates := []string{
|
||||||
|
"kernel.shmmax",
|
||||||
|
"kernel.shmmax",
|
||||||
|
}
|
||||||
|
|
||||||
sysctls := make([]core.Sysctl, len(valid))
|
sysctls := make([]core.Sysctl, len(valid))
|
||||||
for i, sysctl := range valid {
|
for i, sysctl := range valid {
|
||||||
sysctls[i].Name = sysctl
|
sysctls[i].Name = sysctl
|
||||||
@ -12829,6 +12797,17 @@ func TestValidateSysctls(t *testing.T) {
|
|||||||
t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
|
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 {
|
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"
|
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.
|
// PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.
|
||||||
type PodDisruptionBudgetSpec struct {
|
type PodDisruptionBudgetSpec struct {
|
||||||
// An eviction is allowed if at least "minAvailable" pods selected by
|
// An eviction is allowed if at least "minAvailable" pods selected by
|
||||||
@ -215,6 +208,25 @@ type PodSecurityPolicySpec struct {
|
|||||||
// is allowed in the "Volumes" field.
|
// is allowed in the "Volumes" field.
|
||||||
// +optional
|
// +optional
|
||||||
AllowedFlexVolumes []AllowedFlexVolume
|
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
|
// 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, validatePSPDefaultAllowPrivilegeEscalation(fldPath.Child("defaultAllowPrivilegeEscalation"), spec.DefaultAllowPrivilegeEscalation, spec.AllowPrivilegeEscalation)...)
|
||||||
allErrs = append(allErrs, validatePSPAllowedHostPaths(fldPath.Child("allowedHostPaths"), spec.AllowedHostPaths)...)
|
allErrs = append(allErrs, validatePSPAllowedHostPaths(fldPath.Child("allowedHostPaths"), spec.AllowedHostPaths)...)
|
||||||
allErrs = append(allErrs, validatePSPAllowedFlexVolumes(fldPath.Child("allowedFlexVolumes"), spec.AllowedFlexVolumes)...)
|
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
|
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 != "" {
|
if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" {
|
||||||
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...)
|
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...)
|
||||||
}
|
}
|
||||||
@ -307,11 +301,55 @@ func IsValidSysctlPattern(name string) bool {
|
|||||||
return sysctlPatternRegexp.MatchString(name)
|
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.
|
// validatePodSecurityPolicySysctls validates the sysctls fields of PodSecurityPolicy.
|
||||||
func validatePodSecurityPolicySysctls(fldPath *field.Path, sysctls []string) field.ErrorList {
|
func validatePodSecurityPolicySysctls(fldPath *field.Path, sysctls []string) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
coversAll := false
|
||||||
for i, s := range sysctls {
|
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 = append(
|
||||||
allErrs,
|
allErrs,
|
||||||
field.Invalid(fldPath.Index(i), sysctls[i], fmt.Sprintf("must have at most %d characters and match regex %s",
|
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,
|
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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,8 +323,19 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||||||
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + ",not-good",
|
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + ",not-good",
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidSysctlPattern := validPSP()
|
invalidAllowedUnsafeSysctlPattern := validPSP()
|
||||||
invalidSysctlPattern.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "a.*.b"
|
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 := validPSP()
|
||||||
invalidSeccompDefault.Annotations = map[string]string{
|
invalidSeccompDefault.Annotations = map[string]string{
|
||||||
@ -456,11 +467,26 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||||||
errorType: field.ErrorTypeInvalid,
|
errorType: field.ErrorTypeInvalid,
|
||||||
errorDetail: "invalid AppArmor profile name: \"not-good\"",
|
errorDetail: "invalid AppArmor profile name: \"not-good\"",
|
||||||
},
|
},
|
||||||
"invalid sysctl pattern": {
|
"invalid allowed unsafe sysctl pattern": {
|
||||||
psp: invalidSysctlPattern,
|
psp: invalidAllowedUnsafeSysctlPattern,
|
||||||
errorType: field.ErrorTypeInvalid,
|
errorType: field.ErrorTypeInvalid,
|
||||||
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
|
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": {
|
"invalid seccomp default profile": {
|
||||||
psp: invalidSeccompDefault,
|
psp: invalidSeccompDefault,
|
||||||
errorType: field.ErrorTypeInvalid,
|
errorType: field.ErrorTypeInvalid,
|
||||||
@ -561,8 +587,11 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||||||
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo",
|
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo",
|
||||||
}
|
}
|
||||||
|
|
||||||
withSysctl := validPSP()
|
withForbiddenSysctl := validPSP()
|
||||||
withSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "net.*"
|
withForbiddenSysctl.Spec.ForbiddenSysctls = []string{"net.*"}
|
||||||
|
|
||||||
|
withAllowedUnsafeSysctl := validPSP()
|
||||||
|
withAllowedUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"net.ipv4.tcp_max_syn_backlog"}
|
||||||
|
|
||||||
validSeccomp := validPSP()
|
validSeccomp := validPSP()
|
||||||
validSeccomp.Annotations = map[string]string{
|
validSeccomp.Annotations = map[string]string{
|
||||||
@ -607,8 +636,11 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
|||||||
"valid AppArmor annotations": {
|
"valid AppArmor annotations": {
|
||||||
psp: validAppArmor,
|
psp: validAppArmor,
|
||||||
},
|
},
|
||||||
"with network sysctls": {
|
"with network sysctls forbidden": {
|
||||||
psp: withSysctl,
|
psp: withForbiddenSysctl,
|
||||||
|
},
|
||||||
|
"with unsafe net.ipv4.tcp_max_syn_backlog sysctl allowed": {
|
||||||
|
psp: withAllowedUnsafeSysctl,
|
||||||
},
|
},
|
||||||
"valid seccomp annotations": {
|
"valid seccomp annotations": {
|
||||||
psp: validSeccomp,
|
psp: validSeccomp,
|
||||||
|
@ -100,6 +100,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubelet/volumemanager"
|
"k8s.io/kubernetes/pkg/kubelet/volumemanager"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
|
||||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||||
|
sysctlwhitelist "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
|
||||||
utildbus "k8s.io/kubernetes/pkg/util/dbus"
|
utildbus "k8s.io/kubernetes/pkg/util/dbus"
|
||||||
kubeio "k8s.io/kubernetes/pkg/util/io"
|
kubeio "k8s.io/kubernetes/pkg/util/io"
|
||||||
utilipt "k8s.io/kubernetes/pkg/util/iptables"
|
utilipt "k8s.io/kubernetes/pkg/util/iptables"
|
||||||
@ -837,20 +838,16 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
safeWhitelist, err := sysctl.NewWhitelist(sysctl.SafeSysctlWhitelist(), v1.SysctlsPodAnnotationKey)
|
|
||||||
if err != nil {
|
// Safe, whitelisted sysctls can always be used as unsafe sysctls in the spec.
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Safe, whitelisted sysctls can always be used as unsafe sysctls in the spec
|
|
||||||
// Hence, we concatenate those two lists.
|
// Hence, we concatenate those two lists.
|
||||||
safeAndUnsafeSysctls := append(sysctl.SafeSysctlWhitelist(), allowedUnsafeSysctls...)
|
safeAndUnsafeSysctls := append(sysctlwhitelist.SafeSysctlWhitelist(), allowedUnsafeSysctls...)
|
||||||
unsafeWhitelist, err := sysctl.NewWhitelist(safeAndUnsafeSysctls, v1.UnsafeSysctlsPodAnnotationKey)
|
sysctlsWhitelist, err := sysctl.NewWhitelist(safeAndUnsafeSysctls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
klet.admitHandlers.AddPodAdmitHandler(runtimeSupport)
|
klet.admitHandlers.AddPodAdmitHandler(runtimeSupport)
|
||||||
klet.admitHandlers.AddPodAdmitHandler(safeWhitelist)
|
klet.admitHandlers.AddPodAdmitHandler(sysctlsWhitelist)
|
||||||
klet.admitHandlers.AddPodAdmitHandler(unsafeWhitelist)
|
|
||||||
|
|
||||||
// enable active deadline handler
|
// enable active deadline handler
|
||||||
activeDeadlineHandler, err := newActiveDeadlineHandler(klet.statusManager, kubeDeps.Recorder, klet.clock)
|
activeDeadlineHandler, err := newActiveDeadlineHandler(klet.statusManager, kubeDeps.Recorder, klet.clock)
|
||||||
|
@ -26,7 +26,6 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
@ -191,24 +190,6 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus) *kubecontainer.Runtim
|
|||||||
return &kubecontainer.RuntimeStatus{Conditions: conditions}
|
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.
|
// getSeccompProfileFromAnnotations gets seccomp profile from annotations.
|
||||||
// It gets pod's profile if containerName is empty.
|
// It gets pod's profile if containerName is empty.
|
||||||
func (m *kubeGenericRuntimeManager) getSeccompProfileFromAnnotations(annotations map[string]string, containerName string) string {
|
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)
|
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) {
|
func TestToKubeContainer(t *testing.T) {
|
||||||
c := &runtimeapi.Container{
|
c := &runtimeapi.Container{
|
||||||
Id: "test-id",
|
Id: "test-id",
|
||||||
|
@ -134,10 +134,13 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(pod *v1.Pod) (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
sysctls, err := getSysctlsFromAnnotations(pod.Annotations)
|
sysctls := make(map[string]string)
|
||||||
if err != nil {
|
if pod.Spec.SecurityContext != nil {
|
||||||
return nil, fmt.Errorf("failed to get sysctls from annotations %v for pod %q: %v", pod.Annotations, format.Pod(pod), err)
|
for _, c := range pod.Spec.SecurityContext.Sysctls {
|
||||||
|
sysctls[c.Name] = c.Value
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lc.Sysctls = sysctls
|
lc.Sysctls = sysctls
|
||||||
|
|
||||||
if pod.Spec.SecurityContext != nil {
|
if pod.Spec.SecurityContext != nil {
|
||||||
|
@ -19,7 +19,6 @@ package sysctl
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/container"
|
"k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||||
)
|
)
|
||||||
@ -83,18 +82,12 @@ func NewRuntimeAdmitHandler(runtime container.Runtime) (*runtimeAdmitHandler, er
|
|||||||
|
|
||||||
// Admit checks whether the runtime supports sysctls.
|
// Admit checks whether the runtime supports sysctls.
|
||||||
func (w *runtimeAdmitHandler) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
|
func (w *runtimeAdmitHandler) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
|
||||||
sysctls, unsafeSysctls, err := v1helper.SysctlsFromPodAnnotations(attrs.Pod.Annotations)
|
if attrs.Pod.Spec.SecurityContext != nil {
|
||||||
if err != nil {
|
|
||||||
return lifecycle.PodAdmitResult{
|
|
||||||
Admit: false,
|
|
||||||
Reason: AnnotationInvalidReason,
|
|
||||||
Message: fmt.Sprintf("invalid sysctl annotation: %v", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sysctls)+len(unsafeSysctls) > 0 {
|
if len(attrs.Pod.Spec.SecurityContext.Sysctls) > 0 {
|
||||||
return w.result
|
return w.result
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return lifecycle.PodAdmitResult{
|
return lifecycle.PodAdmitResult{
|
||||||
Admit: true,
|
Admit: true,
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
policyvalidation "k8s.io/kubernetes/pkg/apis/policy/validation"
|
policyvalidation "k8s.io/kubernetes/pkg/apis/policy/validation"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||||
@ -31,36 +30,21 @@ const (
|
|||||||
ForbiddenReason = "SysctlForbidden"
|
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
|
// 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
|
// checks validity via a sysctl and prefix map, rejecting those which are not known
|
||||||
// to be namespaced.
|
// to be namespaced.
|
||||||
type patternWhitelist struct {
|
type patternWhitelist struct {
|
||||||
sysctls map[string]Namespace
|
sysctls map[string]Namespace
|
||||||
prefixes map[string]Namespace
|
prefixes map[string]Namespace
|
||||||
annotationKey string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ lifecycle.PodAdmitHandler = &patternWhitelist{}
|
var _ lifecycle.PodAdmitHandler = &patternWhitelist{}
|
||||||
|
|
||||||
// NewWhitelist creates a new Whitelist from a list of sysctls and sysctl pattern (ending in *).
|
// 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{
|
w := &patternWhitelist{
|
||||||
sysctls: map[string]Namespace{},
|
sysctls: map[string]Namespace{},
|
||||||
prefixes: map[string]Namespace{},
|
prefixes: map[string]Namespace{},
|
||||||
annotationKey: annotationKey,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range patterns {
|
for _, s := range patterns {
|
||||||
@ -121,32 +105,22 @@ func (w *patternWhitelist) validateSysctl(sysctl string, hostNet, hostIPC bool)
|
|||||||
return fmt.Errorf("%q not whitelisted", sysctl)
|
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.
|
// are valid according to the whitelist.
|
||||||
func (w *patternWhitelist) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
|
func (w *patternWhitelist) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
|
||||||
pod := attrs.Pod
|
pod := attrs.Pod
|
||||||
a := pod.Annotations[w.annotationKey]
|
if pod.Spec.SecurityContext == nil || len(pod.Spec.SecurityContext.Sysctls) == 0 {
|
||||||
if a == "" {
|
|
||||||
return lifecycle.PodAdmitResult{
|
return lifecycle.PodAdmitResult{
|
||||||
Admit: true,
|
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
|
var hostNet, hostIPC bool
|
||||||
if pod.Spec.SecurityContext != nil {
|
if pod.Spec.SecurityContext != nil {
|
||||||
hostNet = pod.Spec.HostNetwork
|
hostNet = pod.Spec.HostNetwork
|
||||||
hostIPC = pod.Spec.HostIPC
|
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 {
|
if err := w.validateSysctl(s.Name, hostNet, hostIPC); err != nil {
|
||||||
return lifecycle.PodAdmitResult{
|
return lifecycle.PodAdmitResult{
|
||||||
Admit: false,
|
Admit: false,
|
||||||
|
@ -19,7 +19,7 @@ package sysctl
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewWhitelist(t *testing.T) {
|
func TestNewWhitelist(t *testing.T) {
|
||||||
@ -35,7 +35,7 @@ func TestNewWhitelist(t *testing.T) {
|
|||||||
{sysctls: []string{"net.*.foo"}, err: true},
|
{sysctls: []string{"net.*.foo"}, err: true},
|
||||||
{sysctls: []string{"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 {
|
if test.err && err == nil {
|
||||||
t.Errorf("expected an error creating a whitelist for %v", test.sysctls)
|
t.Errorf("expected an error creating a whitelist for %v", test.sysctls)
|
||||||
} else if !test.err && err != nil {
|
} else if !test.err && err != nil {
|
||||||
@ -65,7 +65,7 @@ func TestWhitelist(t *testing.T) {
|
|||||||
{sysctl: "kernel.sem", hostIPC: true},
|
{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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to create whitelist: %v", err)
|
t.Fatalf("failed to create whitelist: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -77,15 +77,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *policy.PodSecurityPolicy,
|
|||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var unsafeSysctls []string
|
sysctlsStrat := createSysctlsStrategy(sysctl.SafeSysctlWhitelist(), psp.Spec.AllowedUnsafeSysctls, psp.Spec.ForbiddenSysctls)
|
||||||
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)
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return nil, errors.NewAggregate(errs)
|
return nil, errors.NewAggregate(errs)
|
||||||
@ -170,7 +162,7 @@ func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []
|
|||||||
return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps)
|
return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSysctlsStrategy creates a new unsafe sysctls strategy.
|
// createSysctlsStrategy creates a new sysctls strategy.
|
||||||
func createSysctlsStrategy(sysctlsPatterns []string) sysctl.SysctlsStrategy {
|
func createSysctlsStrategy(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls []string) sysctl.SysctlsStrategy {
|
||||||
return sysctl.NewMustMatchPatterns(sysctlsPatterns)
|
return sysctl.NewMustMatchPatterns(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls)
|
||||||
}
|
}
|
||||||
|
@ -267,17 +267,34 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
failOtherSysctlsAllowedPSP := defaultPSP()
|
failSysctlDisallowedPSP := defaultPSP()
|
||||||
failOtherSysctlsAllowedPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "bar,abc"
|
failSysctlDisallowedPSP.Spec.ForbiddenSysctls = []string{"kernel.shm_rmid_forced"}
|
||||||
|
|
||||||
failNoSysctlAllowedPSP := defaultPSP()
|
failNoSafeSysctlAllowedPSP := defaultPSP()
|
||||||
failNoSysctlAllowedPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = ""
|
failNoSafeSysctlAllowedPSP.Spec.ForbiddenSysctls = []string{"*"}
|
||||||
|
|
||||||
failSafeSysctlFooPod := defaultPod()
|
failAllUnsafeSysctlsPSP := defaultPSP()
|
||||||
failSafeSysctlFooPod.Annotations[api.SysctlsPodAnnotationKey] = "foo=1"
|
failAllUnsafeSysctlsPSP.Spec.AllowedUnsafeSysctls = []string{}
|
||||||
|
|
||||||
failUnsafeSysctlFooPod := defaultPod()
|
failSafeSysctlKernelPod := defaultPod()
|
||||||
failUnsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
|
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 := defaultPod()
|
||||||
failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"}
|
failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"}
|
||||||
@ -359,25 +376,20 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
|
|||||||
psp: failHostPathReadOnlyPSP,
|
psp: failHostPathReadOnlyPSP,
|
||||||
expectedError: "must be read-only",
|
expectedError: "must be read-only",
|
||||||
},
|
},
|
||||||
"failSafeSysctlFooPod with failNoSysctlAllowedSCC": {
|
"failSafeSysctlKernelPod with failNoSafeSysctlAllowedPSP": {
|
||||||
pod: failSafeSysctlFooPod,
|
pod: failSafeSysctlKernelPod,
|
||||||
psp: failNoSysctlAllowedPSP,
|
psp: failNoSafeSysctlAllowedPSP,
|
||||||
expectedError: "sysctls are not allowed",
|
expectedError: "sysctl \"kernel.shm_rmid_forced\" is not allowed",
|
||||||
},
|
},
|
||||||
"failUnsafeSysctlFooPod with failNoSysctlAllowedSCC": {
|
"failSafeSysctlKernelPod with failSysctlDisallowedPSP": {
|
||||||
pod: failUnsafeSysctlFooPod,
|
pod: failSafeSysctlKernelPod,
|
||||||
psp: failNoSysctlAllowedPSP,
|
psp: failSysctlDisallowedPSP,
|
||||||
expectedError: "sysctls are not allowed",
|
expectedError: "sysctl \"kernel.shm_rmid_forced\" is not allowed",
|
||||||
},
|
},
|
||||||
"failSafeSysctlFooPod with failOtherSysctlsAllowedSCC": {
|
"failUnsafeSysctlPod with failAllUnsafeSysctlsPSP": {
|
||||||
pod: failSafeSysctlFooPod,
|
pod: failUnsafeSysctlPod,
|
||||||
psp: failOtherSysctlsAllowedPSP,
|
psp: failAllUnsafeSysctlsPSP,
|
||||||
expectedError: "sysctl \"foo\" is not allowed",
|
expectedError: "unsafe sysctl \"kernel.sem\" is not allowed",
|
||||||
},
|
|
||||||
"failUnsafeSysctlFooPod with failOtherSysctlsAllowedSCC": {
|
|
||||||
pod: failUnsafeSysctlFooPod,
|
|
||||||
psp: failOtherSysctlsAllowedPSP,
|
|
||||||
expectedError: "sysctl \"foo\" is not allowed",
|
|
||||||
},
|
},
|
||||||
"failInvalidSeccomp": {
|
"failInvalidSeccomp": {
|
||||||
pod: failSeccompProfilePod,
|
pod: failSeccompProfilePod,
|
||||||
@ -707,14 +719,29 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
|
|||||||
{PathPrefix: "/foo"},
|
{PathPrefix: "/foo"},
|
||||||
}
|
}
|
||||||
|
|
||||||
sysctlAllowFooPSP := defaultPSP()
|
sysctlAllowAllPSP := defaultPSP()
|
||||||
sysctlAllowFooPSP.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "foo"
|
sysctlAllowAllPSP.Spec.ForbiddenSysctls = []string{}
|
||||||
|
sysctlAllowAllPSP.Spec.AllowedUnsafeSysctls = []string{"*"}
|
||||||
|
|
||||||
safeSysctlFooPod := defaultPod()
|
safeSysctlKernelPod := defaultPod()
|
||||||
safeSysctlFooPod.Annotations[api.SysctlsPodAnnotationKey] = "foo=1"
|
safeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||||
|
Sysctls: []api.Sysctl{
|
||||||
|
{
|
||||||
|
Name: "kernel.shm_rmid_forced",
|
||||||
|
Value: "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
unsafeSysctlFooPod := defaultPod()
|
unsafeSysctlKernelPod := defaultPod()
|
||||||
unsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
|
unsafeSysctlKernelPod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||||
|
Sysctls: []api.Sysctl{
|
||||||
|
{
|
||||||
|
Name: "kernel.sem",
|
||||||
|
Value: "32000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
seccompPSP := defaultPSP()
|
seccompPSP := defaultPSP()
|
||||||
seccompPSP.Annotations = map[string]string{
|
seccompPSP.Annotations = map[string]string{
|
||||||
@ -766,21 +793,13 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
|
|||||||
pod: seLinuxPod,
|
pod: seLinuxPod,
|
||||||
psp: seLinuxPSP,
|
psp: seLinuxPSP,
|
||||||
},
|
},
|
||||||
"pass sysctl specific profile with safe sysctl": {
|
"pass sysctl specific profile with safe kernel sysctl": {
|
||||||
pod: safeSysctlFooPod,
|
pod: safeSysctlKernelPod,
|
||||||
psp: sysctlAllowFooPSP,
|
psp: sysctlAllowAllPSP,
|
||||||
},
|
},
|
||||||
"pass sysctl specific profile with unsafe sysctl": {
|
"pass sysctl specific profile with unsafe kernel sysctl": {
|
||||||
pod: unsafeSysctlFooPod,
|
pod: unsafeSysctlKernelPod,
|
||||||
psp: sysctlAllowFooPSP,
|
psp: sysctlAllowAllPSP,
|
||||||
},
|
|
||||||
"pass empty profile with safe sysctl": {
|
|
||||||
pod: safeSysctlFooPod,
|
|
||||||
psp: defaultPSP(),
|
|
||||||
},
|
|
||||||
"pass empty profile with unsafe sysctl": {
|
|
||||||
pod: unsafeSysctlFooPod,
|
|
||||||
psp: defaultPSP(),
|
|
||||||
},
|
},
|
||||||
"pass hostDir allowed directory validating PSP": {
|
"pass hostDir allowed directory validating PSP": {
|
||||||
pod: hostPathDirPod,
|
pod: hostPathDirPod,
|
||||||
|
@ -22,12 +22,26 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
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
|
// mustMatchPatterns implements the SysctlsStrategy interface
|
||||||
type mustMatchPatterns struct {
|
type mustMatchPatterns struct {
|
||||||
patterns []string
|
safeWhitelist []string
|
||||||
|
allowedUnsafeSysctls []string
|
||||||
|
forbiddenSysctls []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -38,56 +52,75 @@ var (
|
|||||||
|
|
||||||
// NewMustMatchPatterns creates a new mustMatchPatterns strategy that will provide validation.
|
// 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.
|
// Passing nil means the default pattern, passing an empty list means to disallow all sysctls.
|
||||||
func NewMustMatchPatterns(patterns []string) SysctlsStrategy {
|
func NewMustMatchPatterns(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls []string) SysctlsStrategy {
|
||||||
if patterns == nil {
|
|
||||||
patterns = defaultSysctlsPatterns
|
|
||||||
}
|
|
||||||
return &mustMatchPatterns{
|
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.
|
// Validate ensures that the specified values fall within the range of the strategy.
|
||||||
func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList {
|
func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList {
|
||||||
allErrs := 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 {
|
var sysctls []api.Sysctl
|
||||||
allErrs := field.ErrorList{}
|
if pod.Spec.SecurityContext != nil {
|
||||||
|
sysctls = pod.Spec.SecurityContext.Sysctls
|
||||||
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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sysctls) > 0 {
|
fieldPath := field.NewPath("pod", "spec", "securityContext").Child("sysctls")
|
||||||
if len(s.patterns) == 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fieldPath, pod.Annotations[key], "sysctls are not allowed"))
|
|
||||||
} else {
|
|
||||||
for i, sysctl := range sysctls {
|
for i, sysctl := range sysctls {
|
||||||
allErrs = append(allErrs, s.ValidateSysctl(sysctl.Name, fieldPath.Index(i))...)
|
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
|
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
|
package sysctl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
patterns []string
|
whitelist []string
|
||||||
|
forbiddenSafe []string
|
||||||
|
allowedUnsafe []string
|
||||||
allowed []string
|
allowed []string
|
||||||
disallowed []string
|
disallowed []string
|
||||||
}{
|
}{
|
||||||
// no container requests
|
// no container requests
|
||||||
"nil": {
|
"with allow all": {
|
||||||
patterns: nil,
|
whitelist: []string{"foo"},
|
||||||
allowed: []string{"foo"},
|
allowed: []string{"foo"},
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
patterns: []string{},
|
whitelist: []string{"foo"},
|
||||||
|
forbiddenSafe: []string{"*"},
|
||||||
disallowed: []string{"foo"},
|
disallowed: []string{"foo"},
|
||||||
},
|
},
|
||||||
"without wildcard": {
|
"without wildcard": {
|
||||||
patterns: []string{"a", "a.b"},
|
whitelist: []string{"a", "a.b"},
|
||||||
allowed: []string{"a", "a.b"},
|
allowed: []string{"a", "a.b"},
|
||||||
disallowed: []string{"b"},
|
disallowed: []string{"b"},
|
||||||
},
|
},
|
||||||
"with catch-all wildcard": {
|
|
||||||
patterns: []string{"*"},
|
|
||||||
allowed: []string{"a", "a.b"},
|
|
||||||
},
|
|
||||||
"with catch-all wildcard and non-wildcard": {
|
"with catch-all wildcard and non-wildcard": {
|
||||||
patterns: []string{"a.b.c", "*"},
|
allowedUnsafe: []string{"a.b.c", "*"},
|
||||||
allowed: []string{"a", "a.b", "a.b.c", "b"},
|
allowed: []string{"a", "a.b", "a.b.c", "b"},
|
||||||
},
|
},
|
||||||
"without catch-all wildcard": {
|
"without catch-all wildcard": {
|
||||||
patterns: []string{"a.*", "b.*", "c.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"},
|
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"},
|
disallowed: []string{"a", "b", "c", "c.d", "d.e", "d.e.f"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range tests {
|
for k, v := range tests {
|
||||||
strategy := NewMustMatchPatterns(v.patterns)
|
strategy := NewMustMatchPatterns(v.whitelist, v.allowedUnsafe, v.forbiddenSafe)
|
||||||
|
|
||||||
pod := &api.Pod{}
|
pod := &api.Pod{}
|
||||||
errs := strategy.Validate(pod)
|
errs := strategy.Validate(pod)
|
||||||
@ -66,6 +65,7 @@ func TestValidate(t *testing.T) {
|
|||||||
t.Errorf("%s: unexpected validaton errors for empty sysctls: %v", k, errs)
|
t.Errorf("%s: unexpected validaton errors for empty sysctls: %v", k, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testAllowed := func() {
|
||||||
sysctls := []api.Sysctl{}
|
sysctls := []api.Sysctl{}
|
||||||
for _, s := range v.allowed {
|
for _, s := range v.allowed {
|
||||||
sysctls = append(sysctls, api.Sysctl{
|
sysctls = append(sysctls, api.Sysctl{
|
||||||
@ -73,30 +73,32 @@ func TestValidate(t *testing.T) {
|
|||||||
Value: "dummy",
|
Value: "dummy",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
testAllowed := func(key string, category string) {
|
pod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||||
pod.Annotations = map[string]string{
|
Sysctls: sysctls,
|
||||||
key: helper.PodAnnotationsFromSysctls(sysctls),
|
|
||||||
}
|
}
|
||||||
errs = strategy.Validate(pod)
|
errs = strategy.Validate(pod)
|
||||||
if len(errs) != 0 {
|
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 {
|
for _, s := range v.disallowed {
|
||||||
pod.Annotations = map[string]string{
|
pod.Spec.SecurityContext = &api.PodSecurityContext{
|
||||||
key: helper.PodAnnotationsFromSysctls([]api.Sysctl{{Name: s, Value: "dummy"}}),
|
Sysctls: []api.Sysctl{
|
||||||
|
{
|
||||||
|
Name: s,
|
||||||
|
Value: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
errs = strategy.Validate(pod)
|
errs = strategy.Validate(pod)
|
||||||
if len(errs) == 0 {
|
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()
|
||||||
testAllowed(api.UnsafeSysctlsPodAnnotationKey, "unsafe")
|
testDisallowed()
|
||||||
testDisallowed(api.SysctlsPodAnnotationKey, "safe")
|
|
||||||
testDisallowed(api.UnsafeSysctlsPodAnnotationKey, "unsafe")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
kapi "k8s.io/kubernetes/pkg/apis/core"
|
kapi "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/policy"
|
"k8s.io/kubernetes/pkg/apis/policy"
|
||||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||||
"k8s.io/kubernetes/pkg/controller"
|
"k8s.io/kubernetes/pkg/controller"
|
||||||
@ -1608,37 +1607,40 @@ func TestAdmitSysctls(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return sysctls
|
return sysctls
|
||||||
}
|
}
|
||||||
pod.Annotations[kapi.SysctlsPodAnnotationKey] = helper.PodAnnotationsFromSysctls(dummySysctls(safeSysctls))
|
pod.Spec.SecurityContext = &kapi.PodSecurityContext{
|
||||||
pod.Annotations[kapi.UnsafeSysctlsPodAnnotationKey] = helper.PodAnnotationsFromSysctls(dummySysctls(unsafeSysctls))
|
Sysctls: dummySysctls(append(safeSysctls, unsafeSysctls...)),
|
||||||
|
}
|
||||||
|
|
||||||
return pod
|
return pod
|
||||||
}
|
}
|
||||||
|
|
||||||
noSysctls := restrictivePSP()
|
safeSysctls := restrictivePSP()
|
||||||
noSysctls.Name = "no sysctls"
|
safeSysctls.Name = "no sysctls"
|
||||||
|
|
||||||
emptySysctls := restrictivePSP()
|
noSysctls := restrictivePSP()
|
||||||
emptySysctls.Name = "empty sysctls"
|
noSysctls.Name = "empty sysctls"
|
||||||
emptySysctls.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = ""
|
noSysctls.Spec.ForbiddenSysctls = []string{"*"}
|
||||||
|
|
||||||
mixedSysctls := restrictivePSP()
|
mixedSysctls := restrictivePSP()
|
||||||
mixedSysctls.Name = "wildcard sysctls"
|
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()
|
aUnsafeSysctl := restrictivePSP()
|
||||||
aSysctl.Name = "a sysctl"
|
aUnsafeSysctl.Name = "a sysctl"
|
||||||
aSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "a"
|
aUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"a"}
|
||||||
|
|
||||||
bSysctl := restrictivePSP()
|
bUnsafeSysctl := restrictivePSP()
|
||||||
bSysctl.Name = "b sysctl"
|
bUnsafeSysctl.Name = "b sysctl"
|
||||||
bSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "b"
|
bUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"b"}
|
||||||
|
|
||||||
cSysctl := restrictivePSP()
|
cUnsafeSysctl := restrictivePSP()
|
||||||
cSysctl.Name = "c sysctl"
|
cUnsafeSysctl.Name = "c sysctl"
|
||||||
cSysctl.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "c"
|
cUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"c"}
|
||||||
|
|
||||||
catchallSysctls := restrictivePSP()
|
catchallSysctls := restrictivePSP()
|
||||||
catchallSysctls.Name = "catchall sysctl"
|
catchallSysctls.Name = "catchall sysctl"
|
||||||
catchallSysctls.Annotations[policy.SysctlsPodSecurityPolicyAnnotationKey] = "*"
|
catchallSysctls.Spec.AllowedUnsafeSysctls = []string{"*"}
|
||||||
|
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
pod *kapi.Pod
|
pod *kapi.Pod
|
||||||
@ -1647,148 +1649,102 @@ func TestAdmitSysctls(t *testing.T) {
|
|||||||
shouldPassValidate bool
|
shouldPassValidate bool
|
||||||
expectedPSP string
|
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(),
|
pod: goodPod(),
|
||||||
psps: []*policy.PodSecurityPolicy{noSysctls},
|
psps: []*policy.PodSecurityPolicy{noSysctls},
|
||||||
shouldPassAdmit: true,
|
shouldPassAdmit: true,
|
||||||
shouldPassValidate: true,
|
shouldPassValidate: true,
|
||||||
expectedPSP: noSysctls.Name,
|
expectedPSP: noSysctls.Name,
|
||||||
},
|
},
|
||||||
"pod without any sysctls request allowed under emptySysctls PSP": {
|
"pod with safe sysctls request allowed under safeSysctls PSP": {
|
||||||
pod: goodPod(),
|
pod: podWithSysctls([]string{"kernel.shm_rmid_forced", "net.ipv4.tcp_syncookies"}, []string{}),
|
||||||
psps: []*policy.PodSecurityPolicy{emptySysctls},
|
psps: []*policy.PodSecurityPolicy{safeSysctls},
|
||||||
shouldPassAdmit: true,
|
shouldPassAdmit: true,
|
||||||
shouldPassValidate: true,
|
shouldPassValidate: true,
|
||||||
expectedPSP: emptySysctls.Name,
|
expectedPSP: safeSysctls.Name,
|
||||||
},
|
},
|
||||||
"pod with safe sysctls request allowed under noSysctls PSP": {
|
"pod with unsafe sysctls request disallowed 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: podWithSysctls([]string{}, []string{"a", "b"}),
|
pod: podWithSysctls([]string{}, []string{"a", "b"}),
|
||||||
psps: []*policy.PodSecurityPolicy{noSysctls},
|
psps: []*policy.PodSecurityPolicy{noSysctls},
|
||||||
shouldPassAdmit: true,
|
shouldPassAdmit: false,
|
||||||
shouldPassValidate: true,
|
shouldPassValidate: false,
|
||||||
expectedPSP: noSysctls.Name,
|
expectedPSP: noSysctls.Name,
|
||||||
},
|
},
|
||||||
"pod with safe sysctls request disallowed under emptySysctls PSP": {
|
"pod with unsafe sysctls a, b request disallowed under aUnsafeSysctl SCC": {
|
||||||
pod: podWithSysctls([]string{"a", "b"}, []string{}),
|
pod: podWithSysctls([]string{}, []string{"a", "b"}),
|
||||||
psps: []*policy.PodSecurityPolicy{emptySysctls},
|
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||||
shouldPassAdmit: false,
|
shouldPassAdmit: false,
|
||||||
shouldPassValidate: false,
|
shouldPassValidate: false,
|
||||||
},
|
},
|
||||||
"pod with unsafe sysctls a, b request disallowed under aSysctls SCC": {
|
"pod with unsafe sysctls b request disallowed under aUnsafeSysctl 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: podWithSysctls([]string{}, []string{"b"}),
|
pod: podWithSysctls([]string{}, []string{"b"}),
|
||||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||||
shouldPassAdmit: false,
|
shouldPassAdmit: false,
|
||||||
shouldPassValidate: 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"}),
|
pod: podWithSysctls([]string{}, []string{"a"}),
|
||||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||||
shouldPassAdmit: true,
|
shouldPassAdmit: true,
|
||||||
shouldPassValidate: true,
|
shouldPassValidate: true,
|
||||||
expectedPSP: aSysctl.Name,
|
expectedPSP: aUnsafeSysctl.Name,
|
||||||
},
|
},
|
||||||
"pod with safe sysctls a, b request disallowed under aSysctls SCC": {
|
"pod with safe net sysctl request allowed under aUnsafeSysctl SCC": {
|
||||||
pod: podWithSysctls([]string{"a", "b"}, []string{}),
|
pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}),
|
||||||
psps: []*policy.PodSecurityPolicy{aSysctl},
|
psps: []*policy.PodSecurityPolicy{aUnsafeSysctl},
|
||||||
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},
|
|
||||||
shouldPassAdmit: true,
|
shouldPassAdmit: true,
|
||||||
shouldPassValidate: true,
|
shouldPassValidate: true,
|
||||||
expectedPSP: aSysctl.Name,
|
expectedPSP: aUnsafeSysctl.Name,
|
||||||
},
|
},
|
||||||
"pod with unsafe sysctls request disallowed under emptySysctls PSP": {
|
"pod with safe sysctls request disallowed under noSysctls PSP": {
|
||||||
pod: podWithSysctls([]string{}, []string{"a", "b"}),
|
pod: podWithSysctls([]string{"net.ipv4.ip_local_port_range"}, []string{}),
|
||||||
psps: []*policy.PodSecurityPolicy{emptySysctls},
|
psps: []*policy.PodSecurityPolicy{noSysctls},
|
||||||
shouldPassAdmit: false,
|
shouldPassAdmit: false,
|
||||||
shouldPassValidate: false,
|
shouldPassValidate: false,
|
||||||
},
|
},
|
||||||
"pod with matching sysctls request allowed under mixedSysctls PSP": {
|
"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},
|
psps: []*policy.PodSecurityPolicy{mixedSysctls},
|
||||||
shouldPassAdmit: true,
|
shouldPassAdmit: true,
|
||||||
shouldPassValidate: true,
|
shouldPassValidate: true,
|
||||||
expectedPSP: mixedSysctls.Name,
|
expectedPSP: mixedSysctls.Name,
|
||||||
},
|
},
|
||||||
"pod with not-matching unsafe sysctls request disallowed under mixedSysctls PSP": {
|
"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},
|
psps: []*policy.PodSecurityPolicy{mixedSysctls},
|
||||||
shouldPassAdmit: false,
|
shouldPassAdmit: false,
|
||||||
shouldPassValidate: false,
|
shouldPassValidate: false,
|
||||||
},
|
},
|
||||||
"pod with not-matching safe sysctls request disallowed under mixedSysctls PSP": {
|
"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},
|
psps: []*policy.PodSecurityPolicy{mixedSysctls},
|
||||||
shouldPassAdmit: false,
|
shouldPassAdmit: false,
|
||||||
shouldPassValidate: false,
|
shouldPassValidate: false,
|
||||||
},
|
},
|
||||||
"pod with sysctls request allowed under catchallSysctls PSP": {
|
"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},
|
psps: []*policy.PodSecurityPolicy{catchallSysctls},
|
||||||
shouldPassAdmit: true,
|
shouldPassAdmit: true,
|
||||||
shouldPassValidate: true,
|
shouldPassValidate: true,
|
||||||
expectedPSP: catchallSysctls.Name,
|
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 {
|
for k, v := range tests {
|
||||||
origSafeSysctls, origUnsafeSysctls, err := helper.SysctlsFromPodAnnotations(v.pod.Annotations)
|
origSysctl := v.pod.Spec.SecurityContext.Sysctls
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("invalid sysctl annotation: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t)
|
testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t)
|
||||||
|
|
||||||
if v.shouldPassAdmit {
|
if v.shouldPassAdmit {
|
||||||
safeSysctls, unsafeSysctls, _ := helper.SysctlsFromPodAnnotations(v.pod.Annotations)
|
if !reflect.DeepEqual(v.pod.Spec.SecurityContext.Sysctls, origSysctl) {
|
||||||
if !reflect.DeepEqual(safeSysctls, origSafeSysctls) {
|
t.Errorf("%s: wrong sysctls: expected=%v, got=%v", k, origSysctl, v.pod.Spec.SecurityContext.Sysctls)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,20 +56,6 @@ const (
|
|||||||
// in the Annotations of a Node.
|
// in the Annotations of a Node.
|
||||||
PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"
|
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
|
// 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.
|
// an object (e.g. secret, config map) before fetching it again from apiserver.
|
||||||
// This annotation can be attached to node.
|
// 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.
|
// If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
||||||
// +optional
|
// +optional
|
||||||
FSGroup *int64 `json:"fsGroup,omitempty" protobuf:"varint,5,opt,name=fsGroup"`
|
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.
|
// PodQOSClass defines the supported qos classes of Pods.
|
||||||
@ -5203,9 +5207,9 @@ const (
|
|||||||
// Sysctl defines a kernel parameter to be set
|
// Sysctl defines a kernel parameter to be set
|
||||||
type Sysctl struct {
|
type Sysctl struct {
|
||||||
// Name of a property to set
|
// 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 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.
|
// NodeResources is an object for conveying resource information about a node.
|
||||||
|
@ -946,6 +946,25 @@ type PodSecurityPolicySpec struct {
|
|||||||
// is allowed in the "volumes" field.
|
// is allowed in the "volumes" field.
|
||||||
// +optional
|
// +optional
|
||||||
AllowedFlexVolumes []AllowedFlexVolume `json:"allowedFlexVolumes,omitempty" protobuf:"bytes,18,rep,name=allowedFlexVolumes"`
|
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
|
// 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.
|
// is allowed in the "volumes" field.
|
||||||
// +optional
|
// +optional
|
||||||
AllowedFlexVolumes []AllowedFlexVolume `json:"allowedFlexVolumes,omitempty" protobuf:"bytes,18,rep,name=allowedFlexVolumes"`
|
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
|
// AllowedHostPath defines the host volume conditions that will be enabled by a policy
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
"k8s.io/apimachinery/pkg/util/uuid"
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
@ -59,12 +58,14 @@ var _ = framework.KubeDescribe("Sysctls [NodeFeature:Sysctls]", func() {
|
|||||||
|
|
||||||
It("should support sysctls", func() {
|
It("should support sysctls", func() {
|
||||||
pod := testPod()
|
pod := testPod()
|
||||||
pod.Annotations[v1.SysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||||
|
Sysctls: []v1.Sysctl{
|
||||||
{
|
{
|
||||||
Name: "kernel.shm_rmid_forced",
|
Name: "kernel.shm_rmid_forced",
|
||||||
Value: "1",
|
Value: "1",
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
}
|
||||||
pod.Spec.Containers[0].Command = []string{"/bin/sysctl", "kernel.shm_rmid_forced"}
|
pod.Spec.Containers[0].Command = []string{"/bin/sysctl", "kernel.shm_rmid_forced"}
|
||||||
|
|
||||||
By("Creating a pod with the kernel.shm_rmid_forced sysctl")
|
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() {
|
It("should support unsafe sysctls which are actually whitelisted", func() {
|
||||||
pod := testPod()
|
pod := testPod()
|
||||||
pod.Annotations[v1.UnsafeSysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||||
|
Sysctls: []v1.Sysctl{
|
||||||
{
|
{
|
||||||
Name: "kernel.shm_rmid_forced",
|
Name: "kernel.shm_rmid_forced",
|
||||||
Value: "1",
|
Value: "1",
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
}
|
||||||
pod.Spec.Containers[0].Command = []string{"/bin/sysctl", "kernel.shm_rmid_forced"}
|
pod.Spec.Containers[0].Command = []string{"/bin/sysctl", "kernel.shm_rmid_forced"}
|
||||||
|
|
||||||
By("Creating a pod with the kernel.shm_rmid_forced sysctl")
|
By("Creating a pod with the kernel.shm_rmid_forced sysctl")
|
||||||
@ -141,7 +144,9 @@ var _ = framework.KubeDescribe("Sysctls [NodeFeature:Sysctls]", func() {
|
|||||||
|
|
||||||
It("should reject invalid sysctls", func() {
|
It("should reject invalid sysctls", func() {
|
||||||
pod := testPod()
|
pod := testPod()
|
||||||
pod.Annotations[v1.SysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||||
|
Sysctls: []v1.Sysctl{
|
||||||
|
// Safe parameters
|
||||||
{
|
{
|
||||||
Name: "foo-",
|
Name: "foo-",
|
||||||
Value: "bar",
|
Value: "bar",
|
||||||
@ -154,21 +159,12 @@ var _ = framework.KubeDescribe("Sysctls [NodeFeature:Sysctls]", func() {
|
|||||||
Name: "safe-and-unsafe",
|
Name: "safe-and-unsafe",
|
||||||
Value: "100000000",
|
Value: "100000000",
|
||||||
},
|
},
|
||||||
})
|
|
||||||
pod.Annotations[v1.UnsafeSysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
|
||||||
{
|
|
||||||
Name: "kernel.shmall",
|
|
||||||
Value: "100000000",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "bar..",
|
Name: "bar..",
|
||||||
Value: "42",
|
Value: "42",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "safe-and-unsafe",
|
|
||||||
Value: "100000000",
|
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
By("Creating a pod with one valid and two invalid sysctls")
|
By("Creating a pod with one valid and two invalid sysctls")
|
||||||
client := f.ClientSet.CoreV1().Pods(f.Namespace.Name)
|
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).NotTo(BeNil())
|
||||||
Expect(err.Error()).To(ContainSubstring(`Invalid value: "foo-"`))
|
Expect(err.Error()).To(ContainSubstring(`Invalid value: "foo-"`))
|
||||||
Expect(err.Error()).To(ContainSubstring(`Invalid value: "bar.."`))
|
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"))
|
Expect(err.Error()).NotTo(ContainSubstring("kernel.shmmax"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should not launch unsafe, but not explicitly enabled sysctls on the node", func() {
|
It("should not launch unsafe, but not explicitly enabled sysctls on the node", func() {
|
||||||
pod := testPod()
|
pod := testPod()
|
||||||
pod.Annotations[v1.SysctlsPodAnnotationKey] = v1helper.PodAnnotationsFromSysctls([]v1.Sysctl{
|
pod.Spec.SecurityContext = &v1.PodSecurityContext{
|
||||||
|
Sysctls: []v1.Sysctl{
|
||||||
{
|
{
|
||||||
Name: "kernel.msgmax",
|
Name: "kernel.msgmax",
|
||||||
Value: "10000000000",
|
Value: "10000000000",
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
By("Creating a pod with a greylisted, but not whitelisted sysctl on the node")
|
By("Creating a pod with a greylisted, but not whitelisted sysctl on the node")
|
||||||
pod = podClient.Create(pod)
|
pod = podClient.Create(pod)
|
||||||
|
@ -71,6 +71,7 @@ func PrivilegedPSP(name string) *extensionsv1beta1.PodSecurityPolicy {
|
|||||||
Rule: extensionsv1beta1.FSGroupStrategyRunAsAny,
|
Rule: extensionsv1beta1.FSGroupStrategyRunAsAny,
|
||||||
},
|
},
|
||||||
ReadOnlyRootFilesystem: false,
|
ReadOnlyRootFilesystem: false,
|
||||||
|
AllowedUnsafeSysctls: []string{"*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
"k8s.io/apimachinery/pkg/util/uuid"
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
||||||
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
@ -123,9 +122,6 @@ func sysctlTestPod(name string, sysctls map[string]string) *v1.Pod {
|
|||||||
return &v1.Pod{
|
return &v1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Annotations: map[string]string{
|
|
||||||
v1.SysctlsPodAnnotationKey: v1helper.PodAnnotationsFromSysctls(sysctlList),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
@ -136,6 +132,9 @@ func sysctlTestPod(name string, sysctls map[string]string) *v1.Pod {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
RestartPolicy: v1.RestartPolicyNever,
|
RestartPolicy: v1.RestartPolicyNever,
|
||||||
|
SecurityContext: &v1.PodSecurityContext{
|
||||||
|
Sysctls: sysctlList,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user