Add validation check for PodDNSConfig and 'None' DNSPolicy

This commit is contained in:
Zihong Zheng 2017-11-15 21:57:36 -08:00
parent ddb5b63832
commit af7208047a
2 changed files with 251 additions and 2 deletions

View File

@ -2507,16 +2507,81 @@ func validateDNSPolicy(dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.Err
allErrors := field.ErrorList{}
switch *dnsPolicy {
case core.DNSClusterFirstWithHostNet, core.DNSClusterFirst, core.DNSDefault:
break
case core.DNSNone:
if !utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) {
allErrors = append(allErrors, field.Invalid(fldPath, dnsPolicy, "DNSPolicy: can not use 'None', custom pod DNS is disabled by feature gate"))
}
case "":
allErrors = append(allErrors, field.Required(fldPath, ""))
default:
validValues := []string{string(core.DNSClusterFirstWithHostNet), string(core.DNSClusterFirst), string(core.DNSDefault)}
if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) {
validValues = append(validValues, string(core.DNSNone))
}
allErrors = append(allErrors, field.NotSupported(fldPath, dnsPolicy, validValues))
}
return allErrors
}
const (
// Limits on various DNS parameters. These are derived from
// restrictions in Linux libc name resolution handling.
// Max number of DNS name servers.
MaxDNSNameservers = 3
// Max number of domains in search path.
MaxDNSSearchPaths = 6
// Max number of characters in search path.
MaxDNSSearchListChars = 256
)
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// Validate DNSNone case. Must provide at least one DNS name server.
if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) && dnsPolicy != nil && *dnsPolicy == core.DNSNone {
if dnsConfig == nil {
return append(allErrs, field.Required(fldPath, fmt.Sprintf("must provide `dnsConfig` when `dnsPolicy` is %s", core.DNSNone)))
}
if len(dnsConfig.Nameservers) == 0 {
return append(allErrs, field.Required(fldPath.Child("nameservers"), fmt.Sprintf("must provide at least one DNS nameserver when `dnsPolicy` is %s", core.DNSNone)))
}
}
if dnsConfig != nil {
if !utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) {
return append(allErrs, field.Forbidden(fldPath, "DNSConfig: custom pod DNS is disabled by feature gate"))
}
// Validate nameservers.
if len(dnsConfig.Nameservers) > MaxDNSNameservers {
allErrs = append(allErrs, field.Invalid(fldPath.Child("nameservers"), dnsConfig.Nameservers, fmt.Sprintf("must not have more than %v nameservers", MaxDNSNameservers)))
}
for i, ns := range dnsConfig.Nameservers {
if ip := net.ParseIP(ns); ip == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("nameservers").Index(i), ns, "must be valid IP address"))
}
}
// Validate searches.
if len(dnsConfig.Searches) > MaxDNSSearchPaths {
allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v search paths", MaxDNSSearchPaths)))
}
// Include the space between search paths.
if len(strings.Join(dnsConfig.Searches, " ")) > MaxDNSSearchListChars {
allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, "must not have more than 256 characters (including spaces) in the search list"))
}
for i, search := range dnsConfig.Searches {
allErrs = append(allErrs, ValidateDNS1123Subdomain(search, fldPath.Child("searches").Index(i))...)
}
// Validate options.
for i, option := range dnsConfig.Options {
if len(option.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("options").Index(i), "must not be empty"))
}
}
}
return allErrs
}
func validateHostNetwork(hostNetwork bool, containers []core.Container, fldPath *field.Path) field.ErrorList {
allErrors := field.ErrorList{}
if hostNetwork {
@ -2767,6 +2832,7 @@ func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"))...)
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"))...)
if len(spec.ServiceAccountName) > 0 {
for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg))

View File

@ -17,6 +17,7 @@ limitations under the License.
package validation
import (
"fmt"
"math"
"reflect"
"strings"
@ -5148,7 +5149,18 @@ func TestValidateRestartPolicy(t *testing.T) {
}
func TestValidateDNSPolicy(t *testing.T) {
successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSPolicy(core.DNSClusterFirst)}
customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS")
defer func() {
// Restoring the old value.
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil {
t.Errorf("Failed to restore CustomPodDNS feature gate: %v", err)
}
}()
if err := utilfeature.DefaultFeatureGate.Set("CustomPodDNS=true"); err != nil {
t.Errorf("Failed to enable CustomPodDNS feature gate: %v", err)
}
successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSPolicy(core.DNSClusterFirst), core.DNSNone}
for _, policy := range successCases {
if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
@ -5163,6 +5175,177 @@ func TestValidateDNSPolicy(t *testing.T) {
}
}
func TestValidatePodDNSConfig(t *testing.T) {
customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS")
defer func() {
// Restoring the old value.
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil {
t.Errorf("Failed to restore CustomPodDNS feature gate: %v", err)
}
}()
if err := utilfeature.DefaultFeatureGate.Set("CustomPodDNS=true"); err != nil {
t.Errorf("Failed to enable CustomPodDNS feature gate: %v", err)
}
generateTestSearchPathFunc := func(numChars int) string {
res := ""
for i := 0; i < numChars; i++ {
res = res + "a"
}
return res
}
testOptionValue := "2"
testDNSNone := core.DNSNone
testDNSClusterFirst := core.DNSClusterFirst
testCases := []struct {
desc string
dnsConfig *core.PodDNSConfig
dnsPolicy *core.DNSPolicy
expectedError bool
}{
{
desc: "valid: empty DNSConfig",
dnsConfig: &core.PodDNSConfig{},
expectedError: false,
},
{
desc: "valid: 1 option",
dnsConfig: &core.PodDNSConfig{
Options: []core.PodDNSConfigOption{
{Name: "ndots", Value: &testOptionValue},
},
},
expectedError: false,
},
{
desc: "valid: 1 nameserver",
dnsConfig: &core.PodDNSConfig{
Nameservers: []string{"127.0.0.1"},
},
expectedError: false,
},
{
desc: "valid: DNSNone with 1 nameserver",
dnsConfig: &core.PodDNSConfig{
Nameservers: []string{"127.0.0.1"},
},
dnsPolicy: &testDNSNone,
expectedError: false,
},
{
desc: "valid: 1 search path",
dnsConfig: &core.PodDNSConfig{
Searches: []string{"custom"},
},
expectedError: false,
},
{
desc: "valid: 3 nameservers and 6 search paths",
dnsConfig: &core.PodDNSConfig{
Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local"},
},
expectedError: false,
},
{
desc: "valid: 256 characters in search path list",
dnsConfig: &core.PodDNSConfig{
// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
Searches: []string{
generateTestSearchPathFunc(1),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
},
},
expectedError: false,
},
{
desc: "valid: ipv6 nameserver",
dnsConfig: &core.PodDNSConfig{
Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"},
},
expectedError: false,
},
{
desc: "invalid: 4 nameservers",
dnsConfig: &core.PodDNSConfig{
Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
},
expectedError: true,
},
{
desc: "invalid: 7 search paths",
dnsConfig: &core.PodDNSConfig{
Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"},
},
expectedError: true,
},
{
desc: "invalid: 257 characters in search path list",
dnsConfig: &core.PodDNSConfig{
// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
Searches: []string{
generateTestSearchPathFunc(2),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
generateTestSearchPathFunc(50),
},
},
expectedError: true,
},
{
desc: "invalid search path",
dnsConfig: &core.PodDNSConfig{
Searches: []string{"custom?"},
},
expectedError: true,
},
{
desc: "invalid nameserver",
dnsConfig: &core.PodDNSConfig{
Nameservers: []string{"invalid"},
},
expectedError: true,
},
{
desc: "invalid empty option name",
dnsConfig: &core.PodDNSConfig{
Options: []core.PodDNSConfigOption{
{Value: &testOptionValue},
},
},
expectedError: true,
},
{
desc: "invalid: DNSNone with 0 nameserver",
dnsConfig: &core.PodDNSConfig{
Searches: []string{"custom"},
},
dnsPolicy: &testDNSNone,
expectedError: true,
},
}
for _, tc := range testCases {
if tc.dnsPolicy == nil {
tc.dnsPolicy = &testDNSClusterFirst
}
errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig"))
if len(errs) != 0 && !tc.expectedError {
t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs)
} else if len(errs) == 0 && tc.expectedError {
t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig)
}
}
}
func TestValidatePodSpec(t *testing.T) {
activeDeadlineSeconds := int64(30)
activeDeadlineSecondsMax := int64(math.MaxInt32)