Пример #1
0
// GetAPIServerCertCAPool returns the cert pool containing the roots for the API server cert
func GetAPIServerCertCAPool(options MasterConfig) (*x509.CertPool, error) {
	if !UseTLS(options.ServingInfo.ServingInfo) {
		return x509.NewCertPool(), nil
	}

	return cmdutil.CertPoolFromFile(options.ServingInfo.ClientCA)
}
Пример #2
0
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)
	}

}
Пример #3
0
// InstallAPI registers endpoints for an OAuth2 server into the provided mux,
// then returns an array of strings indicating what endpoints were started
// (these are format strings that will expect to be sent a single string value).
func (c *AuthConfig) InstallAPI(container *restful.Container) ([]string, error) {
	mux := c.getMux(container)

	clientStorage, err := clientetcd.NewREST(c.RESTOptionsGetter)
	if err != nil {
		return nil, err
	}
	clientRegistry := clientregistry.NewRegistry(clientStorage)
	combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(c.KubeClient, c.KubeClient, clientRegistry)

	accessTokenStorage, err := accesstokenetcd.NewREST(c.RESTOptionsGetter, combinedOAuthClientGetter, c.EtcdBackends...)
	if err != nil {
		return nil, err
	}
	accessTokenRegistry := accesstokenregistry.NewRegistry(accessTokenStorage)

	authorizeTokenStorage, err := authorizetokenetcd.NewREST(c.RESTOptionsGetter, combinedOAuthClientGetter, c.EtcdBackends...)
	if err != nil {
		return nil, err
	}
	authorizeTokenRegistry := authorizetokenregistry.NewRegistry(authorizeTokenStorage)

	clientAuthStorage, err := clientauthetcd.NewREST(c.RESTOptionsGetter, combinedOAuthClientGetter)
	if err != nil {
		return nil, err
	}
	clientAuthRegistry := clientauthregistry.NewRegistry(clientAuthStorage)

	errorPageHandler, err := c.getErrorHandler()
	if err != nil {
		glog.Fatal(err)
	}

	authRequestHandler, authHandler, authFinalizer, err := c.getAuthorizeAuthenticationHandlers(mux, errorPageHandler)
	if err != nil {
		glog.Fatal(err)
	}

	storage := registrystorage.New(accessTokenRegistry, authorizeTokenRegistry, combinedOAuthClientGetter, registry.NewUserConversion())
	config := osinserver.NewDefaultServerConfig()
	if c.Options.TokenConfig.AuthorizeTokenMaxAgeSeconds > 0 {
		config.AuthorizationExpiration = c.Options.TokenConfig.AuthorizeTokenMaxAgeSeconds
	}
	if c.Options.TokenConfig.AccessTokenMaxAgeSeconds > 0 {
		config.AccessExpiration = c.Options.TokenConfig.AccessTokenMaxAgeSeconds
	}

	grantChecker := registry.NewClientAuthorizationGrantChecker(clientAuthRegistry)
	grantHandler := c.getGrantHandler(mux, authRequestHandler, combinedOAuthClientGetter, clientAuthRegistry)

	server := osinserver.New(
		config,
		storage,
		osinserver.AuthorizeHandlers{
			handlers.NewAuthorizeAuthenticator(
				authRequestHandler,
				authHandler,
				errorPageHandler,
			),
			handlers.NewGrantCheck(
				grantChecker,
				grantHandler,
				errorPageHandler,
			),
			authFinalizer,
		},
		osinserver.AccessHandlers{
			handlers.NewDenyAccessAuthenticator(),
		},
		osinserver.NewDefaultErrorHandler(),
	)
	server.Install(mux, OpenShiftOAuthAPIPrefix)

	if err := CreateOrUpdateDefaultOAuthClients(c.Options.MasterPublicURL, c.AssetPublicAddresses, clientRegistry); err != nil {
		glog.Fatal(err)
	}
	browserClient, err := clientRegistry.GetClient(kapi.NewContext(), OpenShiftBrowserClientID)
	if err != nil {
		glog.Fatal(err)
	}
	osOAuthClientConfig := c.NewOpenShiftOAuthClientConfig(browserClient)
	osOAuthClientConfig.RedirectUrl = c.Options.MasterPublicURL + path.Join(OpenShiftOAuthAPIPrefix, tokenrequest.DisplayTokenEndpoint)

	osOAuthClient, _ := osincli.NewClient(osOAuthClientConfig)
	if len(*c.Options.MasterCA) > 0 {
		rootCAs, err := cmdutil.CertPoolFromFile(*c.Options.MasterCA)
		if err != nil {
			glog.Fatal(err)
		}

		osOAuthClient.Transport = knet.SetTransportDefaults(&http.Transport{
			TLSClientConfig: &tls.Config{RootCAs: rootCAs},
		})
	}

	tokenRequestEndpoints := tokenrequest.NewEndpoints(c.Options.MasterPublicURL, osOAuthClient)
	tokenRequestEndpoints.Install(mux, OpenShiftOAuthAPIPrefix)

	// glog.Infof("oauth server configured as: %#v", server)
	// glog.Infof("auth handler: %#v", authHandler)
	// glog.Infof("auth request handler: %#v", authRequestHandler)
	// glog.Infof("grant checker: %#v", grantChecker)
	// glog.Infof("grant handler: %#v", grantHandler)

	return []string{
		fmt.Sprintf("Started OAuth2 API at %%s%s", OpenShiftOAuthAPIPrefix),
	}, nil
}
Пример #4
0
// GetAPIClientCertCAPool returns the cert pool used to validate client certificates to the API server
func GetAPIClientCertCAPool(options MasterConfig) (*x509.CertPool, error) {
	return cmdutil.CertPoolFromFile(options.ServingInfo.ClientCA)
}
Пример #5
0
func TestSNI(t *testing.T) {
	// Create tempfiles with certs and keys we're going to use
	certNames := map[string]string{}
	for certName, certContents := range sniCerts {
		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 master config
	masterOptions, err := testserver.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Set custom cert
	masterOptions.ServingInfo.NamedCertificates = []configapi.NamedCertificate{
		{
			Names: []string{"customhost.com"},
			CertInfo: configapi.CertInfo{
				CertFile: certNames[sniServerCert],
				KeyFile:  certNames[sniServerKey],
			},
		},
		{
			Names: []string{"*.wildcardhost.com"},
			CertInfo: configapi.CertInfo{
				CertFile: certNames[sniServerCert],
				KeyFile:  certNames[sniServerKey],
			},
		},
	}

	// Start server
	_, err = testserver.StartConfiguredMaster(masterOptions)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Build transports
	sniRoots, err := util.CertPoolFromFile(certNames[sniCACert])
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	sniConfig := &tls.Config{RootCAs: sniRoots}

	generatedRoots, err := util.CertPoolFromFile(masterOptions.ServiceAccountConfig.MasterCA)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	generatedConfig := &tls.Config{RootCAs: generatedRoots}

	insecureConfig := &tls.Config{InsecureSkipVerify: true}

	tests := map[string]struct {
		Hostname   string
		TLSConfig  *tls.Config
		ExpectedOK bool
	}{
		"sni client -> generated ip": {
			Hostname:  "127.0.0.1",
			TLSConfig: sniConfig,
		},
		"sni client -> generated hostname": {
			Hostname:  "openshift",
			TLSConfig: sniConfig,
		},
		"sni client -> sni host": {
			Hostname:   "customhost.com",
			TLSConfig:  sniConfig,
			ExpectedOK: true,
		},
		"sni client -> sni wildcard host": {
			Hostname:   "www.wildcardhost.com",
			TLSConfig:  sniConfig,
			ExpectedOK: true,
		},
		"sni client -> invalid ip": {
			Hostname:  "10.10.10.10",
			TLSConfig: sniConfig,
		},
		"sni client -> invalid host": {
			Hostname:  "invalidhost.com",
			TLSConfig: sniConfig,
		},

		"generated client -> generated ip": {
			Hostname:   "127.0.0.1",
			TLSConfig:  generatedConfig,
			ExpectedOK: true,
		},
		"generated client -> generated hostname": {
			Hostname:   "openshift",
			TLSConfig:  generatedConfig,
			ExpectedOK: true,
		},
		"generated client -> sni host": {
			Hostname:  "customhost.com",
			TLSConfig: generatedConfig,
		},
		"generated client -> sni wildcard host": {
			Hostname:  "www.wildcardhost.com",
			TLSConfig: generatedConfig,
		},
		"generated client -> invalid ip": {
			Hostname:  "10.10.10.10",
			TLSConfig: generatedConfig,
		},
		"generated client -> invalid host": {
			Hostname:  "invalidhost.com",
			TLSConfig: generatedConfig,
		},

		"insecure client -> generated ip": {
			Hostname:   "127.0.0.1",
			TLSConfig:  insecureConfig,
			ExpectedOK: true,
		},
		"insecure client -> generated hostname": {
			Hostname:   "openshift",
			TLSConfig:  insecureConfig,
			ExpectedOK: true,
		},
		"insecure client -> sni host": {
			Hostname:   "customhost.com",
			TLSConfig:  insecureConfig,
			ExpectedOK: true,
		},
		"insecure client -> sni wildcard host": {
			Hostname:   "www.wildcardhost.com",
			TLSConfig:  insecureConfig,
			ExpectedOK: true,
		},
		"insecure client -> invalid ip": {
			Hostname:   "10.10.10.10",
			TLSConfig:  insecureConfig,
			ExpectedOK: true,
		},
		"insecure client -> invalid host": {
			Hostname:   "invalidhost.com",
			TLSConfig:  insecureConfig,
			ExpectedOK: true,
		},
	}

	masterPublicURL, err := url.Parse(masterOptions.MasterPublicURL)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	for k, tc := range tests {
		u := *masterPublicURL
		if err != nil {
			t.Errorf("%s: unexpected error: %v", k, err)
			continue
		}
		u.Path = "/healthz"

		if _, port, err := net.SplitHostPort(u.Host); err == nil {
			u.Host = net.JoinHostPort(tc.Hostname, port)
		} else {
			u.Host = tc.Hostname
		}

		req, err := http.NewRequest("GET", u.String(), nil)
		if err != nil {
			t.Errorf("%s: unexpected error: %v", k, err)
			continue
		}

		transport := &http.Transport{
			// Custom Dial func to always dial the real master, no matter what host is asked for
			Dial: func(network, addr string) (net.Conn, error) {
				// t.Logf("%s: Dialing for %s", k, addr)
				return net.Dial(network, masterPublicURL.Host)
			},
			TLSClientConfig: tc.TLSConfig,
		}
		resp, err := transport.RoundTrip(req)
		if tc.ExpectedOK && err != nil {
			t.Errorf("%s: unexpected error: %v", k, err)
			continue
		}
		if !tc.ExpectedOK && err == nil {
			t.Errorf("%s: expected error, got none", k)
			continue
		}
		if err == nil {
			data, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%s: unexpected error: %v", k, err)
				continue
			}
			if string(data) != "ok" {
				t.Errorf("%s: expected %q, got %q", k, "ok", string(data))
				continue
			}
		}
	}
}
Пример #6
0
// InstallAPI registers endpoints for an OAuth2 server into the provided mux,
// then returns an array of strings indicating what endpoints were started
// (these are format strings that will expect to be sent a single string value).
func (c *AuthConfig) InstallAPI(container *restful.Container) []string {
	// TODO: register into container
	mux := container.ServeMux

	accessTokenStorage := accesstokenetcd.NewREST(c.EtcdHelper)
	accessTokenRegistry := accesstokenregistry.NewRegistry(accessTokenStorage)
	authorizeTokenStorage := authorizetokenetcd.NewREST(c.EtcdHelper)
	authorizeTokenRegistry := authorizetokenregistry.NewRegistry(authorizeTokenStorage)
	clientStorage := clientetcd.NewREST(c.EtcdHelper)
	clientRegistry := clientregistry.NewRegistry(clientStorage)
	clientAuthStorage := clientauthetcd.NewREST(c.EtcdHelper)
	clientAuthRegistry := clientauthregistry.NewRegistry(clientAuthStorage)

	authRequestHandler, authHandler, authFinalizer, err := c.getAuthorizeAuthenticationHandlers(mux)
	if err != nil {
		glog.Fatal(err)
	}

	storage := registrystorage.New(accessTokenRegistry, authorizeTokenRegistry, clientRegistry, registry.NewUserConversion())
	config := osinserver.NewDefaultServerConfig()
	if c.Options.TokenConfig.AuthorizeTokenMaxAgeSeconds > 0 {
		config.AuthorizationExpiration = c.Options.TokenConfig.AuthorizeTokenMaxAgeSeconds
	}
	if c.Options.TokenConfig.AccessTokenMaxAgeSeconds > 0 {
		config.AccessExpiration = c.Options.TokenConfig.AccessTokenMaxAgeSeconds
	}

	grantChecker := registry.NewClientAuthorizationGrantChecker(clientAuthRegistry)
	grantHandler := c.getGrantHandler(mux, authRequestHandler, clientRegistry, clientAuthRegistry)

	server := osinserver.New(
		config,
		storage,
		osinserver.AuthorizeHandlers{
			handlers.NewAuthorizeAuthenticator(
				authRequestHandler,
				authHandler,
				handlers.EmptyError{},
			),
			handlers.NewGrantCheck(
				grantChecker,
				grantHandler,
				handlers.EmptyError{},
			),
			authFinalizer,
		},
		osinserver.AccessHandlers{
			handlers.NewDenyAccessAuthenticator(),
		},
		osinserver.NewDefaultErrorHandler(),
	)
	server.Install(mux, OpenShiftOAuthAPIPrefix)

	CreateOrUpdateDefaultOAuthClients(c.Options.MasterPublicURL, c.AssetPublicAddresses, clientRegistry)
	osOAuthClientConfig := c.NewOpenShiftOAuthClientConfig(&OSBrowserClientBase)
	osOAuthClientConfig.RedirectUrl = c.Options.MasterPublicURL + path.Join(OpenShiftOAuthAPIPrefix, tokenrequest.DisplayTokenEndpoint)

	osOAuthClient, _ := osincli.NewClient(osOAuthClientConfig)
	if len(*c.Options.MasterCA) > 0 {
		rootCAs, err := cmdutil.CertPoolFromFile(*c.Options.MasterCA)
		if err != nil {
			glog.Fatal(err)
		}

		osOAuthClient.Transport = kutil.SetTransportDefaults(&http.Transport{
			TLSClientConfig: &tls.Config{RootCAs: rootCAs},
		})
	}

	tokenRequestEndpoints := tokenrequest.NewEndpoints(c.Options.MasterPublicURL, osOAuthClient)
	tokenRequestEndpoints.Install(mux, OpenShiftOAuthAPIPrefix)

	// glog.Infof("oauth server configured as: %#v", server)
	// glog.Infof("auth handler: %#v", authHandler)
	// glog.Infof("auth request handler: %#v", authRequestHandler)
	// glog.Infof("grant checker: %#v", grantChecker)
	// glog.Infof("grant handler: %#v", grantHandler)

	return []string{
		fmt.Sprintf("Started OAuth2 API at %%s%s", OpenShiftOAuthAPIPrefix),
		fmt.Sprintf("Started Login endpoint at %%s%s", OpenShiftLoginPrefix),
	}
}
Пример #7
0
func TestOAuthBasicAuthPassword(t *testing.T) {
	expectedLogin := "******"
	expectedPassword := "******"
	expectedAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(expectedLogin+":"+expectedPassword))

	testcases := map[string]struct {
		RemoteStatus  int
		RemoteHeaders http.Header
		RemoteBody    []byte

		ExpectUsername  string
		ExpectSuccess   bool
		ExpectErrStatus int32
	}{
		"success": {
			RemoteStatus:   200,
			RemoteHeaders:  http.Header{"Content-Type": []string{"application/json"}},
			RemoteBody:     []byte(`{"sub":"remoteusername"}`),
			ExpectSuccess:  true,
			ExpectUsername: "******",
		},
		"401": {
			RemoteStatus:    401,
			RemoteHeaders:   http.Header{"Content-Type": []string{"application/json"}},
			RemoteBody:      []byte(`{"error":"bad-user"}`),
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 401,
		},
		"301": {
			RemoteStatus:    301,
			RemoteHeaders:   http.Header{"Location": []string{"http://www.example.com"}},
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 500,
		},
		"302": {
			RemoteStatus:    302,
			RemoteHeaders:   http.Header{"Location": []string{"http://www.example.com"}},
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 500,
		},
		"303": {
			RemoteStatus:    303,
			RemoteHeaders:   http.Header{"Location": []string{"http://www.example.com"}},
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 500,
		},
		"304": {
			RemoteStatus:    304,
			RemoteHeaders:   http.Header{"Location": []string{"http://www.example.com"}},
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 500,
		},
		"305": {
			RemoteStatus:    305,
			RemoteHeaders:   http.Header{"Location": []string{"http://www.example.com"}},
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 500,
		},
		"404": {
			RemoteStatus:    404,
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 500,
		},
		"500": {
			RemoteStatus:    500,
			ExpectSuccess:   false,
			ExpectUsername:  "",
			ExpectErrStatus: 500,
		},
	}

	// 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
	var (
		remoteStatus  int
		remoteHeaders http.Header
		remoteBody    []byte
	)
	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("Expected auth header %s got %s", expectedAuthHeader, req.Header.Get("Authorization"))
		}

		for k, values := range remoteHeaders {
			for _, v := range values {
				w.Header().Add(k, v)
			}
		}
		w.WriteHeader(remoteStatus)
		w.Write(remoteBody)
	})

	// 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: crypto.SecureTLSConfig(&tls.Config{
			// 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
	testutil.RequireEtcd(t)
	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,
		MappingMethod:   "claim",
		Provider: &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 := restclient.Config{}
	anonConfig.Host = clientConfig.Host
	anonConfig.CAFile = clientConfig.CAFile
	anonConfig.CAData = clientConfig.CAData

	for k, tc := range testcases {
		// Specify the remote server's response
		remoteStatus = tc.RemoteStatus
		remoteHeaders = tc.RemoteHeaders
		remoteBody = tc.RemoteBody

		// Attempt to obtain a token
		accessToken, err := tokencmd.RequestToken(&anonConfig, nil, expectedLogin, expectedPassword)

		// Expected error
		if !tc.ExpectSuccess {
			if err == nil {
				t.Errorf("%s: Expected error, got token=%v", k, accessToken)
			} else if statusErr, ok := err.(*apierrs.StatusError); !ok {
				t.Errorf("%s: expected status error, got %#v", k, err)
			} else if statusErr.ErrStatus.Code != tc.ExpectErrStatus {
				t.Errorf("%s: expected error status %d, got %#v", k, tc.ExpectErrStatus, statusErr)
			}
			continue
		}

		// Expected success
		if err != nil {
			t.Errorf("%s: Unexpected error: %v", k, err)
			continue
		}

		// 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("%s: Unexpected error: %v", k, err)
		}

		user, err := userClient.Users().Get("~")
		if err != nil {
			t.Fatalf("%s: Unexpected error: %v", k, err)
		}
		if user.Name != tc.ExpectUsername {
			t.Fatalf("%s: Expected %v as the user, got %v", k, tc.ExpectUsername, user)
		}

	}

}