From cb60a44f8af205dd760deb14f67cf67243637c54 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Fri, 5 Nov 2021 14:18:54 -0700 Subject: [PATCH] extract ldap refresh search into helper function also added an integration test for refresh failing after updating the username attribute --- internal/upstreamldap/upstreamldap.go | 42 +++--- internal/upstreamldap/upstreamldap_test.go | 4 +- test/integration/supervisor_login_test.go | 162 ++++++++++++++++----- 3 files changed, 155 insertions(+), 53 deletions(-) diff --git a/internal/upstreamldap/upstreamldap.go b/internal/upstreamldap/upstreamldap.go index ec97baca..1f0587de 100644 --- a/internal/upstreamldap/upstreamldap.go +++ b/internal/upstreamldap/upstreamldap.go @@ -172,26 +172,10 @@ func (p *Provider) GetConfig() ProviderConfig { func (p *Provider) PerformRefresh(ctx context.Context, userDN, expectedUsername, expectedSubject string) error { t := trace.FromContext(ctx).Nest("slow ldap refresh attempt", trace.Field{Key: "providerName", Value: p.GetName()}) defer t.LogIfLong(500 * time.Millisecond) // to help users debug slow LDAP searches - search := p.refreshUserSearchRequest(userDN) - - conn, err := p.dial(ctx) + searchResult, err := p.performRefresh(ctx, userDN) if err != nil { p.traceRefreshFailure(t, err) - return fmt.Errorf(`error dialing host "%s": %w`, p.c.Host, err) - } - defer conn.Close() - - err = conn.Bind(p.c.BindUsername, p.c.BindPassword) - if err != nil { - p.traceRefreshFailure(t, err) - return fmt.Errorf(`error binding as "%s" before user search: %w`, p.c.BindUsername, err) - } - - searchResult, err := conn.Search(search) - - if err != nil { - p.traceRefreshFailure(t, err) - return fmt.Errorf(`error searching for user "%s": %w`, userDN, err) + return err } // if any more or less than one entry, error. @@ -230,6 +214,28 @@ func (p *Provider) PerformRefresh(ctx context.Context, userDN, expectedUsername, return nil } +func (p *Provider) performRefresh(ctx context.Context, userDN string) (*ldap.SearchResult, error) { + search := p.refreshUserSearchRequest(userDN) + + conn, err := p.dial(ctx) + if err != nil { + return nil, fmt.Errorf(`error dialing host "%s": %w`, p.c.Host, err) + } + defer conn.Close() + + err = conn.Bind(p.c.BindUsername, p.c.BindPassword) + if err != nil { + return nil, fmt.Errorf(`error binding as "%s" before user search: %w`, p.c.BindUsername, err) + } + + searchResult, err := conn.Search(search) + + if err != nil { + return nil, fmt.Errorf(`error searching for user "%s": %w`, userDN, err) + } + return searchResult, nil +} + func (p *Provider) dial(ctx context.Context) (Conn, error) { tlsAddr, err := endpointaddr.Parse(p.c.Host, defaultLDAPSPort) if err != nil { diff --git a/internal/upstreamldap/upstreamldap_test.go b/internal/upstreamldap/upstreamldap_test.go index d04cc3dd..6a9eca34 100644 --- a/internal/upstreamldap/upstreamldap_test.go +++ b/internal/upstreamldap/upstreamldap_test.go @@ -1511,8 +1511,8 @@ func TestUpstreamRefresh(t *testing.T) { }, } - for _, test := range tests { - tt := test + for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) t.Cleanup(ctrl.Finish) diff --git a/test/integration/supervisor_login_test.go b/test/integration/supervisor_login_test.go index 46420c14..43b45e78 100644 --- a/test/integration/supervisor_login_test.go +++ b/test/integration/supervisor_login_test.go @@ -44,7 +44,7 @@ func TestSupervisorLogin(t *testing.T) { tests := []struct { name string maybeSkip func(t *testing.T) - createIDP func(t *testing.T) + createIDP func(t *testing.T) string requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client) wantDownstreamIDTokenSubjectToMatch string wantDownstreamIDTokenUsernameToMatch string @@ -55,16 +55,16 @@ func TestSupervisorLogin(t *testing.T) { // We don't necessarily have any way to revoke the user's session on the upstream provider, // so to cause the upstream refresh to fail we can cheat by manipulating the user's session // data in such a way that it should cause the next upstream refresh attempt to fail. - breakRefreshSessionData func(t *testing.T, sessionData *psession.PinnipedSession) + breakRefreshSessionData func(t *testing.T, sessionData *psession.PinnipedSession, idpName string) }{ { name: "oidc with default username and groups claim settings", maybeSkip: func(t *testing.T) { // never need to skip this test }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() - testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ + oidcIDP := testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ Issuer: env.SupervisorUpstreamOIDC.Issuer, TLS: &idpv1alpha1.TLSSpec{ CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), @@ -73,9 +73,10 @@ func TestSupervisorLogin(t *testing.T) { SecretName: testlib.CreateClientCredsSecret(t, env.SupervisorUpstreamOIDC.ClientID, env.SupervisorUpstreamOIDC.ClientSecret).Name, }, }, idpv1alpha1.PhaseReady) + return oidcIDP.Name }, requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlow, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken) @@ -91,9 +92,9 @@ func TestSupervisorLogin(t *testing.T) { maybeSkip: func(t *testing.T) { // never need to skip this test }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() - testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ + oidcIDP := testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ Issuer: env.SupervisorUpstreamOIDC.Issuer, TLS: &idpv1alpha1.TLSSpec{ CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), @@ -109,9 +110,10 @@ func TestSupervisorLogin(t *testing.T) { AdditionalScopes: env.SupervisorUpstreamOIDC.AdditionalScopes, }, }, idpv1alpha1.PhaseReady) + return oidcIDP.Name }, requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlow, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken) @@ -126,9 +128,9 @@ func TestSupervisorLogin(t *testing.T) { maybeSkip: func(t *testing.T) { // never need to skip this test }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() - testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ + oidcIDP := testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ Issuer: env.SupervisorUpstreamOIDC.Issuer, TLS: &idpv1alpha1.TLSSpec{ CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), @@ -140,6 +142,7 @@ func TestSupervisorLogin(t *testing.T) { AllowPasswordGrant: true, // allow the CLI password flow for this OIDCIdentityProvider }, }, idpv1alpha1.PhaseReady) + return oidcIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -150,7 +153,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken) @@ -169,7 +172,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("LDAP integration test requires connectivity to an LDAP server") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, map[string]string{ @@ -207,6 +210,7 @@ func TestSupervisorLogin(t *testing.T) { secret.Name, secret.ResourceVersion, ) requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg) + return ldapIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -217,7 +221,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.LDAP.UserDN) @@ -242,7 +246,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("LDAP integration test requires connectivity to an LDAP server") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, map[string]string{ @@ -280,6 +284,7 @@ func TestSupervisorLogin(t *testing.T) { secret.Name, secret.ResourceVersion, ) requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg) + return ldapIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -290,7 +295,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.LDAP.UserDN) @@ -315,7 +320,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("LDAP integration test requires connectivity to an LDAP server") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, map[string]string{ @@ -353,6 +358,7 @@ func TestSupervisorLogin(t *testing.T) { secret.Name, secret.ResourceVersion, ) requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg) + return ldapIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -374,7 +380,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("LDAP integration test requires connectivity to an LDAP server") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, @@ -430,6 +436,7 @@ func TestSupervisorLogin(t *testing.T) { requireEventually.NoError(err) requireEventuallySuccessfulLDAPIdentityProviderConditions(t, requireEventually, ldapIDP, expectedMsg) }, time.Minute, 500*time.Millisecond) + return ldapIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -440,7 +447,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.LDAP.UserDN) @@ -464,7 +471,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("LDAP integration test requires connectivity to an LDAP server") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, @@ -534,6 +541,7 @@ func TestSupervisorLogin(t *testing.T) { requireEventually.NoError(err) requireEventuallySuccessfulLDAPIdentityProviderConditions(t, requireEventually, ldapIDP, expectedMsg) }, time.Minute, 500*time.Millisecond) + return ldapIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -544,7 +552,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.LDAP.UserDN) @@ -571,7 +579,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("Active Directory hostname not specified") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, map[string]string{ @@ -594,6 +602,7 @@ func TestSupervisorLogin(t *testing.T) { secret.Name, secret.ResourceVersion, ) requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg) + return adIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -604,7 +613,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) @@ -631,7 +640,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("Active Directory hostname not specified") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, map[string]string{ @@ -668,6 +677,7 @@ func TestSupervisorLogin(t *testing.T) { secret.Name, secret.ResourceVersion, ) requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg) + return adIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -678,7 +688,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) @@ -706,7 +716,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("Active Directory hostname not specified") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, @@ -747,6 +757,7 @@ func TestSupervisorLogin(t *testing.T) { requireEventually.NoError(err) requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t, requireEventually, adIDP, expectedMsg) }, time.Minute, 500*time.Millisecond) + return adIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -757,7 +768,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) @@ -784,7 +795,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("Active Directory hostname not specified") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, @@ -840,6 +851,7 @@ func TestSupervisorLogin(t *testing.T) { requireEventually.NoError(err) requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t, requireEventually, adIDP, expectedMsg) }, time.Minute, 500*time.Millisecond) + return adIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -850,7 +862,7 @@ func TestSupervisorLogin(t *testing.T) { false, ) }, - breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) { customSessionData := pinnipedSession.Custom require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) @@ -877,7 +889,7 @@ func TestSupervisorLogin(t *testing.T) { t.Skip("Active Directory hostname not specified") } }, - createIDP: func(t *testing.T) { + createIDP: func(t *testing.T) string { t.Helper() secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, map[string]string{ @@ -900,6 +912,7 @@ func TestSupervisorLogin(t *testing.T) { secret.Name, secret.ResourceVersion, ) requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg) + return adIDP.Name }, requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorizationUsingCLIPasswordFlow(t, @@ -914,6 +927,89 @@ func TestSupervisorLogin(t *testing.T) { wantErrorDescription: "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider.", wantErrorType: "access_denied", }, + { + name: "ldap refresh fails when username changes from email as username to dn as username", + maybeSkip: func(t *testing.T) { + t.Helper() + if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) { + t.Skip("LDAP integration test requires connectivity to an LDAP server") + } + }, + createIDP: func(t *testing.T) string { + t.Helper() + secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, + map[string]string{ + v1.BasicAuthUsernameKey: env.SupervisorUpstreamLDAP.BindUsername, + v1.BasicAuthPasswordKey: env.SupervisorUpstreamLDAP.BindPassword, + }, + ) + ldapIDP := testlib.CreateTestLDAPIdentityProvider(t, idpv1alpha1.LDAPIdentityProviderSpec{ + Host: env.SupervisorUpstreamLDAP.Host, + TLS: &idpv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.CABundle)), + }, + Bind: idpv1alpha1.LDAPIdentityProviderBind{ + SecretName: secret.Name, + }, + UserSearch: idpv1alpha1.LDAPIdentityProviderUserSearch{ + Base: env.SupervisorUpstreamLDAP.UserSearchBase, + Filter: "", + Attributes: idpv1alpha1.LDAPIdentityProviderUserSearchAttributes{ + Username: env.SupervisorUpstreamLDAP.TestUserMailAttributeName, + UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName, + }, + }, + GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{ + Base: env.SupervisorUpstreamLDAP.GroupSearchBase, + Filter: "", + Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{ + GroupName: "dn", + }, + }, + }, idpv1alpha1.LDAPPhaseReady) + expectedMsg := fmt.Sprintf( + `successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`, + env.SupervisorUpstreamLDAP.Host, env.SupervisorUpstreamLDAP.BindUsername, + secret.Name, secret.ResourceVersion, + ) + requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg) + return ldapIDP.Name + }, + requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { + requestAuthorizationUsingCLIPasswordFlow(t, + downstreamAuthorizeURL, + env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login + env.SupervisorUpstreamLDAP.TestUserPassword, // password to present to server during login + httpClient, + false, + ) + }, + breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, idpName string) { + // get the idp, update the config. + client := testlib.NewSupervisorClientset(t) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + // Create the LDAPIdentityProvider using GenerateName to get a random name. + upstreams := client.IDPV1alpha1().LDAPIdentityProviders(env.SupervisorNamespace) + ldapIDP, err := upstreams.Get(ctx, idpName, metav1.GetOptions{}) + require.NoError(t, err) + ldapIDP.Spec.UserSearch.Attributes.Username = "dn" + + _, err = upstreams.Update(ctx, ldapIDP, metav1.UpdateOptions{}) + require.NoError(t, err) + time.Sleep(10 * time.Second) // wait for controllers to pick up the change + }, + // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute + wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( + "ldaps://"+env.SupervisorUpstreamLDAP.Host+ + "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ + "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), + ) + "$", + // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute + wantDownstreamIDTokenUsernameToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$", + wantDownstreamIDTokenGroups: env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs, + }, } for _, test := range tests { tt := test @@ -1053,9 +1149,9 @@ func requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t *tes func testSupervisorLogin( t *testing.T, - createIDP func(t *testing.T), + createIDP func(t *testing.T) string, requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client), - breakRefreshSessionData func(t *testing.T, pinnipedSession *psession.PinnipedSession), + breakRefreshSessionData func(t *testing.T, pinnipedSession *psession.PinnipedSession, idpName string), wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string, wantErrorDescription string, wantErrorType string, ) { @@ -1143,7 +1239,7 @@ func testSupervisorLogin( }, 30*time.Second, 200*time.Millisecond) // Create upstream IDP and wait for it to become ready. - createIDP(t) + idpName := createIDP(t) // Perform OIDC discovery for our downstream. var discovery *coreosoidc.Provider @@ -1237,7 +1333,7 @@ func testSupervisorLogin( // Next mutate the part of the session that is used during upstream refresh. pinnipedSession, ok := storedRefreshSession.GetSession().(*psession.PinnipedSession) require.True(t, ok, "should have been able to cast session data to PinnipedSession") - breakRefreshSessionData(t, pinnipedSession) + breakRefreshSessionData(t, pinnipedSession, idpName) // Then save the mutated Secret back to Kubernetes. // There is no update function, so delete and create again at the same name.