diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index 091f8a01a55..70edf13f39a 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -124,8 +124,10 @@ type MasterConfiguration struct { // API struct contains elements of API server address. type API struct { - // AdvertiseAddress sets the address for the API server to advertise. + // AdvertiseAddress sets the IP address for the API server to advertise. AdvertiseAddress string + // ControlPlaneEndpoint sets the DNS address for the API server + ControlPlaneEndpoint string // BindPort sets the secure port for the API Server to bind to. // Defaults to 6443. BindPort int32 diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go index a4e4ccced6a..066dd7979cf 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go @@ -116,8 +116,10 @@ type MasterConfiguration struct { // API struct contains elements of API server address. type API struct { - // AdvertiseAddress sets the address for the API server to advertise. + // AdvertiseAddress sets the IP address for the API server to advertise. AdvertiseAddress string `json:"advertiseAddress"` + // ControlPlaneEndpoint sets the DNS address for the API server + ControlPlaneEndpoint string `json:"controlPlaneEndpoint"` // BindPort sets the secure port for the API Server to bind to. // Defaults to 6443. BindPort int32 `json:"bindPort"` diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go index b8a312c4368..80c47bf9b90 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go @@ -67,6 +67,7 @@ func RegisterConversions(scheme *runtime.Scheme) error { func autoConvert_v1alpha1_API_To_kubeadm_API(in *API, out *kubeadm.API, s conversion.Scope) error { out.AdvertiseAddress = in.AdvertiseAddress + out.ControlPlaneEndpoint = in.ControlPlaneEndpoint out.BindPort = in.BindPort return nil } @@ -78,6 +79,7 @@ func Convert_v1alpha1_API_To_kubeadm_API(in *API, out *kubeadm.API, s conversion func autoConvert_kubeadm_API_To_v1alpha1_API(in *kubeadm.API, out *API, s conversion.Scope) error { out.AdvertiseAddress = in.AdvertiseAddress + out.ControlPlaneEndpoint = in.ControlPlaneEndpoint out.BindPort = in.BindPort return nil } diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index c765ca301ba..27c31e0157a 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -351,7 +351,7 @@ func ValidateAPIEndpoint(c *kubeadm.MasterConfiguration, fldPath *field.Path) fi endpoint, err := kubeadmutil.GetMasterEndpoint(c) if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, endpoint, "Invalid API Endpoint")) + allErrs = append(allErrs, field.Invalid(fldPath, endpoint, err.Error())) } return allErrs } diff --git a/cmd/kubeadm/app/cmd/phases/controlplane.go b/cmd/kubeadm/app/cmd/phases/controlplane.go index 5c4066f876f..96f2610870a 100644 --- a/cmd/kubeadm/app/cmd/phases/controlplane.go +++ b/cmd/kubeadm/app/cmd/phases/controlplane.go @@ -143,7 +143,7 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane`) if properties.use == "all" || properties.use == "apiserver" { - cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address or DNS name the API server is accessible on") + cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address of the API server is accessible on") cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "The port the API server is accessible on") cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "The range of IP address used for service VIPs") cmd.Flags().StringVar(&featureGatesString, "feature-gates", featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+ diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go index 83af9150554..05515e3a0e0 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -41,6 +41,7 @@ func TestPrintConfiguration(t *testing.T) { api: advertiseAddress: "" bindPort: 0 + controlPlaneEndpoint: "" auditPolicy: logDir: "" path: "" @@ -78,6 +79,7 @@ func TestPrintConfiguration(t *testing.T) { api: advertiseAddress: "" bindPort: 0 + controlPlaneEndpoint: "" auditPolicy: logDir: "" path: "" @@ -120,6 +122,7 @@ func TestPrintConfiguration(t *testing.T) { api: advertiseAddress: "" bindPort: 0 + controlPlaneEndpoint: "" auditPolicy: logDir: "" path: "" diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index 57742a7a312..258c5b1da26 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -548,5 +548,10 @@ func getAltNames(cfg *kubeadmapi.MasterConfiguration) (*certutil.AltNames, error } } + // add api server dns advertise address + if len(cfg.API.ControlPlaneEndpoint) > 0 { + altNames.DNSNames = append(altNames.DNSNames, cfg.API.ControlPlaneEndpoint) + } + return altNames, nil } diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index 8b135100ba1..1532c5e0403 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -261,8 +261,9 @@ func TestWriteKeyFilesIfNotExist(t *testing.T) { func TestGetAltNames(t *testing.T) { hostname := "valid-hostname" advertiseIP := "1.2.3.4" + controlPlaneEndpoint := "api.k8s.io" cfg := &kubeadmapi.MasterConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: advertiseIP}, + API: kubeadmapi.API{AdvertiseAddress: advertiseIP, ControlPlaneEndpoint: controlPlaneEndpoint}, Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, NodeName: hostname, APIServerCertSANs: []string{"10.1.245.94", "10.1.245.95"}, @@ -273,7 +274,7 @@ func TestGetAltNames(t *testing.T) { t.Fatalf("failed calling getAltNames: %v", err) } - expectedDNSNames := []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local"} + expectedDNSNames := []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local", controlPlaneEndpoint} for _, DNSName := range expectedDNSNames { found := false for _, val := range altNames.DNSNames { diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go index 947bc75ab2f..0d74110ca1d 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go @@ -71,12 +71,25 @@ func TestGetKubeConfigSpecs(t *testing.T) { NodeName: "valid-node-name", } + // Creates a Master Configuration pointing to the pkidir folder + cfgDNS := &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ControlPlaneEndpoint: "api.k8s.io", BindPort: 1234}, + CertificatesDir: pkidir, + NodeName: "valid-node-name", + } + // Executes getKubeConfigSpecs specs, err := getKubeConfigSpecs(cfg) if err != nil { t.Fatal("getKubeConfigSpecs failed!") } + // Executes getKubeConfigSpecs + specsDNS, err := getKubeConfigSpecs(cfgDNS) + if err != nil { + t.Fatal("getKubeConfigSpecs failed!") + } + var assertions = []struct { kubeConfigFile string clientName string @@ -136,6 +149,39 @@ func TestGetKubeConfigSpecs(t *testing.T) { } else { t.Errorf("getKubeConfigSpecs didn't create spec for %s ", assertion.kubeConfigFile) } + + // assert the spec for the kubeConfigFile exists + if spec, ok := specsDNS[assertion.kubeConfigFile]; ok { + + // Assert clientName + if spec.ClientName != assertion.clientName { + t.Errorf("getKubeConfigSpecs for %s clientName is %s, expected %s", assertion.kubeConfigFile, spec.ClientName, assertion.clientName) + } + + // Assert Organizations + if spec.ClientCertAuth == nil || !reflect.DeepEqual(spec.ClientCertAuth.Organizations, assertion.organizations) { + t.Errorf("getKubeConfigSpecs for %s Organizations is %v, expected %v", assertion.kubeConfigFile, spec.ClientCertAuth.Organizations, assertion.organizations) + } + + // Asserts MasterConfiguration values injected into spec + masterEndpoint, err := kubeadmutil.GetMasterEndpoint(cfgDNS) + if err != nil { + t.Error(err) + } + if spec.APIServer != masterEndpoint { + t.Errorf("getKubeConfigSpecs didn't injected cfg.APIServer endpoint into spec for %s", assertion.kubeConfigFile) + } + + // Asserts CA certs and CA keys loaded into specs + if spec.CACert == nil { + t.Errorf("getKubeConfigSpecs didn't loaded CACert into spec for %s!", assertion.kubeConfigFile) + } + if spec.ClientCertAuth == nil || spec.ClientCertAuth.CAKey == nil { + t.Errorf("getKubeConfigSpecs didn't loaded CAKey into spec for %s!", assertion.kubeConfigFile) + } + } else { + t.Errorf("getKubeConfigSpecs didn't create spec for %s ", assertion.kubeConfigFile) + } } } diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 9fb42ce446f..2d9c9a118bd 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -28,6 +28,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", ], ) diff --git a/cmd/kubeadm/app/util/config/masterconfig.go b/cmd/kubeadm/app/util/config/masterconfig.go index e4bec1d6dcc..d109dab38f7 100644 --- a/cmd/kubeadm/app/util/config/masterconfig.go +++ b/cmd/kubeadm/app/util/config/masterconfig.go @@ -34,7 +34,7 @@ import ( "k8s.io/kubernetes/pkg/util/version" ) -// SetInitDynamicDefaults checks and sets conifugration values for Master node +// SetInitDynamicDefaults checks and sets configuration values for Master node func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { // Choose the right address for the API Server to advertise. If the advertise address is localhost or 0.0.0.0, the default interface's IP address is used diff --git a/cmd/kubeadm/app/util/endpoint.go b/cmd/kubeadm/app/util/endpoint.go index d3e32dbecea..fb46d5b8165 100644 --- a/cmd/kubeadm/app/util/endpoint.go +++ b/cmd/kubeadm/app/util/endpoint.go @@ -21,12 +21,14 @@ import ( "net" "strconv" + "k8s.io/apimachinery/pkg/util/validation" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) // GetMasterEndpoint returns a properly formatted Master Endpoint // or passes the error from GetMasterHostPort. func GetMasterEndpoint(cfg *kubeadmapi.MasterConfiguration) (string, error) { + hostPort, err := GetMasterHostPort(cfg) if err != nil { return "", err @@ -37,15 +39,25 @@ func GetMasterEndpoint(cfg *kubeadmapi.MasterConfiguration) (string, error) { // GetMasterHostPort returns a properly formatted Master IP/port pair or error // if the IP address can not be parsed or port is outside the valid TCP range. func GetMasterHostPort(cfg *kubeadmapi.MasterConfiguration) (string, error) { - masterIP := net.ParseIP(cfg.API.AdvertiseAddress) - if masterIP == nil { - return "", fmt.Errorf("error parsing address %s", cfg.API.AdvertiseAddress) + var masterIP string + if len(cfg.API.ControlPlaneEndpoint) > 0 { + errs := validation.IsDNS1123Subdomain(cfg.API.ControlPlaneEndpoint) + if len(errs) > 0 { + return "", fmt.Errorf("error parsing `ControlPlaneEndpoint` to valid dns subdomain with errors: %s", errs) + } + masterIP = cfg.API.ControlPlaneEndpoint + } else { + ip := net.ParseIP(cfg.API.AdvertiseAddress) + if ip == nil { + return "", fmt.Errorf("error parsing address %s", cfg.API.AdvertiseAddress) + } + masterIP = ip.String() } if cfg.API.BindPort < 0 || cfg.API.BindPort > 65535 { return "", fmt.Errorf("api server port must be between 0 and 65535") } - hostPort := net.JoinHostPort(masterIP.String(), strconv.Itoa(int(cfg.API.BindPort))) + hostPort := net.JoinHostPort(masterIP, strconv.Itoa(int(cfg.API.BindPort))) return hostPort, nil } diff --git a/cmd/kubeadm/app/util/endpoint_test.go b/cmd/kubeadm/app/util/endpoint_test.go index f5bef19dbb5..bd3be4a7e84 100644 --- a/cmd/kubeadm/app/util/endpoint_test.go +++ b/cmd/kubeadm/app/util/endpoint_test.go @@ -29,6 +29,40 @@ func TestGetMasterEndpoint(t *testing.T) { endpoint string expected bool }{ + { + name: "bad controlplane endpooint dns", + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ + ControlPlaneEndpoint: "bad!!cp.k8s.io", + BindPort: 1234, + }, + }, + endpoint: "https://cp.k8s.io:1234", + expected: false, + }, + { + name: "both DNS and IP passed", + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ + AdvertiseAddress: "1.2.3.4", + ControlPlaneEndpoint: "cp.k8s.io", + BindPort: 1234, + }, + }, + endpoint: "https://cp.k8s.io:1234", + expected: true, + }, + { + name: "valid DNS endpoint", + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ + ControlPlaneEndpoint: "cp.k8s.io", + BindPort: 1234, + }, + }, + endpoint: "https://cp.k8s.io:1234", + expected: true, + }, { name: "valid IPv4 endpoint", cfg: &kubeadmapi.MasterConfiguration{