func TestAccessOriginWebConsole(t *testing.T) {
	masterOptions, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if _, err = testutil.StartConfiguredMaster(masterOptions); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	for endpoint, exp := range map[string]struct {
		statusCode int
		location   string
	}{
		"":                    {http.StatusFound, masterOptions.AssetConfig.PublicURL},
		"healthz":             {http.StatusOK, ""},
		"login":               {http.StatusOK, ""},
		"oauth/token/request": {http.StatusFound, masterOptions.AssetConfig.MasterPublicURL + "/oauth/authorize"},
		"console":             {http.StatusMovedPermanently, "/console/"},
		"console/":            {http.StatusOK, ""},
		"console/java":        {http.StatusOK, ""},
	} {
		url := masterOptions.AssetConfig.MasterPublicURL + "/" + endpoint
		tryAccessURL(t, url, exp.statusCode, exp.location)
	}
}
func TestOAuthDisabled(t *testing.T) {
	// Build master config
	masterOptions, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Disable OAuth
	masterOptions.OAuthConfig = nil

	// Start server
	clusterAdminKubeConfig, err := testutil.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(labels.Everything(), fields.Everything())
	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")
	}

}
func TestAccessDisabledWebConsole(t *testing.T) {
	masterOptions, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	masterOptions.DisabledFeatures.Add(configapi.FeatureWebConsole)
	if _, err := testutil.StartConfiguredMaster(masterOptions); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	resp := tryAccessURL(t, masterOptions.AssetConfig.MasterPublicURL+"/", http.StatusOK, "")
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Errorf("failed to read reposponse's body: %v", err)
	} else {
		var value interface{}
		if err = json.Unmarshal(body, &value); err != nil {
			t.Errorf("expected json body which couldn't be parsed: %v, got: %s", err, body)
		}
	}

	for endpoint, exp := range map[string]struct {
		statusCode int
		location   string
	}{
		"healthz":             {http.StatusOK, ""},
		"login":               {http.StatusOK, ""},
		"oauth/token/request": {http.StatusFound, masterOptions.AssetConfig.MasterPublicURL + "/oauth/authorize"},
		"console":             {http.StatusForbidden, ""},
		"console/":            {http.StatusForbidden, ""},
		"console/java":        {http.StatusForbidden, ""},
	} {
		url := masterOptions.AssetConfig.MasterPublicURL + "/" + endpoint
		tryAccessURL(t, url, exp.statusCode, exp.location)
	}
}
Exemple #4
0
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 kutil.NewStringSet(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 := testutil.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 := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{
		Name:            providerName,
		UseAsChallenger: true,
		UseAsLogin:      true,
		Provider: runtime.EmbeddedObject{
			&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.LDAPAttributes{
					ID:                []string{idAttr1, idAttr2},
					PreferredUsername: []string{loginAttr1, loginAttr2},
					Name:              []string{nameAttr1, nameAttr2},
					Email:             []string{emailAttr1, emailAttr2},
				},
			},
		},
	}

	clusterAdminKubeConfig, err := testutil.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.Error("Expected a single bind request for the search phase, got %d:\n%#v", len(ldapServer.BindRequests), ldapServer.BindRequests)
	}
	if len(ldapServer.SearchRequests) != 1 {
		t.Error("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.Error("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.Error("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.Error("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.Error("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.Error("Expected %q, got %q", myUserDN, identity.ProviderUserName)
	}
	if v := identity.Extra[authapi.IdentityDisplayNameKey]; v != myUserName {
		t.Error("Expected %q, got %q", myUserName, v)
	}
	if v := identity.Extra[authapi.IdentityPreferredUsernameKey]; v != myUserUID {
		t.Error("Expected %q, got %q", myUserUID, v)
	}
	if v := identity.Extra[authapi.IdentityEmailKey]; v != myUserEmail {
		t.Error("Expected %q, got %q", myUserEmail, v)
	}

}
func TestOAuthRequestHeader(t *testing.T) {
	// 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)
	}

	masterOptions, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{
		Name:            "requestheader",
		UseAsChallenger: false,
		UseAsLogin:      false,
		Provider: runtime.EmbeddedObject{
			&configapi.RequestHeaderIdentityProvider{
				ClientCA: caFile.Name(),
				Headers:  []string{"My-Remote-User", "SSO-User"},
			},
		},
	}

	// Start server
	clusterAdminKubeConfig, err := testutil.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

	// Build the authorize request with the My-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
	transport, err := kclient.TransportFor(&anonConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	resp, err := transport.RoundTrip(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	redirect, err := resp.Location()
	if err != nil {
		t.Fatalf("expected 302 redirect, got error: %v", err)
	}
	if redirect.Query().Get("error") == "" {
		t.Fatalf("expected unsuccessful token request, got redirected to %v", redirect.String())
	}

	// Use the server and CA info, with cert info
	authProxyConfig := anonConfig
	authProxyConfig.CertData = clientCert
	authProxyConfig.KeyData = clientKey

	// Make the request with cert info
	transport, err = kclient.TransportFor(&authProxyConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	resp, err = transport.RoundTrip(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	redirect, err = resp.Location()
	if err != nil {
		t.Fatalf("expected 302 redirect, got error: %v", err)
	}
	if redirect.Query().Get("error") != "" {
		t.Fatalf("expected successful token request, got error %v", redirect.String())
	}

	// Extract the access_token

	// group #0 is everything.                      #1                #2     #3
	accessTokenRedirectRegex := regexp.MustCompile(`(^|&)access_token=([^&]+)($|&)`)
	accessToken := ""
	if matches := accessTokenRedirectRegex.FindStringSubmatch(redirect.Fragment); matches != nil {
		accessToken = matches[2]
	}
	if accessToken == "" {
		t.Fatalf("Expected access token, got %s", redirect.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 != "myuser" {
		t.Fatalf("Expected myuser as the user, got %v", user)
	}
}
func TestUnprivilegedNewProjectFromTemplate(t *testing.T) {
	namespace := "foo"
	templateName := "bar"

	masterOptions, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	masterOptions.ProjectConfig.ProjectRequestTemplate = namespace + "/" + templateName

	clusterAdminKubeConfig, err := testutil.StartConfiguredMaster(masterOptions)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	valerieClientConfig := *clusterAdminClientConfig
	valerieClientConfig.Username = ""
	valerieClientConfig.Password = ""
	valerieClientConfig.BearerToken = ""
	valerieClientConfig.CertFile = ""
	valerieClientConfig.KeyFile = ""
	valerieClientConfig.CertData = nil
	valerieClientConfig.KeyData = nil

	accessToken, err := tokencmd.RequestToken(&valerieClientConfig, nil, "valerie", "security!")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	valerieClientConfig.BearerToken = accessToken
	valerieOpenshiftClient, err := client.New(&valerieClientConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, err := clusterAdminClient.Projects().Create(&projectapi.Project{ObjectMeta: kapi.ObjectMeta{Name: namespace}}); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	template := projectrequeststorage.DefaultTemplate()
	template.Name = templateName
	template.Namespace = namespace

	template.Objects[0].(*projectapi.Project).Annotations["extra"] = "here"
	_, err = clusterAdminClient.Templates(namespace).Create(template)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	requestProject := oc.NewProjectOptions{
		ProjectName: "new-project",
		DisplayName: "display name here",
		Description: "the special description",

		Client: valerieOpenshiftClient,
		Out:    ioutil.Discard,
	}

	if err := requestProject.Run(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	waitForProject(t, valerieOpenshiftClient, "new-project", 5*time.Second, 10)
	project, err := valerieOpenshiftClient.Projects().Get("new-project")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if project.Annotations["extra"] != "here" {
		t.Errorf("unexpected project %#v", project)
	}

	if err := clusterAdminClient.Templates(namespace).Delete(templateName); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	requestProject.ProjectName = "different"
	// This should fail during the template retrieve
	if err := requestProject.Run(); !kapierrors.IsNotFound(err) {
		t.Fatalf("expected a not found error, but got %v", err)
	}

}
func TestEnforcingServiceAccount(t *testing.T) {
	masterConfig, err := testutil.DefaultMasterOptions()
	masterConfig.ServiceAccountConfig.LimitSecretReferences = false
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminConfig, err := testutil.StartConfiguredMaster(masterConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(clusterAdminConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Get a service account token
	saToken, err := waitForServiceAccountToken(clusterAdminKubeClient, api.NamespaceDefault, serviceaccountadmission.DefaultServiceAccountName, 20, time.Second)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if len(saToken) == 0 {
		t.Errorf("token was not created")
	}

	pod := &api.Pod{}
	pod.Name = "foo"
	pod.Namespace = api.NamespaceDefault
	pod.Spec.ServiceAccountName = serviceaccountadmission.DefaultServiceAccountName

	container := api.Container{}
	container.Name = "foo"
	container.Image = "openshift/hello-openshift"
	pod.Spec.Containers = []api.Container{container}

	secretVolume := api.Volume{}
	secretVolume.Name = "bar-vol"
	secretVolume.Secret = &api.SecretVolumeSource{}
	secretVolume.Secret.SecretName = "bar"
	pod.Spec.Volumes = []api.Volume{secretVolume}

	err = wait.Poll(100*time.Millisecond, 5*time.Second, func() (bool, error) {
		if _, err := clusterAdminKubeClient.Pods(api.NamespaceDefault).Create(pod); err != nil {
			// The SA admission controller cache seems to take forever to update.  This check comes after the limit check, so until we get it sorted out
			// check if we're getting this particular error
			if strings.Contains(err.Error(), "no API token found for service account") {
				return true, nil
			}

			t.Log(err)
			return false, nil
		}

		return true, nil
	})
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	clusterAdminKubeClient.Pods(api.NamespaceDefault).Delete(pod.Name, nil)

	sa, err := clusterAdminKubeClient.ServiceAccounts(api.NamespaceDefault).Get(bootstrappolicy.DeployerServiceAccountName)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if sa.Annotations == nil {
		sa.Annotations = map[string]string{}
	}
	sa.Annotations[serviceaccountadmission.EnforceMountableSecretsAnnotation] = "true"

	time.Sleep(5)

	_, err = clusterAdminKubeClient.ServiceAccounts(api.NamespaceDefault).Update(sa)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expectedMessage := "is not allowed because service account deployer does not reference that secret"
	pod.Spec.ServiceAccountName = bootstrappolicy.DeployerServiceAccountName

	err = wait.Poll(100*time.Millisecond, 5*time.Second, func() (bool, error) {
		if _, err := clusterAdminKubeClient.Pods(api.NamespaceDefault).Create(pod); err == nil || !strings.Contains(err.Error(), expectedMessage) {
			clusterAdminKubeClient.Pods(api.NamespaceDefault).Delete(pod.Name, nil)
			return false, nil
		}

		return true, nil
	})
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

}
func TestServiceAccountAuthorization(t *testing.T) {
	saNamespace := api.NamespaceDefault
	saName := serviceaccountadmission.DefaultServiceAccountName
	saUsername := serviceaccount.MakeUsername(saNamespace, saName)

	// Start one OpenShift master as "cluster1" to play the external kube server
	cluster1MasterConfig, cluster1AdminConfigFile, err := testutil.StartTestMaster()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	cluster1AdminConfig, err := testutil.GetClusterAdminClientConfig(cluster1AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	cluster1AdminKubeClient, err := testutil.GetClusterAdminKubeClient(cluster1AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	cluster1AdminOSClient, err := testutil.GetClusterAdminClient(cluster1AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Get a service account token and build a client
	saToken, err := waitForServiceAccountToken(cluster1AdminKubeClient, saNamespace, saName, 20, time.Second)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(saToken) == 0 {
		t.Fatalf("token was not created")
	}
	cluster1SAClientConfig := kclient.Config{
		Host:        cluster1AdminConfig.Host,
		Prefix:      cluster1AdminConfig.Prefix,
		BearerToken: saToken,
		TLSClientConfig: kclient.TLSClientConfig{
			CAFile: cluster1AdminConfig.CAFile,
			CAData: cluster1AdminConfig.CAData,
		},
	}
	cluster1SAKubeClient, err := kclient.New(&cluster1SAClientConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Make sure the service account doesn't have access
	failNS := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-fail"}}
	if _, err := cluster1SAKubeClient.Namespaces().Create(failNS); !errors.IsForbidden(err) {
		t.Fatalf("expected forbidden error, got %v", err)
	}

	// Make the service account a cluster admin on cluster1
	addRoleOptions := &policy.RoleModificationOptions{
		RoleName:            bootstrappolicy.ClusterAdminRoleName,
		RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(cluster1AdminOSClient),
		Users:               []string{saUsername},
	}
	if err := addRoleOptions.AddRole(); err != nil {
		t.Fatalf("could not add role to service account")
	}

	// Give the policy cache a second to catch it's breath
	time.Sleep(time.Second)

	// Make sure the service account now has access
	// This tests authentication using the etcd-based token getter
	passNS := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-pass"}}
	if _, err := cluster1SAKubeClient.Namespaces().Create(passNS); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Create a kubeconfig from the serviceaccount config
	cluster1SAKubeConfigFile, err := ioutil.TempFile(testutil.GetBaseDir(), "cluster1-service-account.kubeconfig")
	if err != nil {
		t.Fatalf("error creating tmpfile: %v", err)
	}
	defer os.Remove(cluster1SAKubeConfigFile.Name())
	if err := writeClientConfigToKubeConfig(cluster1SAClientConfig, cluster1SAKubeConfigFile.Name()); err != nil {
		t.Fatalf("error creating kubeconfig: %v", err)
	}

	// Set up cluster 2 to run against cluster 1 as external kubernetes
	cluster2MasterConfig, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// Don't start kubernetes in process
	cluster2MasterConfig.KubernetesMasterConfig = nil
	// Connect to cluster1 using the service account credentials
	cluster2MasterConfig.MasterClients.ExternalKubernetesKubeConfig = cluster1SAKubeConfigFile.Name()
	// Don't start etcd
	cluster2MasterConfig.EtcdConfig = nil
	// Use the same credentials as cluster1 to connect to existing etcd
	cluster2MasterConfig.EtcdClientInfo = cluster1MasterConfig.EtcdClientInfo
	// Set a custom etcd prefix to make sure data is getting sent to cluster1
	cluster2MasterConfig.EtcdStorageConfig.KubernetesStoragePrefix += "2"
	cluster2MasterConfig.EtcdStorageConfig.OpenShiftStoragePrefix += "2"
	// Don't manage any names in cluster2
	cluster2MasterConfig.ServiceAccountConfig.ManagedNames = []string{}
	// Don't create any service account tokens in cluster2
	cluster2MasterConfig.ServiceAccountConfig.PrivateKeyFile = ""
	// Use the same public keys to validate tokens as cluster1
	cluster2MasterConfig.ServiceAccountConfig.PublicKeyFiles = cluster1MasterConfig.ServiceAccountConfig.PublicKeyFiles
	// don't try to start second dns server
	cluster2MasterConfig.DNSConfig = nil

	// Start cluster 2 (without clearing etcd) and get admin client configs and clients
	cluster2Options := testutil.TestOptions{DeleteAllEtcdKeys: false}
	cluster2AdminConfigFile, err := testutil.StartConfiguredMasterWithOptions(cluster2MasterConfig, cluster2Options)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	cluster2AdminConfig, err := testutil.GetClusterAdminClientConfig(cluster2AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	cluster2AdminOSClient, err := testutil.GetClusterAdminClient(cluster2AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Build a client to use the same service account token against cluster2
	cluster2SAClientConfig := cluster1SAClientConfig
	cluster2SAClientConfig.Host = cluster2AdminConfig.Host
	cluster2SAKubeClient, err := kclient.New(&cluster2SAClientConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Make sure the service account doesn't have access
	// A forbidden error makes sure the token was recognized, and policy denied us
	// This exercises the client-based token getter
	// It also makes sure we don't loop back through the cluster2 kube proxy which would cause an auth loop
	failNS2 := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-fail2"}}
	if _, err := cluster2SAKubeClient.Namespaces().Create(failNS2); !errors.IsForbidden(err) {
		t.Fatalf("expected forbidden error, got %v", err)
	}

	// Make the service account a cluster admin on cluster2
	addRoleOptions2 := &policy.RoleModificationOptions{
		RoleName:            bootstrappolicy.ClusterAdminRoleName,
		RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(cluster2AdminOSClient),
		Users:               []string{saUsername},
	}
	if err := addRoleOptions2.AddRole(); err != nil {
		t.Fatalf("could not add role to service account")
	}

	// Give the policy cache a second to catch it's breath
	time.Sleep(time.Second)

	// Make sure the service account now has access to cluster2
	passNS2 := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-pass2"}}
	if _, err := cluster2SAKubeClient.Namespaces().Create(passNS2); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Make sure the ns actually got created in cluster1
	if _, err := cluster1SAKubeClient.Namespaces().Get(passNS2.Name); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}
Exemple #9
0
func TestExternalKube(t *testing.T) {
	// Start one OpenShift master as "cluster1" to play the external kube server
	cluster1MasterConfig, cluster1AdminConfigFile, err := testutil.StartTestMaster()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	cluster1AdminKubeClient, err := testutil.GetClusterAdminKubeClient(cluster1AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Copy admin.kubeconfig with a name-change top stop from over-writing it later
	persistentCluster1AdminConfigFile := cluster1AdminConfigFile + "old"
	err = copyFile(cluster1AdminConfigFile, persistentCluster1AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Set up cluster 2 to run against cluster 1 as external kubernetes
	cluster2MasterConfig, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// Don't start kubernetes in process
	cluster2MasterConfig.KubernetesMasterConfig = nil
	// Connect to cluster1 using the service account credentials
	cluster2MasterConfig.MasterClients.ExternalKubernetesKubeConfig = persistentCluster1AdminConfigFile
	// Don't start etcd
	cluster2MasterConfig.EtcdConfig = nil
	// Use the same credentials as cluster1 to connect to existing etcd
	cluster2MasterConfig.EtcdClientInfo = cluster1MasterConfig.EtcdClientInfo
	// Set a custom etcd prefix to make sure data is getting sent to cluster1
	cluster2MasterConfig.EtcdStorageConfig.KubernetesStoragePrefix += "2"
	cluster2MasterConfig.EtcdStorageConfig.OpenShiftStoragePrefix += "2"
	// Don't manage any names in cluster2
	cluster2MasterConfig.ServiceAccountConfig.ManagedNames = []string{}
	// Don't create any service account tokens in cluster2
	cluster2MasterConfig.ServiceAccountConfig.PrivateKeyFile = ""
	// Use the same public keys to validate tokens as cluster1
	cluster2MasterConfig.ServiceAccountConfig.PublicKeyFiles = cluster1MasterConfig.ServiceAccountConfig.PublicKeyFiles
	// Don't run controllers in the second cluster
	cluster2MasterConfig.PauseControllers = true
	// don't try to start second dns server
	cluster2MasterConfig.DNSConfig = nil

	// Start cluster 2 (without clearing etcd) and get admin client configs and clients
	cluster2Options := testutil.TestOptions{DeleteAllEtcdKeys: false}
	cluster2AdminConfigFile, err := testutil.StartConfiguredMasterWithOptions(cluster2MasterConfig, cluster2Options)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	cluster2AdminKubeClient, err := testutil.GetClusterAdminKubeClient(cluster2AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	healthzProxyTest(cluster2MasterConfig, t)

	watchProxyTest(cluster1AdminKubeClient, cluster2AdminKubeClient, t)

}
func TestExternalKube(t *testing.T) {
	// Start one OpenShift master as "cluster1" to play the external kube server
	cluster1MasterConfig, cluster1AdminConfigFile, err := testutil.StartTestMaster()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Copy admin.kubeconfig with a name-change top stop from over-writing it later
	persistentCluster1AdminConfigFile := cluster1AdminConfigFile + "old"
	err = copyFile(cluster1AdminConfigFile, persistentCluster1AdminConfigFile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Set up cluster 2 to run against cluster 1 as external kubernetes
	cluster2MasterConfig, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// Don't start kubernetes in process
	cluster2MasterConfig.KubernetesMasterConfig = nil
	// Connect to cluster1 using the service account credentials
	cluster2MasterConfig.MasterClients.ExternalKubernetesKubeConfig = persistentCluster1AdminConfigFile
	// Don't start etcd
	cluster2MasterConfig.EtcdConfig = nil
	// Use the same credentials as cluster1 to connect to existing etcd
	cluster2MasterConfig.EtcdClientInfo = cluster1MasterConfig.EtcdClientInfo
	// Set a custom etcd prefix to make sure data is getting sent to cluster1
	cluster2MasterConfig.EtcdStorageConfig.KubernetesStoragePrefix += "2"
	cluster2MasterConfig.EtcdStorageConfig.OpenShiftStoragePrefix += "2"
	// Don't manage any names in cluster2
	cluster2MasterConfig.ServiceAccountConfig.ManagedNames = []string{}
	// Don't create any service account tokens in cluster2
	cluster2MasterConfig.ServiceAccountConfig.PrivateKeyFile = ""
	// Use the same public keys to validate tokens as cluster1
	cluster2MasterConfig.ServiceAccountConfig.PublicKeyFiles = cluster1MasterConfig.ServiceAccountConfig.PublicKeyFiles
	// Don't run controllers in the second cluster
	cluster2MasterConfig.PauseControllers = true
	// don't try to start second dns server
	cluster2MasterConfig.DNSConfig = nil

	// Start cluster 2 (without clearing etcd) and get admin client configs and clients
	cluster2Options := testutil.TestOptions{DeleteAllEtcdKeys: false}
	_, err = testutil.StartConfiguredMasterWithOptions(cluster2MasterConfig, cluster2Options)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Ping the healthz endpoint on the second OpenShift cluster
	url, err := url.Parse(cluster2MasterConfig.MasterPublicURL)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	url.Path = "/healthz"
	response, err := doHTTPSProbe(url, 1*time.Second)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// Only valid "healthy" response from server is 200 - OK
	if response.StatusCode != http.StatusOK {
		t.Fatalf("OpenShift reported unhealthy: %v", response)
	}
}
func TestHTPasswd(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 := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{
		Name:            "htpasswd",
		UseAsChallenger: true,
		UseAsLogin:      true,
		Provider: runtime.EmbeddedObject{
			&configapi.HTPasswdPasswordIdentityProvider{
				File: htpasswdFile.Name(),
			},
		},
	}

	clusterAdminKubeConfig, err := testutil.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 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 := testutil.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 := testutil.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{
			&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 := testutil.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 TestExtensions(t *testing.T) {
	// Create a temporary directory.
	tmpDir, err := ioutil.TempDir("", "extensions")
	if err != nil {
		t.Fatalf("Could not create tmp dir for extensions: %v", err)
		return
	}
	defer os.RemoveAll(tmpDir)

	// Create extension files.
	var testData = map[string]string{
		"script1.js":                       script1,
		"script2.js":                       script2,
		"stylesheet1.css":                  stylesheet1,
		"stylesheet2.css":                  stylesheet2,
		"extension1/index.html":            index,
		"extension2/index.html":            index,
		"extension1/files/shakespeare.txt": plaintext,
	}

	for path, content := range testData {
		if err := os.MkdirAll(filepath.Dir(filepath.Join(tmpDir, path)), 0755); err != nil {
			t.Fatalf("Failed creating directory for %s: %v", path, err)
			return
		}
		if err := ioutil.WriteFile(filepath.Join(tmpDir, path), []byte(content), 0755); err != nil {
			t.Fatalf("Failed creating file %s: %v", path, err)
			return
		}
	}

	// Build master config.
	masterOptions, err := testutil.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("Failed creating master configuration: %v", err)
		return
	}
	masterOptions.AssetConfig.ExtensionScripts = []string{
		filepath.Join(tmpDir, "script1.js"),
		filepath.Join(tmpDir, "script2.js"),
	}
	masterOptions.AssetConfig.ExtensionStylesheets = []string{
		filepath.Join(tmpDir, "stylesheet1.css"),
		filepath.Join(tmpDir, "stylesheet2.css"),
	}
	masterOptions.AssetConfig.Extensions = []configapi.AssetExtensionsConfig{
		{
			Name:            "extension1",
			SourceDirectory: filepath.Join(tmpDir, "extension1"),
			HTML5Mode:       true,
		},
		{
			Name:            "extension2",
			SourceDirectory: filepath.Join(tmpDir, "extension2"),
			HTML5Mode:       false,
		},
	}

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

	// Inject the base into index.html to test HTML5Mode
	publicURL, err := url.Parse(masterOptions.AssetConfig.PublicURL)
	if err != nil {
		t.Fatalf("Unexpected error parsing PublicURL %q: %v", masterOptions.AssetConfig.PublicURL, err)
		return
	}
	baseInjected := injectBase(index, "extension1", publicURL)

	// TODO: Add tests for caching.

	testcases := map[string]struct {
		URL              string
		Status           int
		Type             string
		Content          []byte
		RedirectLocation string
	}{
		"extension scripts": {
			URL:     "scripts/extensions.js",
			Status:  http.StatusOK,
			Type:    "text/javascript",
			Content: []byte(script1 + ";\n" + script2 + ";\n"),
		},
		"extension css": {
			URL:     "styles/extensions.css",
			Status:  http.StatusOK,
			Type:    "text/css",
			Content: []byte(stylesheet1 + "\n" + stylesheet2 + "\n"),
		},
		"extension index.html (html5Mode on)": {
			URL:     "extensions/extension1/",
			Status:  http.StatusOK,
			Type:    "text/html",
			Content: baseInjected,
		},
		"extension index.html (html5Mode off)": {
			URL:     "extensions/extension2/",
			Status:  http.StatusOK,
			Type:    "text/html",
			Content: []byte(index),
		},
		"extension no trailing slash (html5Mode on)": {
			URL:              "extensions/extension1",
			Status:           http.StatusMovedPermanently,
			RedirectLocation: "extensions/extension1/",
		},
		"extension missing file (html5Mode on)": {
			URL:     "extensions/extension1/does-not-exist/",
			Status:  http.StatusOK,
			Type:    "text/html",
			Content: baseInjected,
		},
		"extension missing file (html5Mode on, no trailing slash)": {
			URL:     "extensions/extension1/does-not-exist",
			Status:  http.StatusOK,
			Type:    "text/html",
			Content: baseInjected,
		},
		"extension missing file (html5Mode off)": {
			URL:    "extensions/extension2/does-not-exist/",
			Status: http.StatusNotFound,
		},
		"extension missing file (html5Mode off, no trailing slash)": {
			URL:    "extensions/extension2/does-not-exist",
			Status: http.StatusNotFound,
		},
		"extension file in subdir": {
			URL:     "extensions/extension1/files/shakespeare.txt",
			Status:  http.StatusOK,
			Type:    "text/plain",
			Content: []byte(plaintext),
		},
		"extension file from other extension": {
			URL:    "extensions/extension2/files/shakespeare.txt",
			Status: http.StatusNotFound,
		},
	}

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	}

	for k, tc := range testcases {
		testURL := masterOptions.AssetConfig.PublicURL + tc.URL
		req, err := http.NewRequest("GET", testURL, nil)
		if err != nil {
			t.Errorf("%s: Unexpected error creating request for %q: %v", k, testURL, err)
			continue
		}

		resp, err := transport.RoundTrip(req)
		if err != nil {
			t.Errorf("%s: Unexpected error while accessing %q: %v", k, testURL, err)
			continue
		}

		if resp.StatusCode != tc.Status {
			t.Errorf("%s: Expected status %d for %q, got %d", k, tc.Status, testURL, resp.StatusCode)
			continue
		}

		if resp.StatusCode == http.StatusOK {
			actualType := strings.Split(resp.Header.Get("Content-Type"), ";")[0]
			if actualType != tc.Type {
				t.Errorf("%s: Expected type %q for %q, got %s", k, tc.Type, testURL, actualType)
				continue
			}

			defer resp.Body.Close()
			actualContent, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				t.Errorf("%s: Unexpected error while reading body of %q: %v", k, testURL, err)
				continue
			}
			if !bytes.Equal(actualContent, []byte(tc.Content)) {
				t.Errorf("%s: Response body for %q did not match expected, actual:\n%s\nexpected:\n%s", k, testURL, actualContent, tc.Content)
				continue
			}
		}

		if len(tc.RedirectLocation) > 0 {
			actualLocation := resp.Header.Get("Location")
			expectedLocation := publicURL.Path + tc.RedirectLocation
			if actualLocation != expectedLocation {
				t.Errorf("%s: Expected response header Location %q for %q, got %q", k, expectedLocation, testURL, actualLocation)
				continue
			}
		}
	}
}
// 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 := testutil.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,
		Provider: runtime.EmbeddedObject{
			Object: &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 := testutil.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)
	}
}