extract ldap refresh search into helper function

also added an integration test for refresh failing after updating the username attribute
This commit is contained in:
Margo Crawford 2021-11-05 14:18:54 -07:00
parent b5b8cab717
commit cb60a44f8a
3 changed files with 155 additions and 53 deletions

View File

@ -172,26 +172,10 @@ func (p *Provider) GetConfig() ProviderConfig {
func (p *Provider) PerformRefresh(ctx context.Context, userDN, expectedUsername, expectedSubject string) error { 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()}) 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 defer t.LogIfLong(500 * time.Millisecond) // to help users debug slow LDAP searches
search := p.refreshUserSearchRequest(userDN) searchResult, err := p.performRefresh(ctx, userDN)
conn, err := p.dial(ctx)
if err != nil { if err != nil {
p.traceRefreshFailure(t, err) p.traceRefreshFailure(t, err)
return fmt.Errorf(`error dialing host "%s": %w`, p.c.Host, err) return 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)
} }
// if any more or less than one entry, error. // if any more or less than one entry, error.
@ -230,6 +214,28 @@ func (p *Provider) PerformRefresh(ctx context.Context, userDN, expectedUsername,
return nil 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) { func (p *Provider) dial(ctx context.Context) (Conn, error) {
tlsAddr, err := endpointaddr.Parse(p.c.Host, defaultLDAPSPort) tlsAddr, err := endpointaddr.Parse(p.c.Host, defaultLDAPSPort)
if err != nil { if err != nil {

View File

@ -1511,8 +1511,8 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
} }
for _, test := range tests { for _, tt := range tests {
tt := test tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)

View File

@ -44,7 +44,7 @@ func TestSupervisorLogin(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
maybeSkip func(t *testing.T) 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) requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client)
wantDownstreamIDTokenSubjectToMatch string wantDownstreamIDTokenSubjectToMatch string
wantDownstreamIDTokenUsernameToMatch 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, // 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 // 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. // 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", name: "oidc with default username and groups claim settings",
maybeSkip: func(t *testing.T) { maybeSkip: func(t *testing.T) {
// never need to skip this test // never need to skip this test
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ oidcIDP := testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer, Issuer: env.SupervisorUpstreamOIDC.Issuer,
TLS: &idpv1alpha1.TLSSpec{ TLS: &idpv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), 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, SecretName: testlib.CreateClientCredsSecret(t, env.SupervisorUpstreamOIDC.ClientID, env.SupervisorUpstreamOIDC.ClientSecret).Name,
}, },
}, idpv1alpha1.PhaseReady) }, idpv1alpha1.PhaseReady)
return oidcIDP.Name
}, },
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlow, requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlow,
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken) require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken)
@ -91,9 +92,9 @@ func TestSupervisorLogin(t *testing.T) {
maybeSkip: func(t *testing.T) { maybeSkip: func(t *testing.T) {
// never need to skip this test // never need to skip this test
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ oidcIDP := testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer, Issuer: env.SupervisorUpstreamOIDC.Issuer,
TLS: &idpv1alpha1.TLSSpec{ TLS: &idpv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
@ -109,9 +110,10 @@ func TestSupervisorLogin(t *testing.T) {
AdditionalScopes: env.SupervisorUpstreamOIDC.AdditionalScopes, AdditionalScopes: env.SupervisorUpstreamOIDC.AdditionalScopes,
}, },
}, idpv1alpha1.PhaseReady) }, idpv1alpha1.PhaseReady)
return oidcIDP.Name
}, },
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlow, requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlow,
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken) require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken)
@ -126,9 +128,9 @@ func TestSupervisorLogin(t *testing.T) {
maybeSkip: func(t *testing.T) { maybeSkip: func(t *testing.T) {
// never need to skip this test // never need to skip this test
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{ oidcIDP := testlib.CreateTestOIDCIdentityProvider(t, idpv1alpha1.OIDCIdentityProviderSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer, Issuer: env.SupervisorUpstreamOIDC.Issuer,
TLS: &idpv1alpha1.TLSSpec{ TLS: &idpv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), 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 AllowPasswordGrant: true, // allow the CLI password flow for this OIDCIdentityProvider
}, },
}, idpv1alpha1.PhaseReady) }, idpv1alpha1.PhaseReady)
return oidcIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -150,7 +153,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeOIDC, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken) 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") t.Skip("LDAP integration test requires connectivity to an LDAP server")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
map[string]string{ map[string]string{
@ -207,6 +210,7 @@ func TestSupervisorLogin(t *testing.T) {
secret.Name, secret.ResourceVersion, secret.Name, secret.ResourceVersion,
) )
requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg) requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg)
return ldapIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -217,7 +221,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.LDAP.UserDN) 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") t.Skip("LDAP integration test requires connectivity to an LDAP server")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
map[string]string{ map[string]string{
@ -280,6 +284,7 @@ func TestSupervisorLogin(t *testing.T) {
secret.Name, secret.ResourceVersion, secret.Name, secret.ResourceVersion,
) )
requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg) requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg)
return ldapIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -290,7 +295,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.LDAP.UserDN) 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") t.Skip("LDAP integration test requires connectivity to an LDAP server")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
map[string]string{ map[string]string{
@ -353,6 +358,7 @@ func TestSupervisorLogin(t *testing.T) {
secret.Name, secret.ResourceVersion, secret.Name, secret.ResourceVersion,
) )
requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg) requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg)
return ldapIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -374,7 +380,7 @@ func TestSupervisorLogin(t *testing.T) {
t.Skip("LDAP integration test requires connectivity to an LDAP server") t.Skip("LDAP integration test requires connectivity to an LDAP server")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
@ -430,6 +436,7 @@ func TestSupervisorLogin(t *testing.T) {
requireEventually.NoError(err) requireEventually.NoError(err)
requireEventuallySuccessfulLDAPIdentityProviderConditions(t, requireEventually, ldapIDP, expectedMsg) requireEventuallySuccessfulLDAPIdentityProviderConditions(t, requireEventually, ldapIDP, expectedMsg)
}, time.Minute, 500*time.Millisecond) }, time.Minute, 500*time.Millisecond)
return ldapIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -440,7 +447,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.LDAP.UserDN) 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") t.Skip("LDAP integration test requires connectivity to an LDAP server")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
@ -534,6 +541,7 @@ func TestSupervisorLogin(t *testing.T) {
requireEventually.NoError(err) requireEventually.NoError(err)
requireEventuallySuccessfulLDAPIdentityProviderConditions(t, requireEventually, ldapIDP, expectedMsg) requireEventuallySuccessfulLDAPIdentityProviderConditions(t, requireEventually, ldapIDP, expectedMsg)
}, time.Minute, 500*time.Millisecond) }, time.Minute, 500*time.Millisecond)
return ldapIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -544,7 +552,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeLDAP, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.LDAP.UserDN) require.NotEmpty(t, customSessionData.LDAP.UserDN)
@ -571,7 +579,7 @@ func TestSupervisorLogin(t *testing.T) {
t.Skip("Active Directory hostname not specified") t.Skip("Active Directory hostname not specified")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth,
map[string]string{ map[string]string{
@ -594,6 +602,7 @@ func TestSupervisorLogin(t *testing.T) {
secret.Name, secret.ResourceVersion, secret.Name, secret.ResourceVersion,
) )
requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg) requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg)
return adIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -604,7 +613,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN)
@ -631,7 +640,7 @@ func TestSupervisorLogin(t *testing.T) {
t.Skip("Active Directory hostname not specified") t.Skip("Active Directory hostname not specified")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth,
map[string]string{ map[string]string{
@ -668,6 +677,7 @@ func TestSupervisorLogin(t *testing.T) {
secret.Name, secret.ResourceVersion, secret.Name, secret.ResourceVersion,
) )
requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg) requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg)
return adIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -678,7 +688,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN)
@ -706,7 +716,7 @@ func TestSupervisorLogin(t *testing.T) {
t.Skip("Active Directory hostname not specified") t.Skip("Active Directory hostname not specified")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth,
@ -747,6 +757,7 @@ func TestSupervisorLogin(t *testing.T) {
requireEventually.NoError(err) requireEventually.NoError(err)
requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t, requireEventually, adIDP, expectedMsg) requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t, requireEventually, adIDP, expectedMsg)
}, time.Minute, 500*time.Millisecond) }, time.Minute, 500*time.Millisecond)
return adIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -757,7 +768,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN)
@ -784,7 +795,7 @@ func TestSupervisorLogin(t *testing.T) {
t.Skip("Active Directory hostname not specified") t.Skip("Active Directory hostname not specified")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth,
@ -840,6 +851,7 @@ func TestSupervisorLogin(t *testing.T) {
requireEventually.NoError(err) requireEventually.NoError(err)
requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t, requireEventually, adIDP, expectedMsg) requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t, requireEventually, adIDP, expectedMsg)
}, time.Minute, 500*time.Millisecond) }, time.Minute, 500*time.Millisecond)
return adIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, requestAuthorizationUsingCLIPasswordFlow(t,
@ -850,7 +862,7 @@ func TestSupervisorLogin(t *testing.T) {
false, false,
) )
}, },
breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession) { breakRefreshSessionData: func(t *testing.T, pinnipedSession *psession.PinnipedSession, _ string) {
customSessionData := pinnipedSession.Custom customSessionData := pinnipedSession.Custom
require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType) require.Equal(t, psession.ProviderTypeActiveDirectory, customSessionData.ProviderType)
require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN)
@ -877,7 +889,7 @@ func TestSupervisorLogin(t *testing.T) {
t.Skip("Active Directory hostname not specified") t.Skip("Active Directory hostname not specified")
} }
}, },
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) string {
t.Helper() t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth, secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth,
map[string]string{ map[string]string{
@ -900,6 +912,7 @@ func TestSupervisorLogin(t *testing.T) {
secret.Name, secret.ResourceVersion, secret.Name, secret.ResourceVersion,
) )
requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg) requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg)
return adIDP.Name
}, },
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) { requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingCLIPasswordFlow(t, 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.", wantErrorDescription: "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider.",
wantErrorType: "access_denied", 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 { for _, test := range tests {
tt := test tt := test
@ -1053,9 +1149,9 @@ func requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions(t *tes
func testSupervisorLogin( func testSupervisorLogin(
t *testing.T, 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), 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, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string,
wantErrorDescription string, wantErrorType string, wantErrorDescription string, wantErrorType string,
) { ) {
@ -1143,7 +1239,7 @@ func testSupervisorLogin(
}, 30*time.Second, 200*time.Millisecond) }, 30*time.Second, 200*time.Millisecond)
// Create upstream IDP and wait for it to become ready. // Create upstream IDP and wait for it to become ready.
createIDP(t) idpName := createIDP(t)
// Perform OIDC discovery for our downstream. // Perform OIDC discovery for our downstream.
var discovery *coreosoidc.Provider var discovery *coreosoidc.Provider
@ -1237,7 +1333,7 @@ func testSupervisorLogin(
// Next mutate the part of the session that is used during upstream refresh. // Next mutate the part of the session that is used during upstream refresh.
pinnipedSession, ok := storedRefreshSession.GetSession().(*psession.PinnipedSession) pinnipedSession, ok := storedRefreshSession.GetSession().(*psession.PinnipedSession)
require.True(t, ok, "should have been able to cast session data to 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. // Then save the mutated Secret back to Kubernetes.
// There is no update function, so delete and create again at the same name. // There is no update function, so delete and create again at the same name.