diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD index 4e1b7f882f1..b21f53c3dc3 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD @@ -21,6 +21,7 @@ go_library( "//staging/src/k8s.io/cluster-bootstrap/token/util:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/utils/net:go_default_library", ], ) diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index 1511094f5e6..a21dd789570 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -40,6 +40,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" + utilnet "k8s.io/utils/net" ) // ValidateInitConfiguration validates an InitConfiguration object and collects all encountered errors @@ -48,6 +49,7 @@ func ValidateInitConfiguration(c *kubeadm.InitConfiguration) field.ErrorList { allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...) allErrs = append(allErrs, ValidateBootstrapTokens(c.BootstrapTokens, field.NewPath("bootstrapTokens"))...) allErrs = append(allErrs, ValidateClusterConfiguration(&c.ClusterConfiguration)...) + // TODO(Arvinderpal): update advertiseAddress validation for dual-stack once it's implemented. allErrs = append(allErrs, ValidateAPIEndpoint(&c.LocalAPIEndpoint, field.NewPath("localAPIEndpoint"))...) // TODO: Maybe validate that .CertificateKey is a valid hex encoded AES key return allErrs @@ -56,7 +58,7 @@ func ValidateInitConfiguration(c *kubeadm.InitConfiguration) field.ErrorList { // ValidateClusterConfiguration validates an ClusterConfiguration object and collects all encountered errors func ValidateClusterConfiguration(c *kubeadm.ClusterConfiguration) field.ErrorList { allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...) + allErrs = append(allErrs, ValidateNetworking(c, field.NewPath("networking"))...) allErrs = append(allErrs, ValidateAPIServer(&c.APIServer, field.NewPath("apiServer"))...) allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificatesDir"))...) allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("featureGates"))...) @@ -369,30 +371,53 @@ func ValidateHostPort(endpoint string, fldPath *field.Path) field.ErrorList { } // ValidateIPNetFromString validates network portion of ip address -func ValidateIPNetFromString(subnet string, minAddrs int64, fldPath *field.Path) field.ErrorList { +func ValidateIPNetFromString(subnetStr string, minAddrs int64, isDualStack bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - _, svcSubnet, err := net.ParseCIDR(subnet) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, subnet, "couldn't parse subnet")) - return allErrs - } - numAddresses := ipallocator.RangeSize(svcSubnet) - if numAddresses < minAddrs { - allErrs = append(allErrs, field.Invalid(fldPath, subnet, "subnet is too small")) + if isDualStack { + subnets, err := utilnet.ParseCIDRs(strings.Split(subnetStr, ",")) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, subnetStr, err.Error())) + } else { + areDualStackCIDRs, err := utilnet.IsDualStackCIDRs(subnets) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, subnets, err.Error())) + } else if !areDualStackCIDRs { + allErrs = append(allErrs, field.Invalid(fldPath, subnetStr, "expected at least one IP from each family (v4 or v6) for dual-stack networking")) + } + for _, s := range subnets { + numAddresses := ipallocator.RangeSize(s) + if numAddresses < minAddrs { + allErrs = append(allErrs, field.Invalid(fldPath, s, "subnet is too small")) + } + } + } + } else { + _, svcSubnet, err := net.ParseCIDR(subnetStr) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, subnetStr, "couldn't parse subnet")) + return allErrs + } + numAddresses := ipallocator.RangeSize(svcSubnet) + if numAddresses < minAddrs { + allErrs = append(allErrs, field.Invalid(fldPath, subnetStr, "subnet is too small")) + } } return allErrs } // ValidateNetworking validates networking configuration -func ValidateNetworking(c *kubeadm.Networking, fldPath *field.Path) field.ErrorList { +func ValidateNetworking(c *kubeadm.ClusterConfiguration, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} dnsDomainFldPath := field.NewPath("dnsDomain") - for _, err := range validation.IsDNS1123Subdomain(c.DNSDomain) { - allErrs = append(allErrs, field.Invalid(dnsDomainFldPath, c.DNSDomain, err)) + for _, err := range validation.IsDNS1123Subdomain(c.Networking.DNSDomain) { + allErrs = append(allErrs, field.Invalid(dnsDomainFldPath, c.Networking.DNSDomain, err)) } - allErrs = append(allErrs, ValidateIPNetFromString(c.ServiceSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("serviceSubnet"))...) - if len(c.PodSubnet) != 0 { - allErrs = append(allErrs, ValidateIPNetFromString(c.PodSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("podSubnet"))...) + // check if dual-stack feature-gate is enabled + isDualStack := features.Enabled(c.FeatureGates, features.IPv6DualStack) + // TODO(Arvinderpal): use isDualStack flag once list of service CIDRs is supported (PR: #79386) + allErrs = append(allErrs, ValidateIPNetFromString(c.Networking.ServiceSubnet, constants.MinimumAddressesInServiceSubnet, false /*isDualStack*/, field.NewPath("serviceSubnet"))...) + if len(c.Networking.PodSubnet) != 0 { + allErrs = append(allErrs, ValidateIPNetFromString(c.Networking.PodSubnet, constants.MinimumAddressesInServiceSubnet, isDualStack, field.NewPath("podSubnet"))...) } return allErrs } @@ -455,7 +480,6 @@ func ValidateFeatureGates(featureGates map[string]bool, fldPath *field.Path) fie fmt.Sprintf("%s is not a valid feature name.", k))) } } - return allErrs } diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go index 2e52becd914..5394467e5e4 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go @@ -194,29 +194,43 @@ func TestValidateIPFromString(t *testing.T) { func TestValidateIPNetFromString(t *testing.T) { var tests = []struct { - name string - subnet string - minaddrs int64 - expected bool + name string + subnet string + minaddrs int64 + checkDualStack bool + expected bool }{ - {"invalid missing CIDR", "", 0, false}, - {"invalid CIDR missing decimal points in IPv4 address and / mask", "1234", 0, false}, - {"invalid CIDR use of letters instead of numbers and / mask", "abc", 0, false}, - {"invalid IPv4 address provided instead of CIDR representation", "1.2.3.4", 0, false}, - {"invalid IPv6 address provided instead of CIDR representation", "2001:db8::1", 0, false}, - {"valid, but IPv4 CIDR too small. At least 10 addresses needed", "10.0.0.16/29", 10, false}, - {"valid, but IPv6 CIDR too small. At least 10 addresses needed", "2001:db8::/125", 10, false}, - {"valid IPv4 CIDR", "10.0.0.16/12", 10, true}, - {"valid IPv6 CIDR", "2001:db8::/98", 10, true}, + {"invalid missing CIDR", "", 0, false, false}, + {"invalid CIDR missing decimal points in IPv4 address and / mask", "1234", 0, false, false}, + {"invalid CIDR use of letters instead of numbers and / mask", "abc", 0, false, false}, + {"invalid IPv4 address provided instead of CIDR representation", "1.2.3.4", 0, false, false}, + {"invalid IPv6 address provided instead of CIDR representation", "2001:db8::1", 0, false, false}, + {"valid, but IPv4 CIDR too small. At least 10 addresses needed", "10.0.0.16/29", 10, false, false}, + {"valid, but IPv6 CIDR too small. At least 10 addresses needed", "2001:db8::/125", 10, false, false}, + {"valid IPv4 CIDR", "10.0.0.16/12", 10, false, true}, + {"valid IPv6 CIDR", "2001:db8::/98", 10, false, true}, + // dual-stack: + {"invalid missing CIDR", "", 0, true, false}, + {"invalid only an IPv4 CIDR specified", "10.0.0.16/12", 10, true, false}, + {"invalid only an IPv6 CIDR specified", "2001:db8::/98", 10, true, false}, + {"invalid IPv4 address provided instead of CIDR representation", "1.2.3.4,2001:db8::/98", 0, true, false}, + {"invalid IPv6 address provided instead of CIDR representation", "2001:db8::1,10.0.0.16/12", 0, true, false}, + {"valid, but IPv4 CIDR too small. At least 10 addresses needed", "10.0.0.16/29,2001:db8::/98", 10, true, false}, + {"valid, but IPv6 CIDR too small. At least 10 addresses needed", "10.0.0.16/12,2001:db8::/125", 10, true, false}, + {"valid, but only IPv4 family addresses specified. IPv6 CIDR is necessary.", "10.0.0.16/12,192.168.0.0/16", 10, true, false}, + {"valid, but only IPv6 family addresses specified. IPv4 CIDR is necessary.", "2001:db8::/98,2005:db8::/98", 10, true, false}, + {"valid IPv4 and IPv6 CIDR", "10.0.0.16/12,2001:db8::/98", 10, true, true}, + {"valid IPv6 and IPv4 CIDR", "10.0.0.16/12,2001:db8::/98", 10, true, true}, } for _, rt := range tests { - actual := ValidateIPNetFromString(rt.subnet, rt.minaddrs, nil) + actual := ValidateIPNetFromString(rt.subnet, rt.minaddrs, rt.checkDualStack, nil) if (len(actual) == 0) != rt.expected { t.Errorf( - "%s test case failed :\n\texpected: %t\n\t actual: %t", + "%s test case failed :\n\texpected: %t\n\t actual: %t\n\t err(s): %v\n\t", rt.name, rt.expected, (len(actual) == 0), + actual, ) } } diff --git a/cmd/kubeadm/app/phases/controlplane/manifests.go b/cmd/kubeadm/app/phases/controlplane/manifests.go index dcc9cec4bc3..8cbf3fe5815 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests.go @@ -292,6 +292,7 @@ func getControllerManagerCommand(cfg *kubeadmapi.ClusterConfiguration) []string // Let the controller-manager allocate Node CIDRs for the Pod network. // Each node will get a subspace of the address CIDR provided with --pod-network-cidr. if cfg.Networking.PodSubnet != "" { + // TODO(Arvinderpal): Needs to be fixed once PR #73977 lands. Should be a list of maskSizes. maskSize := calcNodeCidrSize(cfg.Networking.PodSubnet) defaultArguments["allocate-node-cidrs"] = "true" defaultArguments["cluster-cidr"] = cfg.Networking.PodSubnet diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index b106a729a8a..3a2fcfca0ec 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -908,7 +908,11 @@ func RunInitNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.InitConfigura FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, manifestsDir)}, HTTPProxyCheck{Proto: "https", Host: cfg.LocalAPIEndpoint.AdvertiseAddress}, HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.ServiceSubnet}, - HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.PodSubnet}, + } + + cidrs := strings.Split(cfg.Networking.PodSubnet, ",") + for _, cidr := range cidrs { + checks = append(checks, HTTPProxyCIDRCheck{Proto: "https", CIDR: cidr}) } if !isSecondaryControlPlane {