func TestLogin(t *testing.T) { clientcmd.DefaultCluster = clientcmdapi.Cluster{Server: ""} _, clusterAdminKubeConfig, err := testutil.StartTestMaster() 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) } username := "******" password := "******" project := "the-singularity-is-near" server := clusterAdminClientConfig.Host loginOptions := newLoginOptions(server, username, password, true) if err := loginOptions.GatherInfo(); err != nil { t.Fatalf("Error trying to determine server info: %v", err) } if loginOptions.Username != username { t.Fatalf("Unexpected user after authentication: %#v", loginOptions) } newProjectOptions := &newproject.NewProjectOptions{ Client: clusterAdminClient, ProjectName: project, AdminRole: bootstrappolicy.AdminRoleName, AdminUser: username, } if err := newProjectOptions.Run(false); err != nil { t.Fatalf("unexpected error, a project is required to continue: %v", err) } oClient, _ := client.New(loginOptions.Config) p, err := oClient.Projects().Get(project) if err != nil { t.Errorf("unexpected error: %v", err) } if p.Name != project { t.Fatalf("unexpected project: %#v", p) } // TODO Commented because of incorrectly hitting cache when listing projects. // Should be enabled again when cache eviction is properly fixed. // err = loginOptions.GatherProjectInfo() // if err != nil { // t.Fatalf("unexpected error: %v", err) // } // if loginOptions.Project != project { // t.Fatalf("Expected project %v but got %v", project, loginOptions.Project) // } // configFile, err := ioutil.TempFile("", "openshiftconfig") // if err != nil { // t.Fatalf("unexpected error: %v", err) // } // defer os.Remove(configFile.Name()) // if _, err = loginOptions.SaveConfig(configFile.Name()); err != nil { // t.Fatalf("unexpected error: %v", err) // } userWhoamiOptions := cmd.WhoAmIOptions{UserInterface: oClient.Users(), Out: ioutil.Discard} retrievedUser, err := userWhoamiOptions.WhoAmI() if err != nil { t.Errorf("unexpected error: %v", err) } if retrievedUser.Name != username { t.Errorf("expected %v, got %v", retrievedUser.Name, username) } adminWhoamiOptions := cmd.WhoAmIOptions{UserInterface: clusterAdminClient.Users(), Out: ioutil.Discard} retrievedAdmin, err := adminWhoamiOptions.WhoAmI() if err != nil { t.Errorf("unexpected error: %v", err) } if retrievedAdmin.Name != "system:admin" { t.Errorf("expected %v, got %v", retrievedAdmin.Name, "system:admin") } }
// 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) } }