func TestAccessOriginWebConsole(t *testing.T) { testutil.RequireEtcd(t) masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } if _, err = testserver.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 setupRunOnceDurationTest(t *testing.T, pluginConfig *pluginapi.RunOnceDurationConfig, nsAnnotations map[string]string) kclient.Interface { masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } plugins := append([]string{"RunOnceDuration"}, kubemaster.AdmissionPlugins...) masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginOrderOverride = plugins masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "RunOnceDuration": { Configuration: runtime.EmbeddedObject{ Object: pluginConfig, }, }, } kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClient, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } ns := &kapi.Namespace{} ns.Name = testutil.Namespace() ns.Annotations = nsAnnotations _, err = kubeClient.Namespaces().Create(ns) if err != nil { t.Fatalf("error creating namespace: %v", err) } if err := testserver.WaitForServiceAccounts(kubeClient, testutil.Namespace(), []string{bootstrappolicy.DefaultServiceAccountName}); err != nil { t.Errorf("unexpected error: %v", err) } return kubeClient }
func setupProjectRequestLimitTest(t *testing.T, pluginConfig *requestlimit.ProjectRequestLimitConfig) (kclientset.Interface, client.Interface, *restclient.Config) { testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } masterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "ProjectRequestLimit": { Configuration: pluginConfig, }, } kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClient, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } openshiftClient, err := testutil.GetClusterAdminClient(kubeConfigFile) if err != nil { t.Fatalf("error getting openshift client: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(kubeConfigFile) if err != nil { t.Fatalf("error getting client config: %v", err) } return kubeClient, openshiftClient, clientConfig }
func setupRunOnceDurationTest(t *testing.T, pluginConfig *pluginapi.RunOnceDurationConfig, nsAnnotations map[string]string) kclient.Interface { testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "RunOnceDuration": { Configuration: pluginConfig, }, } kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClient, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } ns := &kapi.Namespace{} ns.Name = testutil.Namespace() ns.Annotations = nsAnnotations _, err = kubeClient.Namespaces().Create(ns) if err != nil { t.Fatalf("error creating namespace: %v", err) } if err := testserver.WaitForPodCreationServiceAccounts(kubeClient, testutil.Namespace()); err != nil { t.Errorf("unexpected error: %v", err) } return kubeClient }
func setupProjectRequestLimitTest(t *testing.T, pluginConfig *requestlimit.ProjectRequestLimitConfig) (kclient.Interface, client.Interface, *kclient.Config) { masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } masterConfig.AdmissionConfig.PluginOrderOverride = []string{"OriginNamespaceLifecycle", "BuildByStrategy", "ProjectRequestLimit"} masterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "ProjectRequestLimit": { Configuration: runtime.EmbeddedObject{ Object: pluginConfig, }, }, } kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClient, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } openshiftClient, err := testutil.GetClusterAdminClient(kubeConfigFile) if err != nil { t.Fatalf("error getting openshift client: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(kubeConfigFile) if err != nil { t.Fatalf("error getting client config: %v", err) } return kubeClient, openshiftClient, clientConfig }
func setupClusterResourceOverrideTest(t *testing.T, pluginConfig *overrideapi.ClusterResourceOverrideConfig) kclient.Interface { masterConfig, err := testserver.DefaultMasterOptions() checkErr(t, err) // fill in possibly-empty config values if masterConfig.KubernetesMasterConfig == nil { masterConfig.KubernetesMasterConfig = &api.KubernetesMasterConfig{} } kubeMaster := masterConfig.KubernetesMasterConfig if kubeMaster.AdmissionConfig.PluginConfig == nil { kubeMaster.AdmissionConfig.PluginConfig = map[string]api.AdmissionPluginConfig{} } // set our config as desired kubeMaster.AdmissionConfig.PluginConfig[overrideapi.PluginName] = api.AdmissionPluginConfig{Configuration: pluginConfig} // start up a server and return useful clients to that server clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterConfig) checkErr(t, err) clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig) checkErr(t, err) clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) checkErr(t, err) // need to create a project and return client for project admin clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) checkErr(t, err) _, err = testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, testutil.Namespace(), "peon") checkErr(t, err) checkErr(t, testserver.WaitForServiceAccounts(clusterAdminKubeClient, testutil.Namespace(), []string{bootstrappolicy.DefaultServiceAccountName})) return clusterAdminKubeClient }
func setupUserPodNodeConstraintsTest(t *testing.T, pluginConfig *pluginapi.PodNodeConstraintsConfig, user string) (*client.Client, *kclientset.Clientset) { testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } cfg := map[string]configapi.AdmissionPluginConfig{ "PodNodeConstraints": { Configuration: pluginConfig, }, } masterConfig.AdmissionConfig.PluginConfig = cfg masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = cfg kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(kubeConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(kubeConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } userClient, userkubeClientset, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, user) if err != nil { t.Fatalf("error getting user/kube client: %v", err) } kubeClientset, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting kube client: %v", err) } ns := &kapi.Namespace{} ns.Name = testutil.Namespace() _, err = kubeClientset.Core().Namespaces().Create(ns) if err != nil { t.Fatalf("error creating namespace: %v", err) } if err := testserver.WaitForServiceAccounts(kubeClientset, testutil.Namespace(), []string{bootstrappolicy.DefaultServiceAccountName}); err != nil { t.Fatalf("unexpected error: %v", err) } addUser := &policy.RoleModificationOptions{ RoleNamespace: ns.Name, RoleName: bootstrappolicy.AdminRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient), Users: []string{user}, } if err := addUser.AddRole(); err != nil { t.Fatalf("unexpected error: %v", err) } return userClient, userkubeClientset }
func TestAlwaysPullImagesOn(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "AlwaysPullImages": { Configuration: &configapi.DefaultAdmissionConfig{}, }, } kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClientset, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } ns := &kapi.Namespace{} ns.Name = testutil.Namespace() _, err = kubeClientset.Core().Namespaces().Create(ns) if err != nil { t.Fatalf("error creating namespace: %v", err) } if err := testserver.WaitForPodCreationServiceAccounts(kubeClientset, testutil.Namespace()); err != nil { t.Fatalf("error getting client config: %v", err) } testPod := &kapi.Pod{} testPod.GenerateName = "test" testPod.Spec.Containers = []kapi.Container{ { Name: "container", Image: "openshift/origin-pod:notlatest", ImagePullPolicy: kapi.PullNever, }, } actualPod, err := kubeClientset.Core().Pods(testutil.Namespace()).Create(testPod) if err != nil { t.Fatalf("unexpected error: %v", err) } if actualPod.Spec.Containers[0].ImagePullPolicy != kapi.PullAlways { t.Errorf("expected %v, got %v", kapi.PullAlways, actualPod.Spec.Containers[0].ImagePullPolicy) } }
func TestOAuthDisabled(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(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 := restclient.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 setupBuildPodAdmissionTest(t *testing.T, pluginConfig map[string]configapi.AdmissionPluginConfig) (*client.Client, *kclientset.Clientset) { testutil.RequireEtcd(t) master, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("%v", err) } master.AdmissionConfig.PluginConfig = pluginConfig clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(master) if err != nil { t.Fatalf("%v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("%v", err) } clusterAdminKubeClientset, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("%v", err) } _, err = clusterAdminKubeClientset.Namespaces().Create(&kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{Name: testutil.Namespace()}, }) if err != nil { t.Fatalf("%v", err) } err = testserver.WaitForServiceAccounts( clusterAdminKubeClientset, testutil.Namespace(), []string{ bootstrappolicy.BuilderServiceAccountName, bootstrappolicy.DefaultServiceAccountName, }) if err != nil { t.Fatalf("%v", err) } return clusterAdminClient, clusterAdminKubeClientset }
func TestAccessDisabledWebConsole(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } masterOptions.DisabledFeatures.Add(configapi.FeatureWebConsole) if _, err := testserver.StartConfiguredMaster(masterOptions); err != nil { t.Fatalf("unexpected error: %v", err) } resp := tryAccessURL(t, masterOptions.AssetConfig.MasterPublicURL+"/", http.StatusOK, "", nil) 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, nil) } }
func TestAccessStandaloneOriginWebConsole(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } addr, err := testserver.FindAvailableBindAddress(13000, 13999) if err != nil { t.Fatalf("unexpected error: %v", err) } masterOptions.AssetConfig.ServingInfo.BindAddress = addr assetBaseURL := "https://" + addr masterOptions.AssetConfig.PublicURL = assetBaseURL + "/console/" masterOptions.OAuthConfig.AssetPublicURL = assetBaseURL + "/console/" if _, err = testserver.StartConfiguredMaster(masterOptions); err != nil { t.Fatalf("unexpected error: %v", err) } for endpoint, exp := range map[string]struct { statusCode int location string }{ "": {http.StatusFound, "/console/"}, "blarg": {http.StatusFound, "/console/"}, "console": {http.StatusMovedPermanently, "/console/"}, "console/": {http.StatusOK, ""}, "console/java": {http.StatusOK, ""}, } { url := assetBaseURL + "/" + endpoint tryAccessURL(t, url, exp.statusCode, exp.location, nil) } }
func setupClusterAdminPodNodeConstraintsTest(t *testing.T, pluginConfig *pluginapi.PodNodeConstraintsConfig) (*client.Client, *kclientset.Clientset) { testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } cfg := map[string]configapi.AdmissionPluginConfig{ "PodNodeConstraints": { Configuration: pluginConfig, }, } masterConfig.AdmissionConfig.PluginConfig = cfg masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = cfg kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClientset, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } openShiftClient, err := testutil.GetClusterAdminClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } ns := &kapi.Namespace{} ns.Name = testutil.Namespace() _, err = kubeClientset.Core().Namespaces().Create(ns) if err != nil { t.Fatalf("error creating namespace: %v", err) } if err := testserver.WaitForPodCreationServiceAccounts(kubeClientset, testutil.Namespace()); err != nil { t.Fatalf("unexpected error: %v", err) } return openShiftClient, kubeClientset }
func runStorageTest(t *testing.T, ns string, autoscalingVersion, batchVersion, extensionsVersion unversioned.GroupVersion) { etcdServer := testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } keys := etcd.NewKeysAPI(etcdServer.Client) getGVKFromEtcd := func(prefix, name string) (*unversioned.GroupVersionKind, error) { key := path.Join(masterConfig.EtcdStorageConfig.KubernetesStoragePrefix, prefix, ns, name) resp, err := keys.Get(context.TODO(), key, nil) if err != nil { return nil, err } _, gvk, err := runtime.UnstructuredJSONScheme.Decode([]byte(resp.Node.Value), nil, nil) return gvk, err } // TODO: Set storage versions for API groups // masterConfig.EtcdStorageConfig.StorageVersions[autoscaling.GroupName] = autoscalingVersion.String() // masterConfig.EtcdStorageConfig.StorageVersions[batch.GroupName] = batchVersion.String() // masterConfig.EtcdStorageConfig.StorageVersions[extensions.GroupName] = extensionsVersion.String() clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // create the containing project if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, ns, "admin"); err != nil { t.Fatalf("unexpected error creating the project: %v", err) } projectAdminClient, projectAdminKubeClient, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "admin") if err != nil { t.Fatalf("unexpected error getting project admin client: %v", err) } if err := testutil.WaitForPolicyUpdate(projectAdminClient, ns, "get", extensions.Resource("horizontalpodautoscalers"), true); err != nil { t.Fatalf("unexpected error waiting for policy update: %v", err) } jobTestcases := map[string]struct { creator kclient.JobInterface }{ "batch": {creator: projectAdminKubeClient.Batch().Jobs(ns)}, } for name, testcase := range jobTestcases { job := batch.Job{ ObjectMeta: kapi.ObjectMeta{Name: name + "-job"}, Spec: batch.JobSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ RestartPolicy: kapi.RestartPolicyNever, Containers: []kapi.Container{{Name: "containername", Image: "containerimage"}}, }, }, }, } // Create a Job if _, err := testcase.creator.Create(&job); err != nil { t.Fatalf("%s: unexpected error creating Job: %v", name, err) } // Ensure it is persisted correctly if gvk, err := getGVKFromEtcd("jobs", job.Name); err != nil { t.Fatalf("%s: unexpected error reading Job: %v", name, err) } else if *gvk != batchVersion.WithKind("Job") { t.Fatalf("%s: expected api version %s in etcd, got %s reading Job", name, batchVersion, gvk) } // Ensure it is accessible from both APIs if _, err := projectAdminKubeClient.Batch().Jobs(ns).Get(job.Name); err != nil { t.Errorf("%s: Error reading Job from the batch client: %#v", name, err) } if _, err := projectAdminKubeClient.Extensions().Jobs(ns).Get(job.Name); err != nil { t.Errorf("%s: Error reading Job from the extensions client: %#v", name, err) } } hpaTestcases := map[string]struct { creator kclient.HorizontalPodAutoscalerInterface }{ "autoscaling": {creator: projectAdminKubeClient.Autoscaling().HorizontalPodAutoscalers(ns)}, "extensions": {creator: projectAdminKubeClient.Extensions().HorizontalPodAutoscalers(ns)}, } for name, testcase := range hpaTestcases { hpa := extensions.HorizontalPodAutoscaler{ ObjectMeta: kapi.ObjectMeta{Name: name + "-hpa"}, Spec: extensions.HorizontalPodAutoscalerSpec{ MaxReplicas: 1, ScaleRef: extensions.SubresourceReference{Kind: "ReplicationController", Name: "myrc", Subresource: "scale"}, }, } // Create an HPA if _, err := testcase.creator.Create(&hpa); err != nil { t.Fatalf("%s: unexpected error creating HPA: %v", name, err) } // Make sure it is persisted correctly if gvk, err := getGVKFromEtcd("horizontalpodautoscalers", hpa.Name); err != nil { t.Fatalf("%s: unexpected error reading HPA: %v", name, err) } else if *gvk != autoscalingVersion.WithKind("HorizontalPodAutoscaler") { t.Fatalf("%s: expected api version %s in etcd, got %s reading HPA", name, autoscalingVersion, gvk) } // Make sure it is available from both APIs if _, err := projectAdminKubeClient.Autoscaling().HorizontalPodAutoscalers(ns).Get(hpa.Name); err != nil { t.Errorf("%s: Error reading HPA.autoscaling from the autoscaling/v1 API: %#v", name, err) } if _, err := projectAdminKubeClient.Extensions().HorizontalPodAutoscalers(ns).Get(hpa.Name); err != nil { t.Errorf("%s: Error reading HPA.extensions from the extensions/v1beta1 API: %#v", name, err) } } }
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 := 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, Provider: runtime.EmbeddedObject{ Object: &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 TestOAuthCertFallback(t *testing.T) { var ( invalidToken = "invalid" noToken = "" invalidCert = restclient.TLSClientConfig{ // We have to generate this dynamically in order to have an invalid cert signed by a signer with the same name as the valid CA // CertData: ..., // KeyData: ..., } noCert = restclient.TLSClientConfig{} tokenUser = "******" certUser = "******" unauthorizedError = "the server has asked for the client to provide credentials (get users ~)" anonymousError = `User "system:anonymous" cannot get users at the cluster scope` ) testutil.RequireEtcd(t) // Build master config masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Start server clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterOptions) if err != nil { t.Fatalf("unexpected error: %v", err) } adminConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } validCert := adminConfig.TLSClientConfig validToken, err := tokencmd.RequestToken(adminConfig, nil, tokenUser, "pass") if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(validToken) == 0 { t.Fatalf("Expected valid token, got none") } // make a client cert signed by a fake CA with the same name as the real CA. // this is needed to get the go client to actually send the cert to the server, // since the server advertises the signer name it requires fakecadir, err := ioutil.TempDir("", "fakeca") if err != nil { t.Fatalf("Unexpected error: %v", err) } defer os.RemoveAll(fakecadir) cacerts, err := util.CertificatesFromFile(masterOptions.ServingInfo.ClientCA) if err != nil || len(cacerts) != 1 { t.Fatalf("Unexpected error or number of certs: %v, %d", err, len(cacerts)) } fakeca, err := (&admin.CreateSignerCertOptions{ CertFile: path.Join(fakecadir, "fakeca.crt"), KeyFile: path.Join(fakecadir, "fakeca.key"), SerialFile: path.Join(fakecadir, "fakeca.serial"), Name: cacerts[0].Subject.CommonName, Output: ioutil.Discard, Overwrite: true, }).CreateSignerCert() if err != nil { t.Fatalf("Unexpected error: %v", err) } clientCertConfig, err := fakeca.MakeClientCertificate( path.Join(fakecadir, "fakeclient.crt"), path.Join(fakecadir, "fakeclient.key"), &user.DefaultInfo{Name: "fakeuser"}, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } invalidCert.CertData, invalidCert.KeyData, err = clientCertConfig.GetPEMBytes() if err != nil { t.Fatalf("Unexpected error: %v", err) } for k, test := range map[string]struct { token string cert restclient.TLSClientConfig expectedUser string errorExpected bool errorString string }{ "valid token, valid cert": { token: validToken, cert: validCert, expectedUser: tokenUser, }, "valid token, invalid cert": { token: validToken, cert: invalidCert, expectedUser: tokenUser, }, "valid token, no cert": { token: validToken, cert: noCert, expectedUser: tokenUser, }, "invalid token, valid cert": { token: invalidToken, cert: validCert, errorExpected: true, errorString: unauthorizedError, }, "invalid token, invalid cert": { token: invalidToken, cert: invalidCert, errorExpected: true, errorString: unauthorizedError, }, "invalid token, no cert": { token: invalidToken, cert: noCert, errorExpected: true, errorString: unauthorizedError, }, "no token, valid cert": { token: noToken, cert: validCert, expectedUser: certUser, }, "no token, invalid cert": { token: noToken, cert: invalidCert, errorExpected: true, errorString: unauthorizedError, }, "no token, no cert": { token: noToken, cert: noCert, errorExpected: true, errorString: anonymousError, }, } { config := *adminConfig config.BearerToken = test.token config.TLSClientConfig = test.cert config.CAData = adminConfig.CAData client := oclient.NewOrDie(&config) user, err := client.Users().Get("~") if user.Name != test.expectedUser { t.Errorf("%s: unexpected user %q", k, user.Name) } if test.errorExpected { if err == nil { t.Errorf("%s: expected error but got nil", k) } else if err.Error() != test.errorString { t.Errorf("%s: unexpected error string %q", k, err.Error()) } } else { if err != nil { t.Errorf("%s: unexpected error %q", k, err.Error()) } } } }
func TestUnprivilegedNewProjectFromTemplate(t *testing.T) { namespace := "foo" templateName := "bar" masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } masterOptions.ProjectConfig.ProjectRequestTemplate = namespace + "/" + templateName clusterAdminKubeConfig, err := testserver.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 := testserver.DefaultMasterOptions() masterConfig.ServiceAccountConfig.LimitSecretReferences = false if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminConfig, err := testserver.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) } }
// 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) } }
func TestAccessOriginWebConsoleMultipleIdentityProviders(t *testing.T) { testutil.RequireEtcd(t) masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Replace the default IdentityProvider with an AllowAll provider masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{ Name: "foo", UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.AllowAllPasswordIdentityProvider{}, } // Set up a second AllowAll provider masterOptions.OAuthConfig.IdentityProviders = append(masterOptions.OAuthConfig.IdentityProviders, configapi.IdentityProvider{ Name: "bar", UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.AllowAllPasswordIdentityProvider{}, }) // Set up a third AllowAll provider with a space in the name and some unicode characters masterOptions.OAuthConfig.IdentityProviders = append(masterOptions.OAuthConfig.IdentityProviders, configapi.IdentityProvider{ Name: "Iñtërnâtiônàlizætiøn, !@#$^&*()", UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.AllowAllPasswordIdentityProvider{}, }) // Launch the configured server if _, err = testserver.StartConfiguredMaster(masterOptions); err != nil { t.Fatalf("unexpected error: %v", err) } // Create a map of URLs to test type urlResults struct { statusCode int location string } urlMap := make(map[string]urlResults) linkRegexps := make([]string, 0) // Verify that the plain /login URI is unavailable when multiple IDPs are in use. urlMap["/login"] = urlResults{http.StatusForbidden, ""} // Create the common base URLs escapedPublicURL := url.QueryEscape(masterOptions.OAuthConfig.AssetPublicURL) loginSelectorBase := "/oauth/authorize?client_id=openshift-web-console&response_type=token&state=%2F&redirect_uri=" + escapedPublicURL // Iterate through each of the providers and verify that they redirect to // the appropriate login page and that the login page exists. // This is done in a loop so that we can add an arbitrary additional set // of providers to test. for _, value := range masterOptions.OAuthConfig.IdentityProviders { // Query-encode the idp=<provider name> parameter name and value idpQueryParam := url.Values{"idp": []string{value.Name}}.Encode() // Construct a URL that will select that IDP providerSelectionURL := loginSelectorBase + "&" + idpQueryParam // URL-path-encode the idp name to construct the login page URL loginURL := (&url.URL{Path: path.Join("/login", value.Name)}).String() // Expect the providerSelectionURL to redirect to the loginURL urlMap[providerSelectionURL] = urlResults{http.StatusFound, loginURL} // Expect the loginURL to be valid urlMap[loginURL] = urlResults{http.StatusOK, ""} // escape the query param the way the template will templateIDPParam := templateEscapeHref(t, idpQueryParam) // quote for the regex regexIDPParam := regexp.QuoteMeta(templateIDPParam) // Expect to see a link to the provider selection page URL with the idp param linkRegexps = append(linkRegexps, fmt.Sprintf(`/oauth/authorize\?(.*&)?%s(&|")`, regexIDPParam)) } // Test the loginSelectorBase for links to all of the IDPs url := masterOptions.AssetConfig.MasterPublicURL + loginSelectorBase tryAccessURL(t, url, http.StatusOK, "", linkRegexps) // Test all of these URLs for endpoint, exp := range urlMap { url := masterOptions.AssetConfig.MasterPublicURL + endpoint tryAccessURL(t, url, exp.statusCode, exp.location, nil) } }
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 } } } }
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) } } }
// TestOAuthOIDC checks CLI password login against an OIDC provider func TestOAuthOIDC(t *testing.T) { tokenCalled := false userinfoCalled := false expectedTokenPost := url.Values{ "grant_type": []string{"password"}, "client_id": []string{"myclient"}, "client_secret": []string{"mysecret"}, "username": []string{"mylogin"}, "password": []string{"mypassword"}, "scope": []string{"openid scope1 scope2"}, } // id_token made at https://jwt.io/ // { // "sub": "mysub", // "name": "John Doe", // "myidclaim": "myid", // "myemailclaim":"myemail", // } tokenResponse := `{ "token_type": "bearer", "access_token": "12345", "id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteXN1YiIsIm5hbWUiOiJKb2huIERvZSIsIm15aWRjbGFpbSI6Im15aWQiLCJteWVtYWlsY2xhaW0iOiJteWVtYWlsIn0.yMx2ZQw8Su641H_kO8ec_tFaysrFEc9uFUFm4ZbLGHw" }` // Additional claims in userInfo (sub claim must match) userinfoResponse := `{ "sub": "mysub", "mynameclaim":"myname", "myusernameclaim":"myusername" }` // Write cert we're going to use to verify OIDC server 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(), oidcLocalhostCert, os.FileMode(0600)); err != nil { t.Fatalf("unexpected error: %v", err) } // Get master config testutil.RequireEtcd(t) masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Set up a dummy OIDC server oidcServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.String() { case "/token": if r.Method != "POST" { t.Fatalf("Expected POST to /token, got %s", r.Method) } if err := r.ParseForm(); err != nil { t.Fatalf("Error parsing form POSTed to /token: %v", err) } if !reflect.DeepEqual(r.PostForm, expectedTokenPost) { t.Fatalf("Expected\n%#v\ngot\n%#v", expectedTokenPost, r.PostForm) } w.Write([]byte(tokenResponse)) tokenCalled = true case "/userinfo": if r.Header.Get("Authorization") != "Bearer 12345" { t.Fatalf("Expected authorization header, got %#v", r.Header) } w.Write([]byte(userinfoResponse)) userinfoCalled = true default: t.Fatalf("Unexpected OIDC request: %v", r.URL.String()) } })) cert, err := tls.X509KeyPair(oidcLocalhostCert, oidcLocalhostKey) oidcServer.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } oidcServer.StartTLS() defer oidcServer.Close() masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{ Name: "oidc", UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.OpenIDIdentityProvider{ CA: caFile.Name(), ClientID: "myclient", ClientSecret: configapi.StringSource{StringSourceSpec: configapi.StringSourceSpec{Value: "mysecret"}}, ExtraScopes: []string{"scope1", "scope2"}, URLs: configapi.OpenIDURLs{ Authorize: oidcServer.URL + "/authorize", Token: oidcServer.URL + "/token", UserInfo: oidcServer.URL + "/userinfo", }, Claims: configapi.OpenIDClaims{ ID: []string{"myidclaim"}, Email: []string{"myemailclaim"}, Name: []string{"mynameclaim"}, PreferredUsername: []string{"myusernameclaim"}, }, }, } // 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) } // Get the master CA data for the login command masterCAFile := clientConfig.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(), clientConfig.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: clientConfig.Host, CAFile: masterCAFile, StartingKubeConfig: &clientcmdapi.Config{}, Reader: bytes.NewBufferString("mylogin\nmypassword\n"), Out: loginOutput, } if err := loginOptions.GatherInfo(); err != nil { t.Fatalf("Error logging in: %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) } // Ex userConfig := &restclient.Config{ Host: clientConfig.Host, TLSClientConfig: restclient.TLSClientConfig{ CAFile: clientConfig.CAFile, CAData: clientConfig.CAData, }, BearerToken: loginOptions.Config.BearerToken, } userClient, err := client.New(userConfig) userWhoamiOptions := cmd.WhoAmIOptions{UserInterface: userClient.Users(), Out: ioutil.Discard} retrievedUser, err := userWhoamiOptions.WhoAmI() if err != nil { t.Errorf("unexpected error: %v", err) } if retrievedUser.Name != "myusername" { t.Errorf("expected username %v, got %v", "myusername", retrievedUser.Name) } if retrievedUser.FullName != "myname" { t.Errorf("expected display name %v, got %v", "myname", retrievedUser.FullName) } if !reflect.DeepEqual([]string{"oidc:myid"}, retrievedUser.Identities) { t.Errorf("expected only oidc:myid identity, got %v", retrievedUser.Identities) } }
func TestEndpointAdmission(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "RestrictedEndpointsAdmission": { Configuration: &configapi.DefaultAdmissionConfig{}, }, } masterConfig.NetworkConfig.ClusterNetworkCIDR = clusterNetworkCIDR masterConfig.NetworkConfig.ServiceNetworkCIDR = serviceNetworkCIDR kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting kube client: %v", err) } clusterAdminOSClient, err := testutil.GetClusterAdminClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(kubeConfigFile) if err != nil { t.Fatalf("error getting client config: %v", err) } // Cluster admin testOne(t, clusterAdminKubeClient, "default", "cluster", true) testOne(t, clusterAdminKubeClient, "default", "service", true) testOne(t, clusterAdminKubeClient, "default", "external", true) // Endpoint controller service account _, serviceAccountClient, _, err := testutil.GetClientForServiceAccount(clusterAdminKubeClient, *clientConfig, bootstrappolicy.DefaultOpenShiftInfraNamespace, bootstrappolicy.InfraEndpointControllerServiceAccountName) if err != nil { t.Fatalf("error getting endpoint controller service account: %v", err) } testOne(t, serviceAccountClient, "default", "cluster", true) testOne(t, serviceAccountClient, "default", "service", true) testOne(t, serviceAccountClient, "default", "external", true) // Project admin _, err = testserver.CreateNewProject(clusterAdminOSClient, *clientConfig, "myproject", "myadmin") if err != nil { t.Fatalf("error creating project: %v", err) } _, projectAdminClient, _, err := testutil.GetClientForUser(*clientConfig, "myadmin") if err != nil { t.Fatalf("error getting project admin client: %v", err) } testOne(t, projectAdminClient, "myproject", "cluster", false) testOne(t, projectAdminClient, "myproject", "service", false) testOne(t, projectAdminClient, "myproject", "external", true) // User without restricted endpoint permission can't modify IPs but can still do other modifications ep := testOne(t, clusterAdminKubeClient, "myproject", "cluster", true) ep.Annotations = map[string]string{"foo": "bar"} ep, err = projectAdminClient.Endpoints("myproject").Update(ep) if err != nil { t.Fatalf("unexpected error updating endpoint annotation: %v", err) } ep.Subsets[0].Addresses[0].IP = exampleAddresses["service"] ep, err = projectAdminClient.Endpoints("myproject").Update(ep) if err == nil { t.Fatalf("unexpected success modifying endpoint") } }
func TestExtensionsAPIDisabled(t *testing.T) { const projName = "ext-disabled-proj" testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Disable all extensions API versions masterConfig.KubernetesMasterConfig.DisabledAPIGroupVersions = map[string][]string{"extensions": {"*"}, "autoscaling": {"*"}, "batch": {"*"}} clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // create the containing project if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projName, "admin"); err != nil { t.Fatalf("unexpected error creating the project: %v", err) } projectAdminClient, projectAdminKubeClient, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "admin") if err != nil { t.Fatalf("unexpected error getting project admin client: %v", err) } if err := testutil.WaitForPolicyUpdate(projectAdminClient, projName, "get", expapi.Resource("horizontalpodautoscalers"), true); err != nil { t.Fatalf("unexpected error waiting for policy update: %v", err) } // make sure extensions API objects cannot be listed or created if _, err := projectAdminKubeClient.Extensions().HorizontalPodAutoscalers(projName).List(kapi.ListOptions{}); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error listing HPA, got %v", err) } if _, err := projectAdminKubeClient.Extensions().HorizontalPodAutoscalers(projName).Create(&expapi.HorizontalPodAutoscaler{}); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error creating HPA, got %v", err) } if _, err := projectAdminKubeClient.Extensions().Jobs(projName).List(kapi.ListOptions{}); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error listing jobs, got %v", err) } if _, err := projectAdminKubeClient.Extensions().Jobs(projName).Create(&batch.Job{}); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error creating job, got %v", err) } // Delete the containing project if err := testutil.DeleteAndWaitForNamespaceTermination(clusterAdminKubeClient, projName); err != nil { t.Fatalf("unexpected error: %#v", err) } }
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 := testserver.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{ Object: &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 TestExtensionsAPIDisabledAutoscaleBatchEnabled(t *testing.T) { const projName = "ext-disabled-batch-enabled-proj" testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Disable all extensions API versions // Leave autoscaling/batch APIs enabled masterConfig.KubernetesMasterConfig.DisabledAPIGroupVersions = map[string][]string{"extensions": {"*"}} clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // create the containing project if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projName, "admin"); err != nil { t.Fatalf("unexpected error creating the project: %v", err) } projectAdminClient, projectAdminKubeClient, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "admin") if err != nil { t.Fatalf("unexpected error getting project admin client: %v", err) } if err := testutil.WaitForPolicyUpdate(projectAdminClient, projName, "get", expapi.Resource("horizontalpodautoscalers"), true); err != nil { t.Fatalf("unexpected error waiting for policy update: %v", err) } validHPA := &expapi.HorizontalPodAutoscaler{ ObjectMeta: kapi.ObjectMeta{Name: "myjob"}, Spec: expapi.HorizontalPodAutoscalerSpec{ ScaleRef: expapi.SubresourceReference{Name: "foo", Kind: "ReplicationController", Subresource: "scale"}, MaxReplicas: 1, }, } validJob := &batch.Job{ ObjectMeta: kapi.ObjectMeta{Name: "myjob"}, Spec: batch.JobSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "mycontainer", Image: "myimage"}}, RestartPolicy: kapi.RestartPolicyNever, }, }, }, } // make sure extensions API objects cannot be listed or created if _, err := projectAdminKubeClient.Extensions().HorizontalPodAutoscalers(projName).List(kapi.ListOptions{}); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error listing HPA, got %v", err) } if _, err := projectAdminKubeClient.Extensions().HorizontalPodAutoscalers(projName).Create(validHPA); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error creating HPA, got %v", err) } if _, err := projectAdminKubeClient.Extensions().Jobs(projName).List(kapi.ListOptions{}); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error listing jobs, got %v", err) } if _, err := projectAdminKubeClient.Extensions().Jobs(projName).Create(validJob); !errors.IsNotFound(err) { t.Fatalf("expected NotFound error creating job, got %v", err) } // make sure autoscaling and batch API objects can be listed and created if _, err := projectAdminKubeClient.Autoscaling().HorizontalPodAutoscalers(projName).List(kapi.ListOptions{}); err != nil { t.Fatalf("unexpected error: %#v", err) } if _, err := projectAdminKubeClient.Autoscaling().HorizontalPodAutoscalers(projName).Create(validHPA); err != nil { t.Fatalf("unexpected error: %#v", err) } if _, err := projectAdminKubeClient.Batch().Jobs(projName).List(kapi.ListOptions{}); err != nil { t.Fatalf("unexpected error: %#v", err) } if _, err := projectAdminKubeClient.Batch().Jobs(projName).Create(validJob); err != nil { t.Fatalf("unexpected error: %#v", err) } // Delete the containing project if err := testutil.DeleteAndWaitForNamespaceTermination(clusterAdminKubeClient, projName); err != nil { t.Fatalf("unexpected error: %#v", err) } // recreate the containing project if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projName, "admin"); err != nil { t.Fatalf("unexpected error creating the project: %v", err) } projectAdminClient, projectAdminKubeClient, _, err = testutil.GetClientForUser(*clusterAdminClientConfig, "admin") if err != nil { t.Fatalf("unexpected error getting project admin client: %v", err) } if err := testutil.WaitForPolicyUpdate(projectAdminClient, projName, "get", expapi.Resource("horizontalpodautoscalers"), true); err != nil { t.Fatalf("unexpected error waiting for policy update: %v", err) } // make sure the created objects got cleaned up by namespace deletion if hpas, err := projectAdminKubeClient.Autoscaling().HorizontalPodAutoscalers(projName).List(kapi.ListOptions{}); err != nil { t.Fatalf("unexpected error: %#v", err) } else if len(hpas.Items) > 0 { t.Fatalf("expected 0 HPA objects, got %#v", hpas.Items) } if jobs, err := projectAdminKubeClient.Batch().Jobs(projName).List(kapi.ListOptions{}); err != nil { t.Fatalf("unexpected error: %#v", err) } else if len(jobs.Items) > 0 { t.Fatalf("expected 0 Job objects, got %#v", jobs.Items) } }
func TestWebConsoleExtensions(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. testutil.RequireEtcd(t) masterOptions, err := testserver.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 = testserver.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 := knet.SetTransportDefaults(&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 } } } }
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 TestOadmPodNetwork(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } masterConfig.NetworkConfig.NetworkPluginName = sdnplugin.MultiTenantPluginName kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } osClient, err := testutil.GetClusterAdminClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(kubeConfigFile) if err != nil { t.Fatalf("error getting client config: %v", err) } origNetns1, err := createProject(osClient, clientConfig, "one") if err != nil { t.Fatalf("could not create namespace %q: %v", "one", err) } origNetns2, err := createProject(osClient, clientConfig, "two") if err != nil { t.Fatalf("could not create namespace %q: %v", "two", err) } origNetns3, err := createProject(osClient, clientConfig, "three") if err != nil { t.Fatalf("could not create namespace %q: %v", "three", err) } if origNetns1.NetID == 0 || origNetns2.NetID == 0 || origNetns3.NetID == 0 { t.Fatalf("expected non-0 NetIDs, got %d, %d, %d", origNetns1.NetID, origNetns2.NetID, origNetns3.NetID) } if origNetns1.NetID == origNetns2.NetID || origNetns1.NetID == origNetns3.NetID || origNetns2.NetID == origNetns3.NetID { t.Fatalf("expected unique NetIDs, got %d, %d, %d", origNetns1.NetID, origNetns2.NetID, origNetns3.NetID) } newNetns2, err := updateNetNamespace(osClient, origNetns2, sdnapi.JoinPodNetwork, "one") if err != nil { t.Fatalf("error updating namespace: %v", err) } if newNetns2.NetID != origNetns1.NetID { t.Fatalf("expected netns2 (%d) to be joined to netns1 (%d)", newNetns2.NetID, origNetns1.NetID) } newNetns1, err := osClient.NetNamespaces().Get("one") if err != nil { t.Fatalf("error getting refetching NetNamespace: %v", err) } if newNetns1.NetID != origNetns1.NetID { t.Fatalf("expected netns1 (%d) to be unchanged (%d)", newNetns1.NetID, origNetns1.NetID) } newNetns1, err = updateNetNamespace(osClient, origNetns1, sdnapi.GlobalPodNetwork, "") if err != nil { t.Fatalf("error updating namespace: %v", err) } if newNetns1.NetID != 0 { t.Fatalf("expected netns1 (%d) to be global", newNetns1.NetID) } newNetns2, err = osClient.NetNamespaces().Get("two") if err != nil { t.Fatalf("error getting refetching NetNamespace: %v", err) } if newNetns2.NetID != origNetns1.NetID { t.Fatalf("expected netns2 (%d) to be unchanged (%d)", newNetns2.NetID, origNetns1.NetID) } newNetns1, err = updateNetNamespace(osClient, newNetns1, sdnapi.IsolatePodNetwork, "") if err != nil { t.Fatalf("error updating namespace: %v", err) } if newNetns1.NetID == 0 { t.Fatalf("expected netns1 (%d) to be non-global", newNetns1.NetID) } if newNetns1.NetID == newNetns2.NetID || newNetns1.NetID == origNetns3.NetID { t.Fatalf("expected netns1 (%d) to be unique (not %d, %d)", newNetns1.NetID, newNetns2.NetID, origNetns3.NetID) } }