mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 06:54:01 +00:00
Add validation check for PodDNSConfig and 'None' DNSPolicy
This commit is contained in:
parent
ddb5b63832
commit
af7208047a
@ -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))
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user