func TestNormalizeServerURL(t *testing.T) { testCases := []struct { originalServerURL string normalizedServerURL string }{ { originalServerURL: "localhost", normalizedServerURL: "https://localhost:443", }, { originalServerURL: "https://localhost", normalizedServerURL: "https://localhost:443", }, { originalServerURL: "localhost:443", normalizedServerURL: "https://localhost:443", }, { originalServerURL: "https://localhost:443", normalizedServerURL: "https://localhost:443", }, { originalServerURL: "http://localhost", normalizedServerURL: "http://localhost:80", }, { originalServerURL: "localhost:8443", normalizedServerURL: "https://localhost:8443", }, } for _, test := range testCases { t.Logf("evaluating test: normalize %s -> %s", test.originalServerURL, test.normalizedServerURL) normalized, err := config.NormalizeServerURL(test.originalServerURL) if err != nil { t.Errorf("unexpected error normalizing %s: %s", test.originalServerURL, err) } if normalized != test.normalizedServerURL { t.Errorf("unexpected server URL normalization result for %s: expected %s, got %s", test.originalServerURL, test.normalizedServerURL, normalized) } } }
// getClientConfig returns back the current clientConfig as we know it. If there is no clientConfig, it builds one with enough information // to talk to a server. This may involve user prompts. This method is not threadsafe. func (o *LoginOptions) getClientConfig() (*kclient.Config, error) { if o.Config != nil { return o.Config, nil } clientConfig := &kclient.Config{} if len(o.Server) == 0 { // we need to have a server to talk to if cmdutil.IsTerminal(o.Reader) { for !o.serverProvided() { defaultServer := defaultClusterURL promptMsg := fmt.Sprintf("Server [%s]: ", defaultServer) o.Server = cmdutil.PromptForStringWithDefault(o.Reader, defaultServer, promptMsg) } } } // normalize the provided server to a format expected by config serverNormalized, err := config.NormalizeServerURL(o.Server) if err != nil { return nil, err } o.Server = serverNormalized clientConfig.Host = o.Server if len(o.CAFile) > 0 { clientConfig.CAFile = o.CAFile } else { // check all cluster stanzas to see if we already have one with this URL that contains a client cert for _, cluster := range o.StartingKubeConfig.Clusters { if cluster.Server == clientConfig.Host { if len(cluster.CertificateAuthority) > 0 { clientConfig.CAFile = cluster.CertificateAuthority break } if len(cluster.CertificateAuthorityData) > 0 { clientConfig.CAData = cluster.CertificateAuthorityData break } } } } // ping to check if server is reachable osClient, err := client.New(clientConfig) if err != nil { return nil, err } result := osClient.Get().AbsPath("/osapi").Do() if result.Error() != nil { switch { case o.InsecureTLS: clientConfig.Insecure = true // certificate issue, prompt user for insecure connection case clientcmd.IsCertificateAuthorityUnknown(result.Error()): // check to see if we already have a cluster stanza that tells us to use --insecure for this particular server. If we don't, then prompt clientConfigToTest := *clientConfig clientConfigToTest.Insecure = true matchingClusters := getMatchingClusters(clientConfigToTest, *o.StartingKubeConfig) if len(matchingClusters) > 0 { clientConfig.Insecure = true } else if cmdutil.IsTerminal(o.Reader) { fmt.Fprintln(o.Out, "The server uses a certificate signed by an unknown authority.") fmt.Fprintln(o.Out, "You can bypass the certificate check, but any data you send to the server could be intercepted by others.") clientConfig.Insecure = cmdutil.PromptForBool(os.Stdin, "Use insecure connections? (y/n): ") if !clientConfig.Insecure { return nil, fmt.Errorf(clientcmd.GetPrettyMessageFor(result.Error())) } fmt.Fprintln(o.Out) } default: return nil, result.Error() } } // check for matching api version if len(o.APIVersion) > 0 { clientConfig.Version = o.APIVersion } o.Config = clientConfig return o.Config, nil }
// getClientConfig returns back the current clientConfig as we know it. If there is no clientConfig, it builds one with enough information // to talk to a server. This may involve user prompts. This method is not threadsafe. func (o *LoginOptions) getClientConfig() (*restclient.Config, error) { if o.Config != nil { return o.Config, nil } if len(o.Server) == 0 { // we need to have a server to talk to if kterm.IsTerminal(o.Reader) { for !o.serverProvided() { defaultServer := defaultClusterURL promptMsg := fmt.Sprintf("Server [%s]: ", defaultServer) o.Server = term.PromptForStringWithDefault(o.Reader, o.Out, defaultServer, promptMsg) } } } clientConfig := &restclient.Config{} // normalize the provided server to a format expected by config serverNormalized, err := config.NormalizeServerURL(o.Server) if err != nil { return nil, err } o.Server = serverNormalized clientConfig.Host = o.Server // use specified CA or find existing CA if len(o.CAFile) > 0 { clientConfig.CAFile = o.CAFile clientConfig.CAData = nil } else if caFile, caData, ok := findExistingClientCA(clientConfig.Host, *o.StartingKubeConfig); ok { clientConfig.CAFile = caFile clientConfig.CAData = caData } // try to TCP connect to the server to make sure it's reachable, and discover // about the need of certificates or insecure TLS if err := dialToServer(*clientConfig); err != nil { switch err.(type) { // certificate authority unknown, check or prompt if we want an insecure // connection or if we already have a cluster stanza that tells us to // connect to this particular server insecurely case x509.UnknownAuthorityError, x509.HostnameError, x509.CertificateInvalidError: if o.InsecureTLS || hasExistingInsecureCluster(*clientConfig, *o.StartingKubeConfig) || promptForInsecureTLS(o.Reader, o.Out, err) { clientConfig.Insecure = true clientConfig.CAFile = "" clientConfig.CAData = nil } else { return nil, clientcmd.GetPrettyErrorForServer(err, o.Server) } // TLS record header errors, like oversized record which usually means // the server only supports "http" case tls.RecordHeaderError: return nil, clientcmd.GetPrettyErrorForServer(err, o.Server) default: // suggest the port used in the cluster URL by default, in case we're not already using it host, port, parsed, err1 := getHostPort(o.Server) _, defaultClusterPort, _, err2 := getHostPort(defaultClusterURL) if err1 == nil && err2 == nil && port != defaultClusterPort { parsed.Host = net.JoinHostPort(host, defaultClusterPort) return nil, fmt.Errorf("%s\nYou may want to try using the default cluster port: %s", err.Error(), parsed.String()) } return nil, err } } // check for matching api version if !o.APIVersion.Empty() { clientConfig.GroupVersion = &o.APIVersion } o.Config = clientConfig return o.Config, nil }