Addressing code review changes
- changed to use custom authenticators.Response rather than the k8s one that doesn't include space for a DN - Added more checking for correct idp type in token handler - small style changes Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
parent
84edfcb541
commit
f988879b6e
@ -7,7 +7,7 @@ package authenticators
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This interface is similar to the k8s token authenticator, but works with username/passwords instead
|
// This interface is similar to the k8s token authenticator, but works with username/passwords instead
|
||||||
@ -31,5 +31,10 @@ import (
|
|||||||
// See k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go for the token authenticator
|
// See k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go for the token authenticator
|
||||||
// interface, as well as the Response type.
|
// interface, as well as the Response type.
|
||||||
type UserAuthenticator interface {
|
type UserAuthenticator interface {
|
||||||
AuthenticateUser(ctx context.Context, username, password string) (*authenticator.Response, bool, error)
|
AuthenticateUser(ctx context.Context, username, password string) (*Response, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
User user.Info
|
||||||
|
DN string
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ import (
|
|||||||
"github.com/ory/fosite/token/jwt"
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
|
|
||||||
supervisoroidc "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
supervisoroidc "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
"go.pinniped.dev/internal/httputil/httperr"
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
"go.pinniped.dev/internal/httputil/securityheader"
|
"go.pinniped.dev/internal/httputil/securityheader"
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
@ -112,7 +112,7 @@ func handleAuthRequestForLDAPUpstream(
|
|||||||
subject := downstreamSubjectFromUpstreamLDAP(ldapUpstream, authenticateResponse)
|
subject := downstreamSubjectFromUpstreamLDAP(ldapUpstream, authenticateResponse)
|
||||||
username = authenticateResponse.User.GetName()
|
username = authenticateResponse.User.GetName()
|
||||||
groups := authenticateResponse.User.GetGroups()
|
groups := authenticateResponse.User.GetGroups()
|
||||||
dn := userDNFromAuthenticatedResponse(authenticateResponse)
|
dn := authenticateResponse.DN
|
||||||
|
|
||||||
customSessionData := &psession.CustomSessionData{
|
customSessionData := &psession.CustomSessionData{
|
||||||
ProviderUID: ldapUpstream.GetResourceUID(),
|
ProviderUID: ldapUpstream.GetResourceUID(),
|
||||||
@ -482,16 +482,7 @@ func addCSRFSetCookieHeader(w http.ResponseWriter, csrfValue csrftoken.CSRFToken
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downstreamSubjectFromUpstreamLDAP(ldapUpstream provider.UpstreamLDAPIdentityProviderI, authenticateResponse *authenticator.Response) string {
|
func downstreamSubjectFromUpstreamLDAP(ldapUpstream provider.UpstreamLDAPIdentityProviderI, authenticateResponse *authenticators.Response) string {
|
||||||
ldapURL := *ldapUpstream.GetURL()
|
ldapURL := *ldapUpstream.GetURL()
|
||||||
return downstreamsession.DownstreamLDAPSubject(authenticateResponse.User.GetUID(), ldapURL)
|
return downstreamsession.DownstreamLDAPSubject(authenticateResponse.User.GetUID(), ldapURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func userDNFromAuthenticatedResponse(authenticatedResponse *authenticator.Response) string {
|
|
||||||
// We should always have something here, but do some error checking anyway so it doesn't panic
|
|
||||||
dnSlice := authenticatedResponse.User.GetExtra()["userDN"]
|
|
||||||
if len(dnSlice) != 1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return dnSlice[0]
|
|
||||||
}
|
|
||||||
|
@ -19,12 +19,12 @@ import (
|
|||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
"go.pinniped.dev/internal/here"
|
"go.pinniped.dev/internal/here"
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
"go.pinniped.dev/internal/oidc/csrftoken"
|
"go.pinniped.dev/internal/oidc/csrftoken"
|
||||||
@ -273,18 +273,18 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
parsedUpstreamLDAPURL, err := url.Parse(upstreamLDAPURL)
|
parsedUpstreamLDAPURL, err := url.Parse(upstreamLDAPURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ldapAuthenticateFunc := func(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
ldapAuthenticateFunc := func(ctx context.Context, username, password string) (*authenticators.Response, bool, error) {
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
return nil, false, fmt.Errorf("should not have passed empty username or password to the authenticator")
|
return nil, false, fmt.Errorf("should not have passed empty username or password to the authenticator")
|
||||||
}
|
}
|
||||||
if username == happyLDAPUsername && password == happyLDAPPassword {
|
if username == happyLDAPUsername && password == happyLDAPPassword {
|
||||||
return &authenticator.Response{
|
return &authenticators.Response{
|
||||||
User: &user.DefaultInfo{
|
User: &user.DefaultInfo{
|
||||||
Name: happyLDAPUsernameFromAuthenticator,
|
Name: happyLDAPUsernameFromAuthenticator,
|
||||||
UID: happyLDAPUID,
|
UID: happyLDAPUID,
|
||||||
Groups: happyLDAPGroups,
|
Groups: happyLDAPGroups,
|
||||||
Extra: map[string][]string{"userDN": {happyLDAPUserDN}},
|
|
||||||
},
|
},
|
||||||
|
DN: happyLDAPUserDN,
|
||||||
}, true, nil
|
}, true, nil
|
||||||
}
|
}
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
@ -307,7 +307,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
erroringUpstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
erroringUpstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
Name: ldapUpstreamName,
|
Name: ldapUpstreamName,
|
||||||
ResourceUID: ldapUpstreamResourceUID,
|
ResourceUID: ldapUpstreamResourceUID,
|
||||||
AuthenticateFunc: func(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
AuthenticateFunc: func(ctx context.Context, username, password string) (*authenticators.Response, bool, error) {
|
||||||
return nil, false, fmt.Errorf("some ldap upstream auth error")
|
return nil, false, fmt.Errorf("some ldap upstream auth error")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ type UpstreamLDAPIdentityProviderI interface {
|
|||||||
authenticators.UserAuthenticator
|
authenticators.UserAuthenticator
|
||||||
|
|
||||||
// PerformRefresh performs a refresh against the upstream LDAP identity provider
|
// PerformRefresh performs a refresh against the upstream LDAP identity provider
|
||||||
PerformRefresh(ctx context.Context, userDN string, expectedUsername string, expectedSubject string) error
|
PerformRefresh(ctx context.Context, userDN, expectedUsername, expectedSubject string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DynamicUpstreamIDPProvider interface {
|
type DynamicUpstreamIDPProvider interface {
|
||||||
|
@ -172,7 +172,9 @@ func findOIDCProviderByNameAndValidateUID(
|
|||||||
|
|
||||||
func upstreamLDAPRefresh(ctx context.Context, s *psession.CustomSessionData, providerCache oidc.UpstreamIdentityProvidersLister, username string, subject string) error {
|
func upstreamLDAPRefresh(ctx context.Context, s *psession.CustomSessionData, providerCache oidc.UpstreamIdentityProvidersLister, username string, subject string) error {
|
||||||
// if you have neither a valid ldap session config nor a valid active directory session config
|
// if you have neither a valid ldap session config nor a valid active directory session config
|
||||||
if (s.LDAP == nil || s.LDAP.UserDN == "") && (s.ActiveDirectory == nil || s.ActiveDirectory.UserDN == "") {
|
validLDAP := s.ProviderType == psession.ProviderTypeLDAP && s.LDAP != nil && s.LDAP.UserDN != ""
|
||||||
|
validAD := s.ProviderType == psession.ProviderTypeActiveDirectory && s.ActiveDirectory != nil && s.ActiveDirectory.UserDN != ""
|
||||||
|
if !(validLDAP || validAD) {
|
||||||
return errorsx.WithStack(errMissingUpstreamSessionInternalError)
|
return errorsx.WithStack(errMissingUpstreamSessionInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +232,9 @@ func getDownstreamUsernameFromPinnipedSession(session *psession.PinnipedSession)
|
|||||||
if downstreamUsernameInterface == nil {
|
if downstreamUsernameInterface == nil {
|
||||||
return "", errorsx.WithStack(errMissingUpstreamSessionInternalError)
|
return "", errorsx.WithStack(errMissingUpstreamSessionInternalError)
|
||||||
}
|
}
|
||||||
downstreamUsername := downstreamUsernameInterface.(string)
|
downstreamUsername, ok := downstreamUsernameInterface.(string)
|
||||||
|
if !ok || len(downstreamUsername) == 0 {
|
||||||
|
return "", errorsx.WithStack(errMissingUpstreamSessionInternalError)
|
||||||
|
}
|
||||||
return downstreamUsername, nil
|
return downstreamUsername, nil
|
||||||
}
|
}
|
||||||
|
@ -1922,6 +1922,90 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "username in extra is not a string",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
|
Name: ldapUpstreamName,
|
||||||
|
ResourceUID: ldapUpstreamResourceUID,
|
||||||
|
URL: ldapUpstreamURL,
|
||||||
|
}),
|
||||||
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
|
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access") },
|
||||||
|
customSessionData: happyLDAPCustomSessionData,
|
||||||
|
//fositeSessionData: &openid.DefaultSession{},
|
||||||
|
want: happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccess(
|
||||||
|
happyLDAPCustomSessionData,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, refreshToken string) {
|
||||||
|
refreshTokenSignature := getFositeDataSignature(t, refreshToken)
|
||||||
|
firstRequester, err := oauthStore.GetRefreshTokenSession(context.Background(), refreshTokenSignature, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
session := firstRequester.GetSession().(*psession.PinnipedSession)
|
||||||
|
session.Fosite = &openid.DefaultSession{
|
||||||
|
Claims: &jwt.IDTokenClaims{
|
||||||
|
Extra: map[string]interface{}{"username": 123},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = oauthStore.DeleteRefreshTokenSession(context.Background(), refreshTokenSignature)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = oauthStore.CreateRefreshTokenSession(context.Background(), refreshTokenSignature, firstRequester)
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
refreshRequest: refreshRequestInputs{
|
||||||
|
want: tokenEndpointResponseExpectedValues{
|
||||||
|
wantStatus: http.StatusInternalServerError,
|
||||||
|
wantErrorResponseBody: here.Doc(`
|
||||||
|
{
|
||||||
|
"error": "error",
|
||||||
|
"error_description": "There was an internal server error. Required upstream data not found in session."
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "username in extra is an empty string",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
|
Name: ldapUpstreamName,
|
||||||
|
ResourceUID: ldapUpstreamResourceUID,
|
||||||
|
URL: ldapUpstreamURL,
|
||||||
|
}),
|
||||||
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
|
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access") },
|
||||||
|
customSessionData: happyLDAPCustomSessionData,
|
||||||
|
//fositeSessionData: &openid.DefaultSession{},
|
||||||
|
want: happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccess(
|
||||||
|
happyLDAPCustomSessionData,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, refreshToken string) {
|
||||||
|
refreshTokenSignature := getFositeDataSignature(t, refreshToken)
|
||||||
|
firstRequester, err := oauthStore.GetRefreshTokenSession(context.Background(), refreshTokenSignature, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
session := firstRequester.GetSession().(*psession.PinnipedSession)
|
||||||
|
session.Fosite = &openid.DefaultSession{
|
||||||
|
Claims: &jwt.IDTokenClaims{
|
||||||
|
Extra: map[string]interface{}{"username": ""},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = oauthStore.DeleteRefreshTokenSession(context.Background(), refreshTokenSignature)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = oauthStore.CreateRefreshTokenSession(context.Background(), refreshTokenSignature, firstRequester)
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
refreshRequest: refreshRequestInputs{
|
||||||
|
want: tokenEndpointResponseExpectedValues{
|
||||||
|
wantStatus: http.StatusInternalServerError,
|
||||||
|
wantErrorResponseBody: here.Doc(`
|
||||||
|
{
|
||||||
|
"error": "error",
|
||||||
|
"error_description": "There was an internal server error. Required upstream data not found in session."
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "when the ldap provider in the session storage is found but has the wrong resource UID during the refresh request",
|
name: "when the ldap provider in the session storage is found but has the wrong resource UID during the refresh request",
|
||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
|
@ -21,10 +21,10 @@ import (
|
|||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
"go.pinniped.dev/internal/crud"
|
"go.pinniped.dev/internal/crud"
|
||||||
"go.pinniped.dev/internal/fositestorage/authorizationcode"
|
"go.pinniped.dev/internal/fositestorage/authorizationcode"
|
||||||
"go.pinniped.dev/internal/fositestorage/openidconnect"
|
"go.pinniped.dev/internal/fositestorage/openidconnect"
|
||||||
@ -80,7 +80,7 @@ type TestUpstreamLDAPIdentityProvider struct {
|
|||||||
Name string
|
Name string
|
||||||
ResourceUID types.UID
|
ResourceUID types.UID
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
AuthenticateFunc func(ctx context.Context, username, password string) (*authenticator.Response, bool, error)
|
AuthenticateFunc func(ctx context.Context, username, password string) (*authenticators.Response, bool, error)
|
||||||
performRefreshCallCount int
|
performRefreshCallCount int
|
||||||
performRefreshArgs []*PerformRefreshArgs
|
performRefreshArgs []*PerformRefreshArgs
|
||||||
PerformRefreshErr error
|
PerformRefreshErr error
|
||||||
@ -96,7 +96,7 @@ func (u *TestUpstreamLDAPIdentityProvider) GetName() string {
|
|||||||
return u.Name
|
return u.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *TestUpstreamLDAPIdentityProvider) AuthenticateUser(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
func (u *TestUpstreamLDAPIdentityProvider) AuthenticateUser(ctx context.Context, username, password string) (*authenticators.Response, bool, error) {
|
||||||
return u.AuthenticateFunc(ctx, username, password)
|
return u.AuthenticateFunc(ctx, username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ func (u *TestUpstreamLDAPIdentityProvider) GetURL() *url.URL {
|
|||||||
return u.URL
|
return u.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *TestUpstreamLDAPIdentityProvider) PerformRefresh(ctx context.Context, userDN string, expectedUsername string, expectedSubject string) error {
|
func (u *TestUpstreamLDAPIdentityProvider) PerformRefresh(ctx context.Context, userDN, expectedUsername, expectedSubject string) error {
|
||||||
if u.performRefreshArgs == nil {
|
if u.performRefreshArgs == nil {
|
||||||
u.performRefreshArgs = make([]*PerformRefreshArgs, 0)
|
u.performRefreshArgs = make([]*PerformRefreshArgs, 0)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/utils/trace"
|
"k8s.io/utils/trace"
|
||||||
|
|
||||||
@ -170,7 +169,7 @@ func (p *Provider) GetConfig() ProviderConfig {
|
|||||||
return p.c
|
return p.c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) PerformRefresh(ctx context.Context, userDN string, expectedUsername string, 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)
|
search := p.refreshUserSearchRequest(userDN)
|
||||||
@ -372,7 +371,7 @@ func (p *Provider) TestConnection(ctx context.Context) error {
|
|||||||
// authentication for a given end user's username. It runs the same logic as AuthenticateUser except it does
|
// authentication for a given end user's username. It runs the same logic as AuthenticateUser except it does
|
||||||
// not bind as that user, so it does not test their password. It returns the same values that a real call to
|
// not bind as that user, so it does not test their password. It returns the same values that a real call to
|
||||||
// AuthenticateUser with the correct password would return.
|
// AuthenticateUser with the correct password would return.
|
||||||
func (p *Provider) DryRunAuthenticateUser(ctx context.Context, username string) (*authenticator.Response, bool, error) {
|
func (p *Provider) DryRunAuthenticateUser(ctx context.Context, username string) (*authenticators.Response, bool, error) {
|
||||||
endUserBindFunc := func(conn Conn, foundUserDN string) error {
|
endUserBindFunc := func(conn Conn, foundUserDN string) error {
|
||||||
// Act as if the end user bind always succeeds.
|
// Act as if the end user bind always succeeds.
|
||||||
return nil
|
return nil
|
||||||
@ -381,14 +380,14 @@ func (p *Provider) DryRunAuthenticateUser(ctx context.Context, username string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate an end user and return their mapped username, groups, and UID. Implements authenticators.UserAuthenticator.
|
// Authenticate an end user and return their mapped username, groups, and UID. Implements authenticators.UserAuthenticator.
|
||||||
func (p *Provider) AuthenticateUser(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
func (p *Provider) AuthenticateUser(ctx context.Context, username, password string) (*authenticators.Response, bool, error) {
|
||||||
endUserBindFunc := func(conn Conn, foundUserDN string) error {
|
endUserBindFunc := func(conn Conn, foundUserDN string) error {
|
||||||
return conn.Bind(foundUserDN, password)
|
return conn.Bind(foundUserDN, password)
|
||||||
}
|
}
|
||||||
return p.authenticateUserImpl(ctx, username, endUserBindFunc)
|
return p.authenticateUserImpl(ctx, username, endUserBindFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) authenticateUserImpl(ctx context.Context, username string, bindFunc func(conn Conn, foundUserDN string) error) (*authenticator.Response, bool, error) {
|
func (p *Provider) authenticateUserImpl(ctx context.Context, username string, bindFunc func(conn Conn, foundUserDN string) error) (*authenticators.Response, bool, error) {
|
||||||
t := trace.FromContext(ctx).Nest("slow ldap authenticate user attempt", trace.Field{Key: "providerName", Value: p.GetName()})
|
t := trace.FromContext(ctx).Nest("slow ldap authenticate user 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
|
||||||
|
|
||||||
@ -428,13 +427,13 @@ func (p *Provider) authenticateUserImpl(ctx context.Context, username string, bi
|
|||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &authenticator.Response{
|
response := &authenticators.Response{
|
||||||
User: &user.DefaultInfo{
|
User: &user.DefaultInfo{
|
||||||
Name: mappedUsername,
|
Name: mappedUsername,
|
||||||
UID: mappedUID,
|
UID: mappedUID,
|
||||||
Groups: mappedGroupNames,
|
Groups: mappedGroupNames,
|
||||||
Extra: map[string][]string{"userDN": {userDN}},
|
|
||||||
},
|
},
|
||||||
|
DN: userDN,
|
||||||
}
|
}
|
||||||
p.traceAuthSuccess(t)
|
p.traceAuthSuccess(t)
|
||||||
return response, true, nil
|
return response, true, nil
|
||||||
|
@ -18,9 +18,9 @@ import (
|
|||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
"go.pinniped.dev/internal/certauthority"
|
"go.pinniped.dev/internal/certauthority"
|
||||||
"go.pinniped.dev/internal/endpointaddr"
|
"go.pinniped.dev/internal/endpointaddr"
|
||||||
"go.pinniped.dev/internal/mocks/mockldapconn"
|
"go.pinniped.dev/internal/mocks/mockldapconn"
|
||||||
@ -151,17 +151,16 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The auth response which matches the exampleUserSearchResult and exampleGroupSearchResult.
|
// The auth response which matches the exampleUserSearchResult and exampleGroupSearchResult.
|
||||||
expectedAuthResponse := func(editFunc func(r *user.DefaultInfo)) *authenticator.Response {
|
expectedAuthResponse := func(editFunc func(r *user.DefaultInfo)) *authenticators.Response {
|
||||||
u := &user.DefaultInfo{
|
u := &user.DefaultInfo{
|
||||||
Name: testUserSearchResultUsernameAttributeValue,
|
Name: testUserSearchResultUsernameAttributeValue,
|
||||||
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
||||||
Groups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
|
Groups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
|
||||||
Extra: map[string][]string{"userDN": {testUserSearchResultDNValue}},
|
|
||||||
}
|
}
|
||||||
if editFunc != nil {
|
if editFunc != nil {
|
||||||
editFunc(u)
|
editFunc(u)
|
||||||
}
|
}
|
||||||
return &authenticator.Response{User: u}
|
return &authenticators.Response{User: u, DN: testUserSearchResultDNValue}
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -174,7 +173,7 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
dialError error
|
dialError error
|
||||||
wantError string
|
wantError string
|
||||||
wantToSkipDial bool
|
wantToSkipDial bool
|
||||||
wantAuthResponse *authenticator.Response
|
wantAuthResponse *authenticators.Response
|
||||||
wantUnauthenticated bool
|
wantUnauthenticated bool
|
||||||
skipDryRunAuthenticateUser bool // tests about when the end user bind fails don't make sense for DryRunAuthenticateUser()
|
skipDryRunAuthenticateUser bool // tests about when the end user bind fails don't make sense for DryRunAuthenticateUser()
|
||||||
}{
|
}{
|
||||||
@ -499,13 +498,13 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{
|
User: &user.DefaultInfo{
|
||||||
Name: testUserSearchResultUsernameAttributeValue,
|
Name: testUserSearchResultUsernameAttributeValue,
|
||||||
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
||||||
Groups: []string{"a", "b", "c"},
|
Groups: []string{"a", "b", "c"},
|
||||||
Extra: map[string][]string{"userDN": {testUserSearchResultDNValue}},
|
|
||||||
},
|
},
|
||||||
|
DN: testUserSearchResultDNValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
"go.pinniped.dev/internal/upstreamldap"
|
"go.pinniped.dev/internal/upstreamldap"
|
||||||
"go.pinniped.dev/test/testlib"
|
"go.pinniped.dev/test/testlib"
|
||||||
)
|
)
|
||||||
@ -677,7 +678,7 @@ func TestSimultaneousLDAPRequestsOnSingleProvider(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type authUserResult struct {
|
type authUserResult struct {
|
||||||
response *authenticator.Response
|
response *authenticators.Response
|
||||||
authenticated bool
|
authenticated bool
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user