From acc6c50e48e99dc3166bd4afe331980ab6608f88 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Tue, 3 May 2022 15:43:01 -0700 Subject: [PATCH 1/4] More unit tests for LDAP DNs which contain special chars Adding explicit coverage for PerformRefresh(). --- internal/upstreamldap/upstreamldap_test.go | 398 +++++++++++---------- 1 file changed, 216 insertions(+), 182 deletions(-) diff --git a/internal/upstreamldap/upstreamldap_test.go b/internal/upstreamldap/upstreamldap_test.go index c8bdc395..b4ee6bdf 100644 --- a/internal/upstreamldap/upstreamldap_test.go +++ b/internal/upstreamldap/upstreamldap_test.go @@ -51,6 +51,8 @@ const ( testUserSearchResultUIDAttributeValue = "some-upstream-uid-value" testGroupSearchResultGroupNameAttributeValue1 = "some-upstream-group-name-value1" testGroupSearchResultGroupNameAttributeValue2 = "some-upstream-group-name-value2" + testUserDNWithSpecialChars = `user DN with * \ special characters ()` + testUserDNWithSpecialCharsEscaped = `user DN with \2a \5c special characters \28\29` expectedGroupSearchPageSize = uint32(250) ) @@ -529,7 +531,7 @@ func TestEndUserAuthentication(t *testing.T) { Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { - DN: `result DN with * \ special characters ()`, + DN: testUserDNWithSpecialChars, Attributes: []*ldap.EntryAttribute{ ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}), @@ -538,16 +540,16 @@ func TestEndUserAuthentication(t *testing.T) { }, }, nil).Times(1) conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) { - escapedDN := `result DN with \2a \5c special characters \28\29` + escapedDN := testUserDNWithSpecialCharsEscaped r.Filter = fmt.Sprintf("(some-group-filter=%s-and-more-filter=%s)", escapedDN, escapedDN) }), expectedGroupSearchPageSize).Return(exampleGroupSearchResult, nil).Times(1) conn.EXPECT().Close().Times(1) }, bindEndUserMocks: func(conn *mockldapconn.MockConn) { - conn.EXPECT().Bind(`result DN with * \ special characters ()`, testUpstreamPassword).Times(1) + conn.EXPECT().Bind(testUserDNWithSpecialChars, testUpstreamPassword).Times(1) }, wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) { - r.DN = `result DN with * \ special characters ()` + r.DN = testUserDNWithSpecialChars }), }, { @@ -563,7 +565,7 @@ func TestEndUserAuthentication(t *testing.T) { Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { - DN: `result DN with * \ special characters ()`, + DN: testUserDNWithSpecialChars, Attributes: []*ldap.EntryAttribute{ ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}), @@ -572,15 +574,15 @@ func TestEndUserAuthentication(t *testing.T) { }, }, nil).Times(1) conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) { - r.Filter = fmt.Sprintf("(member=%s)", `result DN with \2a \5c special characters \28\29`) + r.Filter = fmt.Sprintf("(member=%s)", testUserDNWithSpecialCharsEscaped) }), expectedGroupSearchPageSize).Return(exampleGroupSearchResult, nil).Times(1) conn.EXPECT().Close().Times(1) }, bindEndUserMocks: func(conn *mockldapconn.MockConn) { - conn.EXPECT().Bind(`result DN with * \ special characters ()`, testUpstreamPassword).Times(1) + conn.EXPECT().Bind(testUserDNWithSpecialChars, testUpstreamPassword).Times(1) }, wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) { - r.DN = `result DN with * \ special characters ()` + r.DN = testUserDNWithSpecialChars }), }, { @@ -1219,28 +1221,41 @@ func TestEndUserAuthentication(t *testing.T) { func TestUpstreamRefresh(t *testing.T) { pwdLastSetAttribute := "pwdLastSet" - expectedUserSearch := &ldap.SearchRequest{ - BaseDN: testUserSearchResultDNValue, - Scope: ldap.ScopeBaseObject, - DerefAliases: ldap.NeverDerefAliases, - SizeLimit: 2, - TimeLimit: 90, - TypesOnly: false, - Filter: "(objectClass=*)", - Attributes: []string{testUserSearchUsernameAttribute, testUserSearchUIDAttribute, pwdLastSetAttribute}, - Controls: nil, // don't need paging because we set the SizeLimit so small + + expectedUserSearch := func(editFunc func(r *ldap.SearchRequest)) *ldap.SearchRequest { + request := &ldap.SearchRequest{ + BaseDN: testUserSearchResultDNValue, + Scope: ldap.ScopeBaseObject, + DerefAliases: ldap.NeverDerefAliases, + SizeLimit: 2, + TimeLimit: 90, + TypesOnly: false, + Filter: "(objectClass=*)", + Attributes: []string{testUserSearchUsernameAttribute, testUserSearchUIDAttribute, pwdLastSetAttribute}, + Controls: nil, // don't need paging because we set the SizeLimit so small + } + if editFunc != nil { + editFunc(request) + } + return request } - expectedGroupSearch := &ldap.SearchRequest{ - BaseDN: testGroupSearchBase, - Scope: ldap.ScopeWholeSubtree, - DerefAliases: ldap.NeverDerefAliases, - SizeLimit: 0, // unlimited size because we will search with paging - TimeLimit: 90, - TypesOnly: false, - Filter: testGroupSearchFilterInterpolated, - Attributes: []string{testGroupSearchGroupNameAttribute}, - Controls: nil, // nil because ldap.SearchWithPaging() will set the appropriate controls for us + expectedGroupSearch := func(editFunc func(r *ldap.SearchRequest)) *ldap.SearchRequest { + request := &ldap.SearchRequest{ + BaseDN: testGroupSearchBase, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + SizeLimit: 0, // unlimited size because we will search with paging + TimeLimit: 90, + TypesOnly: false, + Filter: testGroupSearchFilterInterpolated, + Attributes: []string{testGroupSearchGroupNameAttribute}, + Controls: nil, // nil because ldap.SearchWithPaging() will set the appropriate controls for us + } + if editFunc != nil { + editFunc(request) + } + return request } happyPathUserSearchResult := &ldap.SearchResult{ @@ -1266,116 +1281,170 @@ func TestUpstreamRefresh(t *testing.T) { }, } - providerConfig := &ProviderConfig{ - Name: "some-provider-name", - Host: testHost, - CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test - ConnectionProtocol: TLS, - BindUsername: testBindUsername, - BindPassword: testBindPassword, - UserSearch: UserSearchConfig{ - Base: testUserSearchBase, - UIDAttribute: testUserSearchUIDAttribute, - UsernameAttribute: testUserSearchUsernameAttribute, - }, - RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ - pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute), + happyPathGroupSearchResult := &ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: testGroupSearchResultDNValue1, + Attributes: []*ldap.EntryAttribute{ + ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue1}), + }, + }, + { + DN: testGroupSearchResultDNValue2, + Attributes: []*ldap.EntryAttribute{ + ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue2}), + }, + }, }, + Referrals: []string{}, // note that we are not following referrals at this time + Controls: []ldap.Control{}, + } + + providerConfig := func(editFunc func(p *ProviderConfig)) *ProviderConfig { + config := &ProviderConfig{ + Name: "some-provider-name", + Host: testHost, + CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test + ConnectionProtocol: TLS, + BindUsername: testBindUsername, + BindPassword: testBindPassword, + UserSearch: UserSearchConfig{ + Base: testUserSearchBase, + UIDAttribute: testUserSearchUIDAttribute, + UsernameAttribute: testUserSearchUsernameAttribute, + }, + GroupSearch: GroupSearchConfig{ + Base: testGroupSearchBase, + Filter: testGroupSearchFilter, + GroupNameAttribute: testGroupSearchGroupNameAttribute, + }, + RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ + pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute), + }, + } + if editFunc != nil { + editFunc(config) + } + return config } tests := []struct { name string providerConfig *ProviderConfig setupMocks func(conn *mockldapconn.MockConn) + refreshUserDN string dialError error wantErr string wantGroups []string }{ { - name: "happy path where searching the dn returns a single entry", - providerConfig: providerConfig, + name: "happy path without group search where searching the dn returns a single entry", + providerConfig: providerConfig(func(p *ProviderConfig) { + p.GroupSearch = GroupSearchConfig{} + }), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) + conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1) conn.EXPECT().Close().Times(1) }, wantGroups: []string{}, }, { - name: "happy path where group search returns groups", - providerConfig: &ProviderConfig{ - Name: "some-provider-name", - Host: testHost, - CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test - ConnectionProtocol: TLS, - BindUsername: testBindUsername, - BindPassword: testBindPassword, - UserSearch: UserSearchConfig{ - Base: testUserSearchBase, - UIDAttribute: testUserSearchUIDAttribute, - UsernameAttribute: testUserSearchUsernameAttribute, - }, - GroupSearch: GroupSearchConfig{ - Base: testGroupSearchBase, - Filter: testGroupSearchFilter, - GroupNameAttribute: testGroupSearchGroupNameAttribute, - }, - RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ - pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute), - }, - }, + name: "happy path where group search returns groups", + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) - conn.EXPECT().SearchWithPaging(expectedGroupSearch, expectedGroupSearchPageSize).Return(&ldap.SearchResult{ - Entries: []*ldap.Entry{ - { - DN: testGroupSearchResultDNValue1, - Attributes: []*ldap.EntryAttribute{ - ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue1}), - }, - }, - { - DN: testGroupSearchResultDNValue2, - Attributes: []*ldap.EntryAttribute{ - ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue2}), - }, - }, - }, - Referrals: []string{}, // note that we are not following referrals at this time - Controls: []ldap.Control{}, - }, nil).Times(1) + conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1) + conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).Return(happyPathGroupSearchResult, nil).Times(1) conn.EXPECT().Close().Times(1) }, wantGroups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2}, }, { - name: "happy path where group search returns no groups", - providerConfig: &ProviderConfig{ - Name: "some-provider-name", - Host: testHost, - CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test - ConnectionProtocol: TLS, - BindUsername: testBindUsername, - BindPassword: testBindPassword, - UserSearch: UserSearchConfig{ - Base: testUserSearchBase, - UIDAttribute: testUserSearchUIDAttribute, - UsernameAttribute: testUserSearchUsernameAttribute, - }, - GroupSearch: GroupSearchConfig{ - Base: testGroupSearchBase, - Filter: testGroupSearchFilter, - GroupNameAttribute: testGroupSearchGroupNameAttribute, - }, - RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ - pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute), - }, - }, + name: "happy path when the user DN has special LDAP search filter characters then they must be properly escaped in the custom group search filter", + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) - conn.EXPECT().SearchWithPaging(expectedGroupSearch, expectedGroupSearchPageSize).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) { + r.BaseDN = testUserDNWithSpecialChars + })). + Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: testUserDNWithSpecialChars, + Attributes: []*ldap.EntryAttribute{ + { + Name: testUserSearchUsernameAttribute, + Values: []string{testUserSearchResultUsernameAttributeValue}, + }, + { + Name: testUserSearchUIDAttribute, + ByteValues: [][]byte{[]byte(testUserSearchResultUIDAttributeValue)}, + }, + { + Name: pwdLastSetAttribute, + Values: []string{"132801740800000000"}, + ByteValues: [][]byte{[]byte("132801740800000000")}, + }, + }, + }, + }, + }, nil).Times(1) + conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) { + r.Filter = fmt.Sprintf("(some-group-filter=%s-and-more-filter=%s)", testUserDNWithSpecialCharsEscaped, testUserDNWithSpecialCharsEscaped) + }), expectedGroupSearchPageSize).Return(happyPathGroupSearchResult, nil).Times(1) + conn.EXPECT().Close().Times(1) + }, + refreshUserDN: testUserDNWithSpecialChars, + wantGroups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2}, + }, + { + name: "when the user DN has special LDAP search filter characters then they must be properly escaped in the default group search filter", + providerConfig: providerConfig(func(p *ProviderConfig) { + p.GroupSearch.Filter = "" + }), + setupMocks: func(conn *mockldapconn.MockConn) { + conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) + conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) { + r.BaseDN = testUserDNWithSpecialChars + })). + Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: testUserDNWithSpecialChars, + Attributes: []*ldap.EntryAttribute{ + { + Name: testUserSearchUsernameAttribute, + Values: []string{testUserSearchResultUsernameAttributeValue}, + }, + { + Name: testUserSearchUIDAttribute, + ByteValues: [][]byte{[]byte(testUserSearchResultUIDAttributeValue)}, + }, + { + Name: pwdLastSetAttribute, + Values: []string{"132801740800000000"}, + ByteValues: [][]byte{[]byte("132801740800000000")}, + }, + }, + }, + }, + }, nil).Times(1) + conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) { + r.Filter = fmt.Sprintf("(member=%s)", testUserDNWithSpecialCharsEscaped) + }), expectedGroupSearchPageSize).Return(happyPathGroupSearchResult, nil).Times(1) + conn.EXPECT().Close().Times(1) + }, + refreshUserDN: testUserDNWithSpecialChars, + wantGroups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2}, + }, + { + name: "happy path where group search returns no groups", + providerConfig: providerConfig(nil), + setupMocks: func(conn *mockldapconn.MockConn) { + conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) + conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1) + conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{}, Referrals: []string{}, // note that we are not following referrals at this time Controls: []ldap.Control{}, @@ -1386,44 +1455,25 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "happy path where group search is configured but skipGroupRefresh is set", - providerConfig: &ProviderConfig{ - Name: "some-provider-name", - Host: testHost, - CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test - ConnectionProtocol: TLS, - BindUsername: testBindUsername, - BindPassword: testBindPassword, - UserSearch: UserSearchConfig{ - Base: testUserSearchBase, - UIDAttribute: testUserSearchUIDAttribute, - UsernameAttribute: testUserSearchUsernameAttribute, - }, - GroupSearch: GroupSearchConfig{ - Base: testGroupSearchBase, - Filter: testGroupSearchFilter, - GroupNameAttribute: testGroupSearchGroupNameAttribute, - SkipGroupRefresh: true, - }, - RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ - pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute), - }, - }, + providerConfig: providerConfig(func(p *ProviderConfig) { + p.GroupSearch.SkipGroupRefresh = true + }), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) // note that group search is not expected + conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1) // note that group search is not expected conn.EXPECT().Close().Times(1) }, wantGroups: nil, // do not update groups }, { name: "error where dial fails", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), dialError: errors.New("some dial error"), wantErr: "error dialing host \"ldap.example.com:8443\": some dial error", }, { name: "error binding", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Return(errors.New("some bind error")).Times(1) conn.EXPECT().Close().Times(1) @@ -1432,10 +1482,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result returns no entries", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{}, }, nil).Times(1) conn.EXPECT().Close().Times(1) @@ -1444,20 +1494,20 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "error searching", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(nil, errors.New("some search error")) + conn.EXPECT().Search(expectedUserSearch(nil)).Return(nil, errors.New("some search error")) conn.EXPECT().Close().Times(1) }, wantErr: "error searching for user \"some-upstream-user-dn\": some search error", }, { name: "search result returns more than one entry", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1475,10 +1525,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has wrong uid", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1501,10 +1551,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has wrong username", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1523,10 +1573,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has no dn", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { Attributes: []*ldap.EntryAttribute{ @@ -1548,10 +1598,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has 0 values for username attribute", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1574,10 +1624,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has more than one value for username attribute", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1600,10 +1650,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has 0 values for uid attribute", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1626,10 +1676,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has 2 values for uid attribute", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1652,10 +1702,10 @@ func TestUpstreamRefresh(t *testing.T) { }, { name: "search result has a changed pwdLastSet value", - providerConfig: providerConfig, + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ + conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{ Entries: []*ldap.Entry{ { DN: testUserSearchResultDNValue, @@ -1681,32 +1731,12 @@ func TestUpstreamRefresh(t *testing.T) { wantErr: "validation for attribute \"pwdLastSet\" failed during upstream refresh: value for attribute \"pwdLastSet\" has changed since initial value at login", }, { - name: "group search returns an error", - providerConfig: &ProviderConfig{ - Name: "some-provider-name", - Host: testHost, - CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test - ConnectionProtocol: TLS, - BindUsername: testBindUsername, - BindPassword: testBindPassword, - UserSearch: UserSearchConfig{ - Base: testUserSearchBase, - UIDAttribute: testUserSearchUIDAttribute, - UsernameAttribute: testUserSearchUsernameAttribute, - }, - GroupSearch: GroupSearchConfig{ - Base: testGroupSearchBase, - Filter: testGroupSearchFilter, - GroupNameAttribute: testGroupSearchGroupNameAttribute, - }, - RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ - pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute), - }, - }, + name: "group search returns an error", + providerConfig: providerConfig(nil), setupMocks: func(conn *mockldapconn.MockConn) { conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) - conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) - conn.EXPECT().SearchWithPaging(expectedGroupSearch, expectedGroupSearchPageSize).Return(nil, errors.New("some search error")).Times(1) + conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1) + conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).Return(nil, errors.New("some search error")).Times(1) conn.EXPECT().Close().Times(1) }, wantErr: "error searching for group memberships for user with DN \"some-upstream-user-dn\": some search error", @@ -1735,13 +1765,17 @@ func TestUpstreamRefresh(t *testing.T) { return conn, nil }) + if tt.refreshUserDN == "" { + tt.refreshUserDN = testUserSearchResultDNValue // default for all tests + } + initialPwdLastSetEncoded := base64.RawURLEncoding.EncodeToString([]byte("132801740800000000")) ldapProvider := New(*tt.providerConfig) subject := "ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU" groups, err := ldapProvider.PerformRefresh(context.Background(), provider.StoredRefreshAttributes{ Username: testUserSearchResultUsernameAttributeValue, Subject: subject, - DN: testUserSearchResultDNValue, + DN: tt.refreshUserDN, AdditionalAttributes: map[string]string{pwdLastSetAttribute: initialPwdLastSetEncoded}, }) if tt.wantErr != "" { From eb891d77a5a2d03bbf372ebb76026d3bdb5cbb68 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Wed, 4 May 2022 12:42:55 -0700 Subject: [PATCH 2/4] Tiny fix: pinninpeds->pinnipeds Signed-off-by: Margo Crawford --- test/deploy/tools/ldap.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/deploy/tools/ldap.yaml b/test/deploy/tools/ldap.yaml index 831dd6e2..94638ecf 100644 --- a/test/deploy/tools/ldap.yaml +++ b/test/deploy/tools/ldap.yaml @@ -1,4 +1,4 @@ -#! Copyright 2021 the Pinniped contributors. All Rights Reserved. +#! Copyright 2021-2022 the Pinniped contributors. All Rights Reserved. #! SPDX-License-Identifier: Apache-2.0 #@ load("@ytt:data", "data") @@ -114,7 +114,7 @@ ldap.ldif: | dn: cn=mammals,ou=groups,dc=pinniped,dc=dev cn: mammals objectClass: groupOfNames - member: cn=pinninpeds,ou=groups,dc=pinniped,dc=dev + member: cn=pinnipeds,ou=groups,dc=pinniped,dc=dev member: cn=olive,ou=users,dc=pinniped,dc=dev #@ end From 1a59b6a686f201a8d2be674ea65418c1badb8387 Mon Sep 17 00:00:00 2001 From: anjalitelang <49958114+anjaltelang@users.noreply.github.com> Date: Wed, 4 May 2022 16:06:33 -0400 Subject: [PATCH 3/4] Update ROADMAP.md Changes made to reflect status as of May 4th, 2022 --- ROADMAP.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 49420bb6..c80700de 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -36,9 +36,8 @@ The following table includes the current roadmap for Pinniped. If you have any q Last Updated: March 2022 |Theme|Description|Timeline| |--|--|--| -|Improving Security Posture|Support FIPS compliant Boring crypto libraries |March/April 2022| -|Improving Security Posture|Support Audit logging of security events related to Authentication |April/May 2022| -|Improving Usability|Support for integrating with UI/Dashboards |June/July 2022| +|Improving Security Posture|Support Audit logging of security events related to Authentication |May/June 2022| +|Improving Usability|Support for integrating with UI/Dashboards |May/June 2022| |Improving Security Posture|TLS hardening contd|June/July 2022| |Multiple IDP support|Support multiple IDPs configured on a single Supervisor|Exploring/Ongoing| |Improving Security Posture|mTLS for Supervisor sessions |Exploring/Ongoing| From afc73221d6ea8fc9a77beb6c5d351a518d505053 Mon Sep 17 00:00:00 2001 From: Pinny Date: Fri, 6 May 2022 19:28:56 +0000 Subject: [PATCH 4/4] Updated versions in docs for v0.17.0 release --- site/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/config.yaml b/site/config.yaml index 24b01b75..ce02f523 100644 --- a/site/config.yaml +++ b/site/config.yaml @@ -7,7 +7,7 @@ params: github_url: "https://github.com/vmware-tanzu/pinniped" slack_url: "https://kubernetes.slack.com/messages/pinniped" community_url: "https://go.pinniped.dev/community" - latest_version: v0.16.0 + latest_version: v0.17.0 latest_codegen_version: 1.23 pygmentsCodefences: true pygmentsStyle: "pygments"