Upgrade preparation to verify sysctl values containing forward slashes by regex

This commit is contained in:
Mengjiao Liu
2021-05-28 13:38:42 +08:00
parent bb24c265ce
commit 275d832ce2
11 changed files with 344 additions and 19 deletions

View File

@@ -3355,6 +3355,8 @@ type PodValidationOptions struct {
AllowExpandedDNSConfig bool
// Allow OSField to be set in the pod spec
AllowOSField bool
// Allow sysctl name to contain a slash
AllowSysctlRegexContainSlash bool
}
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
@@ -3451,7 +3453,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"))...)
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...)
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
@@ -4006,29 +4008,48 @@ const (
// a sysctl name regex
SysctlFmt string = "(" + SysctlSegmentFmt + "\\.)*" + SysctlSegmentFmt
// a sysctl name regex with slash allowed
SysctlContainSlashFmt string = "(" + SysctlSegmentFmt + "[\\./])*" + SysctlSegmentFmt
// the maximal length of a sysctl name
SysctlMaxLength int = 253
)
var sysctlRegexp = regexp.MustCompile("^" + SysctlFmt + "$")
var sysctlContainSlashRegexp = regexp.MustCompile("^" + SysctlContainSlashFmt + "$")
// IsValidSysctlName checks that the given string is a valid sysctl name,
// i.e. matches SysctlFmt.
func IsValidSysctlName(name string) bool {
// i.e. matches SysctlFmt (or SysctlContainSlashFmt if canContainSlash is true).
// More info:
// https://man7.org/linux/man-pages/man8/sysctl.8.html
// https://man7.org/linux/man-pages/man5/sysctl.d.5.html
func IsValidSysctlName(name string, canContainSlash bool) bool {
if len(name) > SysctlMaxLength {
return false
}
if canContainSlash {
return sysctlContainSlashRegexp.MatchString(name)
}
return sysctlRegexp.MatchString(name)
}
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList {
func getSysctlFmt(canContainSlash bool) string {
if canContainSlash {
// use relaxed validation everywhere in 1.24
return SysctlContainSlashFmt
}
// Will be removed in 1.24
return SysctlFmt
}
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path, allowSysctlRegexContainSlash bool) field.ErrorList {
allErrs := field.ErrorList{}
names := make(map[string]struct{})
for i, s := range sysctls {
if len(s.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), ""))
} else if !IsValidSysctlName(s.Name) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, SysctlFmt)))
} else if !IsValidSysctlName(s.Name, allowSysctlRegexContainSlash) {
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, getSysctlFmt(allowSysctlRegexContainSlash))))
} else if _, ok := names[s.Name]; ok {
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("name"), s.Name))
}
@@ -4038,7 +4059,7 @@ func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList
}
// ValidatePodSecurityContext test that the specified PodSecurityContext has valid data.
func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path) field.ErrorList {
func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
if securityContext != nil {
@@ -4068,7 +4089,7 @@ func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *
}
if len(securityContext.Sysctls) != 0 {
allErrs = append(allErrs, validateSysctls(securityContext.Sysctls, fldPath.Child("sysctls"))...)
allErrs = append(allErrs, validateSysctls(securityContext.Sysctls, fldPath.Child("sysctls"), opts.AllowSysctlRegexContainSlash)...)
}
if securityContext.FSGroupChangePolicy != nil {

View File

@@ -17765,13 +17765,35 @@ func TestIsValidSysctlName(t *testing.T) {
return string(x)
}(256),
}
containSlashesValid := []string{
"a/b/c/d",
"a/b.c",
}
containSlashesInvalid := []string{
"/",
"/a",
"a/abc*",
"a/b/*",
}
for _, s := range valid {
if !IsValidSysctlName(s) {
if !IsValidSysctlName(s, false) {
t.Errorf("%q expected to be a valid sysctl name", s)
}
}
for _, s := range invalid {
if IsValidSysctlName(s) {
if IsValidSysctlName(s, false) {
t.Errorf("%q expected to be an invalid sysctl name", s)
}
}
for _, s := range containSlashesValid {
if !IsValidSysctlName(s, true) {
t.Errorf("%q expected to be a valid sysctl name", s)
}
}
for _, s := range containSlashesInvalid {
if IsValidSysctlName(s, true) {
t.Errorf("%q expected to be an invalid sysctl name", s)
}
}
@@ -17792,11 +17814,16 @@ func TestValidateSysctls(t *testing.T) {
"kernel.shmmax",
}
containSlashes := []string{
"net.ipv4.conf.enp3s0/200.forwarding",
"net/ipv4/conf/enp3s0.200/forwarding",
}
sysctls := make([]core.Sysctl, len(valid))
for i, sysctl := range valid {
sysctls[i].Name = sysctl
}
errs := validateSysctls(sysctls, field.NewPath("foo"))
errs := validateSysctls(sysctls, field.NewPath("foo"), false)
if len(errs) != 0 {
t.Errorf("unexpected validation errors: %v", errs)
}
@@ -17805,7 +17832,7 @@ func TestValidateSysctls(t *testing.T) {
for i, sysctl := range invalid {
sysctls[i].Name = sysctl
}
errs = validateSysctls(sysctls, field.NewPath("foo"))
errs = validateSysctls(sysctls, field.NewPath("foo"), false)
if len(errs) != 2 {
t.Errorf("expected 2 validation errors. Got: %v", errs)
} else {
@@ -17821,12 +17848,21 @@ func TestValidateSysctls(t *testing.T) {
for i, sysctl := range duplicates {
sysctls[i].Name = sysctl
}
errs = validateSysctls(sysctls, field.NewPath("foo"))
errs = validateSysctls(sysctls, field.NewPath("foo"), false)
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)
}
sysctls = make([]core.Sysctl, len(containSlashes))
for i, sysctl := range containSlashes {
sysctls[i].Name = sysctl
}
errs = validateSysctls(sysctls, field.NewPath("foo"), true)
if len(errs) != 0 {
t.Errorf("unexpected validation errors: %v", errs)
}
}
func newNodeNameEndpoint(nodeName string) *core.Endpoints {