// SetOpenShiftDefaults sets the default settings on the passed // client configuration func SetOpenShiftDefaults(config *kclient.Config) error { if len(config.UserAgent) == 0 { config.UserAgent = DefaultOpenShiftUserAgent() } if config.Version == "" { // Clients default to the preferred code API version // TODO: implement version negotiation (highest version supported by server) config.Version = latest.Version } if config.Prefix == "" { switch config.Version { case "v1beta3": config.Prefix = "/osapi" default: config.Prefix = "/oapi" } } version := config.Version versionInterfaces, err := latest.InterfacesFor(version) if err != nil { return fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.Versions, ", ")) } if config.Codec == nil { config.Codec = versionInterfaces.Codec } return nil }
// NewClient returns a new client based on the passed in config. The // codec is ignored, as the dynamic client uses it's own codec. func NewClient(conf *client.Config) (*Client, error) { // avoid changing the original config confCopy := *conf conf = &confCopy conf.Codec = dynamicCodec{} if conf.APIPath == "" { conf.APIPath = "/api" } if len(conf.UserAgent) == 0 { conf.UserAgent = client.DefaultKubernetesUserAgent() } if conf.QPS == 0.0 { conf.QPS = 5.0 } if conf.Burst == 0 { conf.Burst = 10 } cl, err := client.RESTClientFor(conf) if err != nil { return nil, err } return &Client{cl: cl}, nil }
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information func makeServerIdentificationConfig(info clientauth.Info) client.Config { config := client.Config{} config.CAFile = info.CAFile if info.Insecure != nil { config.Insecure = *info.Insecure } return config }
func validateToken(token string, clientConfig *kclient.Config) { if len(token) == 0 { fmt.Println("You must provide a token to validate") return } fmt.Printf("Using token: %v\n", token) clientConfig.BearerToken = token osClient, err := osclient.New(clientConfig) if err != nil { fmt.Printf("Error building osClient: %v\n", err) return } jsonResponse, _, err := getTokenInfo(token, osClient) if err != nil { fmt.Printf("%v\n", err) fmt.Println("Try visiting " + getRequestTokenURL(clientConfig) + " for a new token.") return } fmt.Printf("%v\n", string(jsonResponse)) whoami, err := osClient.Users().Get("~") if err != nil { fmt.Printf("Error making whoami request: %v\n", err) return } whoamiJSON, err := json.Marshal(whoami) if err != nil { fmt.Printf("Error interpretting whoami response: %v\n", err) return } fmt.Printf("%v\n", string(whoamiJSON)) }
func TestOAuthDisabled(t *testing.T) { // Build master config masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Disable OAuth masterOptions.OAuthConfig = nil // Start server clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterOptions) if err != nil { t.Fatalf("unexpected error: %v", err) } client, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure cert auth still works namespaces, err := client.Namespaces().List(kapi.ListOptions{}) if err != nil { t.Fatalf("Unexpected error %v", err) } if len(namespaces.Items) == 0 { t.Errorf("Expected namespaces, got none") } // Use the server and CA info anonConfig := kclient.Config{} anonConfig.Host = clientConfig.Host anonConfig.CAFile = clientConfig.CAFile anonConfig.CAData = clientConfig.CAData // Make sure we can't authenticate using OAuth if _, err := tokencmd.RequestToken(&anonConfig, nil, "username", "password"); err == nil { t.Error("Expected error, got none") } }
// Clients returns an OpenShift and Kubernetes client with the credentials of the named service account // TODO: change return types to client.Interface/kclient.Interface to allow auto-reloading credentials func Clients(config kclient.Config, tokenRetriever TokenRetriever, namespace, name string) (*client.Client, *kclient.Client, error) { // Clear existing auth info config.Username = "" config.Password = "" config.CertFile = "" config.CertData = []byte{} config.KeyFile = "" config.KeyData = []byte{} // For now, just initialize the token once // TODO: refetch the token if the client encounters 401 errors token, err := tokenRetriever.GetToken(namespace, name) if err != nil { return nil, nil, err } config.BearerToken = token c, err := client.New(&config) if err != nil { return nil, nil, err } kc, err := kclient.New(&config) if err != nil { return nil, nil, err } return c, kc, nil }
func clientForUserAgentOrDie(config client.Config, userAgent string) *client.Client { fullUserAgent := client.DefaultKubernetesUserAgent() + "/" + userAgent config.UserAgent = fullUserAgent kubeClient, err := client.New(&config) if err != nil { glog.Fatalf("Invalid API configuration: %v", err) } return kubeClient }
// addChaosToClientConfig injects random errors into client connections if configured. func (s *KubeletServer) addChaosToClientConfig(config *client.Config) { if s.ChaosChance != 0.0 { config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { seed := chaosclient.NewSeed(1) // TODO: introduce a standard chaos package with more tunables - this is just a proof of concept // TODO: introduce random latency and stalls return chaosclient.NewChaosRoundTripper(rt, chaosclient.LogChaos, seed.P(s.ChaosChance, chaosclient.ErrSimulatedConnectionResetByPeer)) } } }
// newAPIClient creates a new client to speak to the kubernetes api service func (r *kubeAPIImpl) newAPIClient() (*unversioned.Client, error) { // step: create the configuration cfg := unversioned.Config{ Host: getURL(), Insecure: config.HTTPInsecure, Version: config.APIVersion, } // check: ensure the token file exists if config.TokenFile != "" { if _, err := os.Stat(config.TokenFile); os.IsNotExist(err) { return nil, fmt.Errorf("the token file: %s does not exist", config.TokenFile) } content, err := ioutil.ReadFile(config.TokenFile) if err != nil { return nil, fmt.Errorf("unable to read the token file: %s, error: %s", config.TokenFile, err) } config.Token = string(content) } // check: are we using a user token to authenticate? if config.Token != "" { cfg.BearerToken = config.Token } // check: are we using a cert to authenticate if config.CaCertFile != "" { cfg.Insecure = false cfg.TLSClientConfig = unversioned.TLSClientConfig{ CAFile: config.CaCertFile, } } // step: initialize the client kube, err := unversioned.New(&cfg) if err != nil { return nil, fmt.Errorf("unable to create a kubernetes api client, reason: %s", err) } return kube, nil }
// MergeWithConfig returns a copy of a client.Config with values from the Info. // The fields of client.Config with a corresponding field in the Info are set // with the value from the Info. func (info Info) MergeWithConfig(c client.Config) (client.Config, error) { var config client.Config = c config.Username = info.User config.Password = info.Password config.CAFile = info.CAFile config.CertFile = info.CertFile config.KeyFile = info.KeyFile config.BearerToken = info.BearerToken if info.Insecure != nil { config.Insecure = *info.Insecure } return config, nil }
// Creates new Heapster REST client. When heapsterHost param is empty string the function // assumes that it is running inside a Kubernetes cluster and connects via service proxy. // heapsterHost param is in the format of protocol://address:port, e.g., http://localhost:8002. func CreateHeapsterRESTClient(heapsterHost string, apiclient *client.Client) ( HeapsterClient, error) { cfg := client.Config{} if heapsterHost == "" { bufferProxyHost := bytes.NewBufferString("http://") bufferProxyHost.WriteString(apiclient.RESTClient.Get().URL().Host) cfg.Host = bufferProxyHost.String() cfg.Prefix = "/api/v1/proxy/namespaces/kube-system/services/heapster/api" } else { cfg.Host = heapsterHost } log.Printf("Creating Heapster REST client for %s%s", cfg.Host, cfg.Prefix) clientFactory := new(ClientFactoryImpl) heapsterClient, err := clientFactory.New(&cfg) if err != nil { return nil, err } return heapsterClient.RESTClient, nil }
// SetOpenShiftDefaults sets the default settings on the passed // client configuration func SetOpenShiftDefaults(config *kclient.Config) error { if len(config.UserAgent) == 0 { config.UserAgent = DefaultOpenShiftUserAgent() } if config.Version == "" { // Clients default to the preferred code API version config.Version = latest.Version } if config.Prefix == "" { config.Prefix = "/oapi" } version := config.Version versionInterfaces, err := latest.InterfacesFor(version) if err != nil { return fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.Versions, ", ")) } if config.Codec == nil { config.Codec = versionInterfaces.Codec } return nil }
// AnonymousClientConfig returns a copy of the given config with all user credentials (cert/key, bearer token, and username/password) removed func AnonymousClientConfig(config kclient.Config) kclient.Config { config.BearerToken = "" config.CertData = nil config.CertFile = "" config.KeyData = nil config.KeyFile = "" config.Username = "" config.Password = "" return config }
func setConfigDefaults(config *unversioned.Config) error { // if testgroup group is not registered, return an error g, err := registered.Group("testgroup") if err != nil { return err } config.APIPath = "/apis" if config.UserAgent == "" { config.UserAgent = unversioned.DefaultKubernetesUserAgent() } // TODO: Unconditionally set the config.Version, until we fix the config. //if config.Version == "" { copyGroupVersion := g.GroupVersion config.GroupVersion = ©GroupVersion //} versionInterfaces, err := g.InterfacesFor(*config.GroupVersion) if err != nil { return fmt.Errorf("Testgroup API version '%s' is not recognized (valid values: %s)", config.GroupVersion, g.GroupVersions) } config.Codec = versionInterfaces.Codec if config.QPS == 0 { config.QPS = 5 } if config.Burst == 0 { config.Burst = 10 } return nil }
func setConfigDefaults(config *unversioned.Config) error { // if legacy group is not registered, return an error g, err := registered.Group("") if err != nil { return err } config.APIPath = "/api" if config.UserAgent == "" { config.UserAgent = unversioned.DefaultKubernetesUserAgent() } // TODO: Unconditionally set the config.Version, until we fix the config. //if config.Version == "" { copyGroupVersion := g.GroupVersion config.GroupVersion = ©GroupVersion //} config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) if config.QPS == 0 { config.QPS = 5 } if config.Burst == 0 { config.Burst = 10 } return nil }
func makeTransport(config *schedulerapi.ExtenderConfig) (http.RoundTripper, error) { var cfg client.Config if config.TLSConfig != nil { cfg.TLSClientConfig = *config.TLSConfig } if config.EnableHttps { hasCA := len(cfg.CAFile) > 0 || len(cfg.CAData) > 0 if !hasCA { cfg.Insecure = true } } tlsConfig, err := client.TLSConfigFor(&cfg) if err != nil { return nil, err } if tlsConfig != nil { return &http.Transport{ TLSClientConfig: tlsConfig, }, nil } return http.DefaultTransport, nil }
// SetOpenShiftDefaults sets the default settings on the passed // client configuration func SetOpenShiftDefaults(config *kclient.Config) error { if len(config.UserAgent) == 0 { config.UserAgent = DefaultOpenShiftUserAgent() } if config.GroupVersion == nil { // Clients default to the preferred code API version groupVersionCopy := latest.Version config.GroupVersion = &groupVersionCopy } if config.Prefix == "" { config.Prefix = "/oapi" } version := config.GroupVersion versionInterfaces, err := latest.InterfacesFor(*version) if err != nil { return fmt.Errorf("API version %q is not recognized (valid values: %v)", version, latest.Versions) } if config.Codec == nil { config.Codec = versionInterfaces.Codec } return nil }
// TODO: evaluate using pkg/client/clientcmd func newKubeClient() (*kclient.Client, error) { var ( config *kclient.Config err error masterURL string ) // If the user specified --kube_master_url, expand env vars and verify it. if *argKubeMasterURL != "" { masterURL, err = expandKubeMasterURL() if err != nil { return nil, err } } if masterURL != "" && *argKubecfgFile == "" { // Only --kube_master_url was provided. config = &kclient.Config{Host: masterURL} } else { // We either have: // 1) --kube_master_url and --kubecfg_file // 2) just --kubecfg_file // 3) neither flag // In any case, the logic is the same. If (3), this will automatically // fall back on the service account token. overrides := &kclientcmd.ConfigOverrides{} overrides.ClusterInfo.Server = masterURL // might be "", but that is OK rules := &kclientcmd.ClientConfigLoadingRules{ExplicitPath: *argKubecfgFile} // might be "", but that is OK if config, err = kclientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides).ClientConfig(); err != nil { return nil, err } } config.Version = k8sAPIVersion glog.Infof("Using %s for kubernetes master", config.Host) glog.Infof("Using kubernetes API %s", config.Version) return kclient.New(config) }
// SetOpenShiftDefaults sets the default settings on the passed // client configuration func SetOpenShiftDefaults(config *kclient.Config) error { if len(config.UserAgent) == 0 { config.UserAgent = DefaultOpenShiftUserAgent() } if config.GroupVersion == nil { // Clients default to the preferred code API version groupVersionCopy := latest.Version config.GroupVersion = &groupVersionCopy } if config.APIPath == "" { config.APIPath = "/oapi" } // groupMeta, err := registered.Group(config.GroupVersion.Group) // if err != nil { // return fmt.Errorf("API group %q is not recognized (valid values: %v)", config.GroupVersion.Group, latest.Versions) // } if config.Codec == nil { config.Codec = kapi.Codecs.LegacyCodec(*config.GroupVersion) // config.Codec = kapi.Codecs.CodecForVersions(groupMeta.Codec, []unversioned.GroupVersion{*config.GroupVersion}, groupMeta.GroupVersions) } return nil }
func TestOAuthLDAP(t *testing.T) { var ( randomSuffix = string(kutil.NewUUID()) providerName = "myldapprovider" bindDN = "uid=admin,ou=company,ou=" + randomSuffix bindPassword = "******" + randomSuffix searchDN = "ou=company,ou=" + randomSuffix searchAttr = "myuid" + randomSuffix searchScope = "one" // must be "one","sub", or "base" searchFilter = "(myAttr=myValue)" // must be a valid LDAP filter format nameAttr1 = "missing-name-attr" nameAttr2 = "a-display-name" + randomSuffix idAttr1 = "missing-id-attr" idAttr2 = "dn" // "dn" is a special value, so don't add a random suffix to make sure we handle it correctly emailAttr1 = "missing-attr" emailAttr2 = "c-mail" + randomSuffix loginAttr1 = "missing-attr" loginAttr2 = "d-mylogin" + randomSuffix myUserUID = "myuser" myUserName = "******" myUserEmail = "*****@*****.**" myUserDN = searchAttr + "=" + myUserUID + "," + searchDN myUserPassword = "******" + randomSuffix ) expectedAttributes := [][]byte{} for _, attr := range sets.NewString(searchAttr, nameAttr1, nameAttr2, idAttr1, idAttr2, emailAttr1, emailAttr2, loginAttr1, loginAttr2).List() { expectedAttributes = append(expectedAttributes, []byte(attr)) } expectedSearchRequest := ldapserver.SearchRequest{ BaseObject: []byte(searchDN), Scope: ldapserver.SearchRequestSingleLevel, DerefAliases: 0, SizeLimit: 2, TimeLimit: 0, TypesOnly: false, Attributes: expectedAttributes, Filter: fmt.Sprintf("(&%s(%s=%s))", searchFilter, searchAttr, myUserUID), } // Start LDAP server ldapAddress, err := testserver.FindAvailableBindAddress(8389, 8400) if err != nil { t.Fatalf("could not allocate LDAP bind address: %v", err) } ldapServer := testutil.NewTestLDAPServer() ldapServer.SetPassword(bindDN, bindPassword) ldapServer.Start(ldapAddress) defer ldapServer.Stop() masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{ Name: providerName, UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.LDAPPasswordIdentityProvider{ URL: fmt.Sprintf("ldap://%s/%s?%s?%s?%s", ldapAddress, searchDN, searchAttr, searchScope, searchFilter), BindDN: bindDN, BindPassword: bindPassword, Insecure: true, CA: "", Attributes: configapi.LDAPAttributeMapping{ ID: []string{idAttr1, idAttr2}, PreferredUsername: []string{loginAttr1, loginAttr2}, Name: []string{nameAttr1, nameAttr2}, Email: []string{emailAttr1, emailAttr2}, }, }, } clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterOptions) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Errorf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Errorf("unexpected error: %v", err) } // Use the server and CA info anonConfig := kclient.Config{} anonConfig.Host = clusterAdminClientConfig.Host anonConfig.CAFile = clusterAdminClientConfig.CAFile anonConfig.CAData = clusterAdminClientConfig.CAData // Make sure we can't authenticate as a missing user ldapServer.ResetRequests() if _, err := tokencmd.RequestToken(&anonConfig, nil, myUserUID, myUserPassword); err == nil { t.Error("Expected error, got none") } if len(ldapServer.BindRequests) != 1 { t.Errorf("Expected a single bind request for the search phase, got %d:\n%#v", len(ldapServer.BindRequests), ldapServer.BindRequests) } if len(ldapServer.SearchRequests) != 1 { t.Errorf("Expected a single search request, got %d:\n%#v", len(ldapServer.BindRequests), ldapServer.BindRequests) } // Add user ldapServer.SetPassword(myUserDN, myUserPassword) ldapServer.AddSearchResult(myUserDN, map[string]string{emailAttr2: myUserEmail, nameAttr2: myUserName, loginAttr2: myUserUID}) // Make sure we can't authenticate with a bad password ldapServer.ResetRequests() if _, err := tokencmd.RequestToken(&anonConfig, nil, myUserUID, "badpassword"); err == nil { t.Error("Expected error, got none") } if len(ldapServer.BindRequests) != 2 { t.Errorf("Expected a bind request for the search phase and a failed bind request for the auth phase, got %d:\n%#v", len(ldapServer.BindRequests), ldapServer.BindRequests) } if len(ldapServer.SearchRequests) != 1 { t.Errorf("Expected a single search request, got %d:\n%#v", len(ldapServer.BindRequests), ldapServer.BindRequests) } // Make sure we can get a token with a good password ldapServer.ResetRequests() accessToken, err := tokencmd.RequestToken(&anonConfig, nil, myUserUID, myUserPassword) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(accessToken) == 0 { t.Errorf("Expected access token, got none") } if len(ldapServer.BindRequests) != 2 { t.Errorf("Expected a bind request for the search phase and a failed bind request for the auth phase, got %d:\n%#v", len(ldapServer.BindRequests), ldapServer.BindRequests) } if len(ldapServer.SearchRequests) != 1 { t.Errorf("Expected a single search request, got %d:\n%#v", len(ldapServer.BindRequests), ldapServer.BindRequests) } if !reflect.DeepEqual(expectedSearchRequest.BaseObject, ldapServer.SearchRequests[0].BaseObject) { t.Errorf("Expected search base DN\n\t%#v\ngot\n\t%#v", string(expectedSearchRequest.BaseObject), string(ldapServer.SearchRequests[0].BaseObject), ) } if !reflect.DeepEqual(expectedSearchRequest.Filter, ldapServer.SearchRequests[0].Filter) { t.Errorf("Expected search filter\n\t%#v\ngot\n\t%#v", string(expectedSearchRequest.Filter), string(ldapServer.SearchRequests[0].Filter), ) } { expectedAttrs := []string{} for _, a := range expectedSearchRequest.Attributes { expectedAttrs = append(expectedAttrs, string(a)) } actualAttrs := []string{} for _, a := range ldapServer.SearchRequests[0].Attributes { actualAttrs = append(actualAttrs, string(a)) } if !reflect.DeepEqual(expectedAttrs, actualAttrs) { t.Errorf("Expected search attributes\n\t%#v\ngot\n\t%#v", expectedAttrs, actualAttrs) } } // Make sure we can use the token, and it represents who we expect userConfig := anonConfig userConfig.BearerToken = accessToken userClient, err := client.New(&userConfig) if err != nil { t.Fatalf("Unexpected error: %v", err) } user, err := userClient.Users().Get("~") if err != nil { t.Fatalf("Unexpected error: %v", err) } if user.Name != myUserUID { t.Fatalf("Expected %s as the user, got %v", myUserUID, user) } // Make sure the identity got created and contained the mapped attributes identity, err := clusterAdminClient.Identities().Get(fmt.Sprintf("%s:%s", providerName, myUserDN)) if err != nil { t.Fatalf("Unexpected error: %v", err) } if identity.ProviderUserName != myUserDN { t.Errorf("Expected %q, got %q", myUserDN, identity.ProviderUserName) } if v := identity.Extra[authapi.IdentityDisplayNameKey]; v != myUserName { t.Errorf("Expected %q, got %q", myUserName, v) } if v := identity.Extra[authapi.IdentityPreferredUsernameKey]; v != myUserUID { t.Errorf("Expected %q, got %q", myUserUID, v) } if v := identity.Extra[authapi.IdentityEmailKey]; v != myUserEmail { t.Errorf("Expected %q, got %q", myUserEmail, v) } }
func TestOAuthBasicAuthPassword(t *testing.T) { remotePrefix := "remote" expectedLogin := "******" expectedPassword := "******" expectedAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(expectedLogin+":"+expectedPassword)) expectedUsername := remotePrefix + expectedLogin // Create tempfiles with certs and keys we're going to use certNames := map[string]string{} for certName, certContents := range basicAuthCerts { f, err := ioutil.TempFile("", certName) if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(f.Name()) if err := ioutil.WriteFile(f.Name(), certContents, os.FileMode(0600)); err != nil { t.Fatalf("unexpected error: %v", err) } certNames[certName] = f.Name() } // Build client cert pool clientCAs, err := util.CertPoolFromFile(certNames[basicAuthRemoteCACert]) if err != nil { t.Fatalf("unexpected error: %v", err) } // Build remote handler remoteHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.TLS == nil { w.WriteHeader(http.StatusUnauthorized) t.Fatalf("Expected TLS") } if len(req.TLS.VerifiedChains) != 1 { w.WriteHeader(http.StatusUnauthorized) t.Fatalf("Expected peer cert verified by server") } if req.Header.Get("Authorization") != expectedAuthHeader { w.WriteHeader(http.StatusUnauthorized) t.Fatalf("Unexpected auth header: %s", req.Header.Get("Authorization")) } w.Header().Set("Content-Type", "application/json") w.Write([]byte(fmt.Sprintf(`{"sub":"%s"}`, expectedUsername))) }) // Start remote server remoteAddr, err := testserver.FindAvailableBindAddress(9443, 9999) if err != nil { t.Fatalf("Couldn't get free address for test server: %v", err) } remoteServer := &http.Server{ Addr: remoteAddr, Handler: remoteHandler, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, TLSConfig: &tls.Config{ // Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) MinVersion: tls.VersionTLS10, // RequireAndVerifyClientCert lets us limit requests to ones with a valid client certificate ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: clientCAs, }, } go func() { if err := remoteServer.ListenAndServeTLS(certNames[basicAuthRemoteServerCert], certNames[basicAuthRemoteServerKey]); err != nil { t.Fatalf("unexpected error: %v", err) } }() // Build master config masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{ Name: "basicauth", UseAsChallenger: true, UseAsLogin: true, Provider: runtime.EmbeddedObject{ Object: &configapi.BasicAuthPasswordIdentityProvider{ RemoteConnectionInfo: configapi.RemoteConnectionInfo{ URL: fmt.Sprintf("https://%s", remoteAddr), CA: certNames[basicAuthRemoteCACert], ClientCert: configapi.CertInfo{ CertFile: certNames[basicAuthClientCert], KeyFile: certNames[basicAuthClientKey], }, }, }, }, } // Start server clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterOptions) if err != nil { t.Fatalf("unexpected error: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Use the server and CA info anonConfig := kclient.Config{} anonConfig.Host = clientConfig.Host anonConfig.CAFile = clientConfig.CAFile anonConfig.CAData = clientConfig.CAData // Make sure we can get a token accessToken, err := tokencmd.RequestToken(&anonConfig, nil, expectedLogin, expectedPassword) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(accessToken) == 0 { t.Errorf("Expected access token, got none") } // Make sure we can use the token, and it represents who we expect userConfig := anonConfig userConfig.BearerToken = accessToken userClient, err := client.New(&userConfig) if err != nil { t.Fatalf("Unexpected error: %v", err) } user, err := userClient.Users().Get("~") if err != nil { t.Fatalf("Unexpected error: %v", err) } if user.Name != expectedUsername { t.Fatalf("Expected username as the user, got %v", user) } }
func CreateKubeSources(uri *url.URL, c cache.Cache) ([]api.Source, error) { var ( kubeConfig *kube_client.Config err error ) opts := uri.Query() configOverrides, err := getConfigOverrides(uri) if err != nil { return nil, err } inClusterConfig := defaultInClusterConfig if len(opts["inClusterConfig"]) > 0 { inClusterConfig, err = strconv.ParseBool(opts["inClusterConfig"][0]) if err != nil { return nil, err } } if inClusterConfig { kubeConfig, err = kube_client.InClusterConfig() if err != nil { return nil, err } if configOverrides.ClusterInfo.Server != "" { kubeConfig.Host = configOverrides.ClusterInfo.Server } kubeConfig.Version = configOverrides.ClusterInfo.APIVersion } else { authFile := "" if len(opts["auth"]) > 0 { authFile = opts["auth"][0] } if authFile != "" { if kubeConfig, err = kubeClientCmd.NewNonInteractiveDeferredLoadingClientConfig( &kubeClientCmd.ClientConfigLoadingRules{ExplicitPath: authFile}, configOverrides).ClientConfig(); err != nil { return nil, err } } else { kubeConfig = &kube_client.Config{ Host: configOverrides.ClusterInfo.Server, Version: configOverrides.ClusterInfo.APIVersion, Insecure: configOverrides.ClusterInfo.InsecureSkipTLSVerify, } } } if len(kubeConfig.Host) == 0 { return nil, fmt.Errorf("invalid kubernetes master url specified") } if len(kubeConfig.Version) == 0 { return nil, fmt.Errorf("invalid kubernetes API version specified") } useServiceAccount := defaultUseServiceAccount if len(opts["useServiceAccount"]) >= 1 { useServiceAccount, err = strconv.ParseBool(opts["useServiceAccount"][0]) if err != nil { return nil, err } } if useServiceAccount { // If a readable service account token exists, then use it if contents, err := ioutil.ReadFile(defaultServiceAccountFile); err == nil { kubeConfig.BearerToken = string(contents) } } kubeClient := kube_client.NewOrDie(kubeConfig) nodesApi, err := nodes.NewKubeNodes(kubeClient) if err != nil { return nil, err } kubeletPort := defaultKubeletPort if len(opts["kubeletPort"]) >= 1 { kubeletPort, err = strconv.Atoi(opts["kubeletPort"][0]) if err != nil { return nil, err } } kubeletHttps := defaultKubeletHttps if len(opts["kubeletHttps"]) >= 1 { kubeletHttps, err = strconv.ParseBool(opts["kubeletHttps"][0]) if err != nil { return nil, err } } glog.Infof("Using Kubernetes client with master %q and version %q\n", kubeConfig.Host, kubeConfig.Version) glog.Infof("Using kubelet port %d", kubeletPort) kubeletConfig := &kube_client.KubeletConfig{ Port: uint(kubeletPort), EnableHttps: kubeletHttps, TLSClientConfig: kubeConfig.TLSClientConfig, } kubeletApi, err := datasource.NewKubelet(kubeletConfig) if err != nil { return nil, err } kubePodsSource := NewKubePodMetrics(kubeletPort, kubeletApi, nodesApi, newPodsApi(kubeClient)) kubeNodeSource := NewKubeNodeMetrics(kubeletPort, kubeletApi, nodesApi) kubeEventsSource := NewKubeEvents(kubeClient, c) return []api.Source{kubePodsSource, kubeNodeSource, kubeEventsSource}, nil }
func TestOAuthHTPasswd(t *testing.T) { htpasswdFile, err := ioutil.TempFile("", "test.htpasswd") if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(htpasswdFile.Name()) masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{ Name: "htpasswd", UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.HTPasswdPasswordIdentityProvider{ File: htpasswdFile.Name(), }, } clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterOptions) if err != nil { t.Fatalf("unexpected error: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Errorf("unexpected error: %v", err) } // Use the server and CA info anonConfig := kclient.Config{} anonConfig.Host = clientConfig.Host anonConfig.CAFile = clientConfig.CAFile anonConfig.CAData = clientConfig.CAData // Make sure we can't authenticate if _, err := tokencmd.RequestToken(&anonConfig, nil, "username", "password"); err == nil { t.Error("Expected error, got none") } // Update the htpasswd file with output of `htpasswd -n -b username password` userpass := "******" ioutil.WriteFile(htpasswdFile.Name(), []byte(userpass), os.FileMode(0600)) // Make sure we can get a token accessToken, err := tokencmd.RequestToken(&anonConfig, nil, "username", "password") if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(accessToken) == 0 { t.Errorf("Expected access token, got none") } // Make sure we can use the token, and it represents who we expect userConfig := anonConfig userConfig.BearerToken = accessToken userClient, err := client.New(&userConfig) if err != nil { t.Fatalf("Unexpected error: %v", err) } user, err := userClient.Users().Get("~") if err != nil { t.Fatalf("Unexpected error: %v", err) } if user.Name != "username" { t.Fatalf("Expected username as the user, got %v", user) } }
func NewKubeClient(c config.Config, workQueueReady chan struct{}) (*Kube, error) { const resyncPeriod = 10 * time.Second var err error kube := &Kube{} conf := client.Config{ Host: c.Kubernetes.APIendpoint, } // We only pass the username + password if they were set... if c.Kubernetes.Username != "" { conf.Username = c.Kubernetes.Username } if c.Kubernetes.Password != "" { conf.Password = c.Kubernetes.Password } kube.c, err = client.New(&conf) if err != nil { return nil, err } kube.NodeQueue = workqueue.New() kube.ServiceQueue = workqueue.New() // We will hardcode our namespace for now. namespace := api.NamespaceAll // Set up our enqueing function for node objects nodeEnqueueAsAdd := func(obj interface{}) { kube.NodeQueue.Add(QueueEvent{Obj: obj, ObjType: watch.Added}) } nodeEnqueueAsDelete := func(obj interface{}) { kube.NodeQueue.Add(QueueEvent{Obj: obj, ObjType: watch.Deleted}) } nodeEnqueueAsUpdate := func(obj interface{}) { kube.NodeQueue.Add(QueueEvent{Obj: obj, ObjType: watch.Modified}) } // and one for service objects, too serviceEnqueueAsAdd := func(obj interface{}) { kube.ServiceQueue.Add(QueueEvent{Obj: obj, ObjType: watch.Added}) } serviceEnqueueAsDelete := func(obj interface{}) { kube.ServiceQueue.Add(QueueEvent{Obj: obj, ObjType: watch.Deleted}) } serviceEnqueueAsUpdate := func(obj interface{}) { kube.ServiceQueue.Add(QueueEvent{Obj: obj, ObjType: watch.Modified}) } // Set up our event handlers. These get called every time the cache client gets a new event from the API. nodeEventHandlers := framework.ResourceEventHandlerFuncs{ AddFunc: nodeEnqueueAsAdd, DeleteFunc: nodeEnqueueAsDelete, UpdateFunc: func(old, cur interface{}) { // We're only going to add updates to the queue when the node condition changes if old.(*api.Node).Status.Conditions[0].Status != cur.(*api.Node).Status.Conditions[0].Status { nodeEnqueueAsUpdate(cur) } }, } serviceEventHandlers := framework.ResourceEventHandlerFuncs{ AddFunc: serviceEnqueueAsAdd, DeleteFunc: serviceEnqueueAsDelete, UpdateFunc: func(old, cur interface{}) { if !reflect.DeepEqual(old, cur) { serviceEnqueueAsUpdate(cur) } }, } kube.NodeLister.Store, kube.nodeController = framework.NewInformer( cache.NewListWatchFromClient( kube.c, "nodes", namespace, fields.Everything()), &api.Node{}, resyncPeriod, nodeEventHandlers) kube.ServiceLister.Store, kube.serviceController = framework.NewInformer( cache.NewListWatchFromClient( kube.c, "services", namespace, fields.Everything()), &api.Service{}, resyncPeriod, serviceEventHandlers) go kube.serviceController.Run(util.NeverStop) go kube.nodeController.Run(util.NeverStop) for !kube.serviceController.HasSynced() && !kube.nodeController.HasSynced() { log.Println("Waiting for serviceController and nodeController to sync...") time.Sleep(500 * time.Millisecond) } // Signal that the queue is ready close(workQueueReady) return kube, nil }
// TestOAuthRequestHeader checks the following scenarios: // * request containing remote user header is ignored if it doesn't have client cert auth // * request containing remote user header is honored if it has client cert auth // * unauthenticated requests are redirected to an auth proxy // * login command succeeds against a request-header identity provider via redirection to an auth proxy func TestOAuthRequestHeader(t *testing.T) { // Test data used by auth proxy users := map[string]string{ "myusername": "******", } // Write cert we're going to use to verify OAuth requestheader requests caFile, err := ioutil.TempFile("", "test.crt") if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(caFile.Name()) if err := ioutil.WriteFile(caFile.Name(), rootCACert, os.FileMode(0600)); err != nil { t.Fatalf("unexpected error: %v", err) } // Get master config masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } masterURL, _ := url.Parse(masterOptions.OAuthConfig.MasterPublicURL) // Set up an auth proxy var proxyTransport http.RoundTripper proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Decide whether to challenge username, password, hasBasicAuth := r.BasicAuth() if correctPassword, hasUser := users[username]; !hasBasicAuth || !hasUser || password != correctPassword { w.Header().Set("WWW-Authenticate", "Basic realm=Protected Area") w.WriteHeader(401) return } // Swap the scheme and host to the master, keeping path and params the same proxyURL := r.URL proxyURL.Scheme = masterURL.Scheme proxyURL.Host = masterURL.Host // Build a request, copying the original method, body, and headers, overriding the remote user headers proxyRequest, _ := http.NewRequest(r.Method, proxyURL.String(), r.Body) proxyRequest.Header = r.Header proxyRequest.Header.Set("My-Remote-User", username) proxyRequest.Header.Set("SSO-User", "") // Round trip to the back end response, err := proxyTransport.RoundTrip(r) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer response.Body.Close() // Copy response back to originator for k, v := range response.Header { w.Header()[k] = v } w.WriteHeader(response.StatusCode) if _, err := io.Copy(w, response.Body); err != nil { t.Fatalf("Unexpected error: %v", err) } })) defer proxyServer.Close() masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{ Name: "requestheader", UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.RequestHeaderIdentityProvider{ ChallengeURL: proxyServer.URL + "/oauth/authorize?${query}", LoginURL: "http://www.example.com/login?then=${url}", ClientCA: caFile.Name(), Headers: []string{"My-Remote-User", "SSO-User"}, }, } // Start server clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterOptions) if err != nil { t.Fatalf("unexpected error: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Use the server and CA info, but no client cert info anonConfig := kclient.Config{} anonConfig.Host = clientConfig.Host anonConfig.CAFile = clientConfig.CAFile anonConfig.CAData = clientConfig.CAData anonTransport, err := kclient.TransportFor(&anonConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Use the server and CA info, with cert info proxyConfig := anonConfig proxyConfig.CertData = clientCert proxyConfig.KeyData = clientKey proxyTransport, err = kclient.TransportFor(&proxyConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Build the authorize request, spoofing a remote user header authorizeURL := clientConfig.Host + "/oauth/authorize?client_id=openshift-challenging-client&response_type=token" req, err := http.NewRequest("GET", authorizeURL, nil) req.Header.Set("My-Remote-User", "myuser") // Make the request without cert auth resp, err := anonTransport.RoundTrip(req) if err != nil { t.Fatalf("unexpected error: %v", err) } proxyRedirect, err := resp.Location() if err != nil { t.Fatalf("expected spoofed remote user header to get 302 redirect, got error: %v", err) } if proxyRedirect.String() != proxyServer.URL+"/oauth/authorize?client_id=openshift-challenging-client&response_type=token" { t.Fatalf("expected redirect to proxy endpoint, got redirected to %v", proxyRedirect.String()) } // Request the redirected URL, which should cause the proxy to make the same request with cert auth req, err = http.NewRequest("GET", proxyRedirect.String(), nil) req.Header.Set("My-Remote-User", "myuser") req.SetBasicAuth("myusername", "mypassword") resp, err = proxyTransport.RoundTrip(req) if err != nil { t.Fatalf("unexpected error: %v", err) } tokenRedirect, err := resp.Location() if err != nil { t.Fatalf("expected 302 redirect, got error: %v", err) } if tokenRedirect.Query().Get("error") != "" { t.Fatalf("expected successful token request, got error %v", tokenRedirect.String()) } // Extract the access_token // group #0 is everything. #1 #2 #3 accessTokenRedirectRegex := regexp.MustCompile(`(^|&)access_token=([^&]+)($|&)`) accessToken := "" if matches := accessTokenRedirectRegex.FindStringSubmatch(tokenRedirect.Fragment); matches != nil { accessToken = matches[2] } if accessToken == "" { t.Fatalf("Expected access token, got %s", tokenRedirect.String()) } // Make sure we can use the token, and it represents who we expect userConfig := anonConfig userConfig.BearerToken = accessToken userClient, err := client.New(&userConfig) if err != nil { t.Fatalf("Unexpected error: %v", err) } user, err := userClient.Users().Get("~") if err != nil { t.Fatalf("Unexpected error: %v", err) } if user.Name != "myusername" { t.Fatalf("Expected myusername as the user, got %v", user) } // Get the master CA data for the login command masterCAFile := userConfig.CAFile if masterCAFile == "" { // Write master ca data tmpFile, err := ioutil.TempFile("", "ca.crt") if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(tmpFile.Name()) if err := ioutil.WriteFile(tmpFile.Name(), userConfig.CAData, os.FileMode(0600)); err != nil { t.Fatalf("unexpected error: %v", err) } masterCAFile = tmpFile.Name() } // Attempt a login using a redirecting auth proxy loginOutput := &bytes.Buffer{} loginOptions := &cmd.LoginOptions{ Server: anonConfig.Host, CAFile: masterCAFile, StartingKubeConfig: &clientcmdapi.Config{}, Reader: bytes.NewBufferString("myusername\nmypassword\n"), Out: loginOutput, } if err := loginOptions.GatherInfo(); err != nil { t.Fatalf("Error trying to determine server info: %v\n%v", err, loginOutput.String()) } if loginOptions.Username != "myusername" { t.Fatalf("Unexpected user after authentication: %#v", loginOptions) } if len(loginOptions.Config.BearerToken) == 0 { t.Fatalf("Expected token after authentication: %#v", loginOptions.Config) } }