Fetch AD search base from defaultNamingContext when not specified
This commit is contained in:
parent
8e1d70562d
commit
cb0ee07b51
@ -81,6 +81,45 @@ func (s *activeDirectoryUpstreamGenericLDAPSpec) GroupSearch() upstreamwatchers.
|
|||||||
return &activeDirectoryUpstreamGenericLDAPGroupSearch{s.activeDirectoryIdentityProvider.Spec.GroupSearch}
|
return &activeDirectoryUpstreamGenericLDAPGroupSearch{s.activeDirectoryIdentityProvider.Spec.GroupSearch}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *activeDirectoryUpstreamGenericLDAPSpec) DetectAndSetSearchBase(ctx context.Context, config *upstreamldap.ProviderConfig) *v1alpha1.Condition {
|
||||||
|
config.GroupSearch.Base = s.activeDirectoryIdentityProvider.Spec.GroupSearch.Base
|
||||||
|
config.UserSearch.Base = s.activeDirectoryIdentityProvider.Spec.UserSearch.Base
|
||||||
|
if config.GroupSearch.Base != "" && config.UserSearch.Base != "" {
|
||||||
|
// Both were already set in spec so just return; no need to query the RootDSE
|
||||||
|
return &v1alpha1.Condition{
|
||||||
|
Type: "SearchBaseFound",
|
||||||
|
Status: v1alpha1.ConditionTrue,
|
||||||
|
Reason: "Success",
|
||||||
|
Message: "Using search base from ActiveDirectoryIdentityProvider config.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ldapProvider := upstreamldap.New(*config)
|
||||||
|
// Query your AD server for the defaultNamingContext to get a DN to use as the search base
|
||||||
|
// when it isn't specified.
|
||||||
|
// https://ldapwiki.com/wiki/DefaultNamingContext
|
||||||
|
defaultNamingContext, err := ldapProvider.SearchForDefaultNamingContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return &v1alpha1.Condition{
|
||||||
|
Type: upstreamwatchers.TypeSearchBaseFound,
|
||||||
|
Status: v1alpha1.ConditionFalse,
|
||||||
|
Reason: "Error",
|
||||||
|
Message: fmt.Sprintf(`Error finding search base: %s`, err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.UserSearch.Base == "" {
|
||||||
|
config.UserSearch.Base = defaultNamingContext
|
||||||
|
}
|
||||||
|
if config.GroupSearch.Base == "" {
|
||||||
|
config.GroupSearch.Base = defaultNamingContext
|
||||||
|
}
|
||||||
|
return &v1alpha1.Condition{
|
||||||
|
Type: upstreamwatchers.TypeSearchBaseFound,
|
||||||
|
Status: v1alpha1.ConditionTrue,
|
||||||
|
Reason: "Success",
|
||||||
|
Message: "Successfully fetched defaultNamingContext to use as default search base from RootDSE.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type activeDirectoryUpstreamGenericLDAPUserSearch struct {
|
type activeDirectoryUpstreamGenericLDAPUserSearch struct {
|
||||||
userSearch v1alpha1.ActiveDirectoryIdentityProviderUserSearch
|
userSearch v1alpha1.ActiveDirectoryIdentityProviderUserSearch
|
||||||
}
|
}
|
||||||
|
@ -256,11 +256,51 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
ObservedGeneration: gen,
|
ObservedGeneration: gen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchBaseFoundInRootDSECondition := func(gen int64) v1alpha1.Condition {
|
||||||
|
return v1alpha1.Condition{
|
||||||
|
Type: "SearchBaseFound",
|
||||||
|
Status: "True",
|
||||||
|
LastTransitionTime: now,
|
||||||
|
Reason: "Success",
|
||||||
|
Message: "Successfully fetched defaultNamingContext to use as default search base from RootDSE.",
|
||||||
|
ObservedGeneration: gen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchBaseFoundInConfigCondition := func(gen int64) v1alpha1.Condition {
|
||||||
|
return v1alpha1.Condition{
|
||||||
|
Type: "SearchBaseFound",
|
||||||
|
Status: "True",
|
||||||
|
LastTransitionTime: now,
|
||||||
|
Reason: "Success",
|
||||||
|
Message: "Using search base from ActiveDirectoryIdentityProvider config.",
|
||||||
|
ObservedGeneration: gen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchBaseFoundErrorCondition := func(gen int64, message string) v1alpha1.Condition {
|
||||||
|
return v1alpha1.Condition{
|
||||||
|
Type: "SearchBaseFound",
|
||||||
|
Status: "False",
|
||||||
|
LastTransitionTime: now,
|
||||||
|
Reason: "Error",
|
||||||
|
Message: message,
|
||||||
|
ObservedGeneration: gen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
allConditionsTrue := func(gen int64, secretVersion string) []v1alpha1.Condition {
|
allConditionsTrue := func(gen int64, secretVersion string) []v1alpha1.Condition {
|
||||||
return []v1alpha1.Condition{
|
return []v1alpha1.Condition{
|
||||||
bindSecretValidTrueCondition(gen),
|
bindSecretValidTrueCondition(gen),
|
||||||
activeDirectoryConnectionValidTrueCondition(gen, secretVersion),
|
activeDirectoryConnectionValidTrueCondition(gen, secretVersion),
|
||||||
|
searchBaseFoundInConfigCondition(gen),
|
||||||
tlsConfigurationValidLoadedTrueCondition(gen),
|
tlsConfigurationValidLoadedTrueCondition(gen),
|
||||||
|
// TODO should there be a condition when you just get it from the config? is that worth reporting?
|
||||||
|
// I'm thinking maybe no since it's not a network call or anything... it's just like any other field in the
|
||||||
|
// spec that we don't bother to report on.
|
||||||
|
// Although perhaps it would be weirder to have a condition that only sometimes exists? And it's a useful
|
||||||
|
// way to communicate internally.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,6 +312,34 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expectedDefaultNamingContextSearch := func() *ldap.SearchRequest {
|
||||||
|
request := &ldap.SearchRequest{
|
||||||
|
BaseDN: "",
|
||||||
|
Scope: ldap.ScopeBaseObject,
|
||||||
|
DerefAliases: ldap.NeverDerefAliases,
|
||||||
|
SizeLimit: 2,
|
||||||
|
TimeLimit: 90,
|
||||||
|
TypesOnly: false,
|
||||||
|
Filter: "(objectClass=*)",
|
||||||
|
Attributes: []string{"defaultNamingContext"},
|
||||||
|
Controls: nil, // don't need paging because we set the SizeLimit so small
|
||||||
|
}
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleDefaultNamingContext := "dc=default,dc=naming,dc=context,dc=example,dc=com"
|
||||||
|
|
||||||
|
exampleDefaultNamingContextSearchResult := &ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: "",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute("defaultNamingContext", []string{exampleDefaultNamingContext}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
initialValidatedSettings map[string]upstreamwatchers.ValidatedSettings
|
initialValidatedSettings map[string]upstreamwatchers.ValidatedSettings
|
||||||
@ -305,7 +373,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing secret",
|
name: "missing secret",
|
||||||
@ -477,6 +545,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: []v1alpha1.Condition{
|
Conditions: []v1alpha1.Condition{
|
||||||
bindSecretValidTrueCondition(1234),
|
bindSecretValidTrueCondition(1234),
|
||||||
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundInConfigCondition(1234),
|
||||||
{
|
{
|
||||||
Type: "TLSConfigurationValid",
|
Type: "TLSConfigurationValid",
|
||||||
Status: "True",
|
Status: "True",
|
||||||
@ -488,7 +557,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when TLS connection fails it tries to use StartTLS instead: without a specified port it automatically switches ports",
|
name: "when TLS connection fails it tries to use StartTLS instead: without a specified port it automatically switches ports",
|
||||||
@ -542,11 +611,12 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
"ldap.example.com", testBindUsername, testSecretName, "4242"),
|
"ldap.example.com", testBindUsername, testSecretName, "4242"),
|
||||||
ObservedGeneration: 1234,
|
ObservedGeneration: 1234,
|
||||||
},
|
},
|
||||||
|
searchBaseFoundInConfigCondition(1234),
|
||||||
tlsConfigurationValidLoadedTrueCondition(1234),
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.StartTLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.StartTLS, UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when TLS connection fails it tries to use StartTLS instead: with a specified port it does not automatically switch ports",
|
name: "when TLS connection fails it tries to use StartTLS instead: with a specified port it does not automatically switch ports",
|
||||||
@ -599,10 +669,12 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
"ldap.example.com:5678", testBindUsername, "ldap.example.com:5678"),
|
"ldap.example.com:5678", testBindUsername, "ldap.example.com:5678"),
|
||||||
ObservedGeneration: 1234,
|
ObservedGeneration: 1234,
|
||||||
},
|
},
|
||||||
|
searchBaseFoundInConfigCondition(1234),
|
||||||
tlsConfigurationValidLoadedTrueCondition(1234),
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-nil TLS configuration with empty CertificateAuthorityData is valid",
|
name: "non-nil TLS configuration with empty CertificateAuthorityData is valid",
|
||||||
@ -643,7 +715,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one valid upstream and one invalid upstream updates the cache to include only the valid upstream",
|
name: "one valid upstream and one invalid upstream updates the cache to include only the valid upstream",
|
||||||
@ -686,7 +758,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when testing the connection to the LDAP server fails then the upstream is still added to the cache anyway (treated like a warning)",
|
name: "when testing the connection to the LDAP server fails then the upstream is still added to the cache anyway (treated like a warning)",
|
||||||
@ -716,10 +788,107 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
testHost, testBindUsername, testBindUsername),
|
testHost, testBindUsername, testBindUsername),
|
||||||
ObservedGeneration: 1234,
|
ObservedGeneration: 1234,
|
||||||
},
|
},
|
||||||
|
searchBaseFoundInConfigCondition(1234),
|
||||||
tlsConfigurationValidLoadedTrueCondition(1234),
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when testing the connection to the LDAP server fails, but later querying defaultsearchbase succeeds, then the upstream is still added to the cache anyway (treated like a warning)",
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Spec.UserSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("")},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
// Should perform a test dial and bind.
|
||||||
|
// Expect three calls bind: once for trying TLS and once for trying StartTLS (both fail), and one before querying for defaultNamingContext (succeeds)
|
||||||
|
first := conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(2).Return(errors.New("some bind error"))
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1).After(first)
|
||||||
|
conn.EXPECT().Search(expectedDefaultNamingContextSearch()).Times(1).Return(exampleDefaultNamingContextSearchResult, nil)
|
||||||
|
conn.EXPECT().Close().Times(3)
|
||||||
|
},
|
||||||
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||||
|
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||||
|
{
|
||||||
|
Name: testName,
|
||||||
|
Host: testHost,
|
||||||
|
ConnectionProtocol: upstreamldap.TLS,
|
||||||
|
CABundle: testCABundle,
|
||||||
|
BindUsername: testBindUsername,
|
||||||
|
BindPassword: testBindPassword,
|
||||||
|
UserSearch: upstreamldap.UserSearchConfig{
|
||||||
|
Base: exampleDefaultNamingContext,
|
||||||
|
Filter: testUserSearchFilter,
|
||||||
|
UsernameAttribute: testUsernameAttrName,
|
||||||
|
UIDAttribute: testUIDAttrName,
|
||||||
|
},
|
||||||
|
GroupSearch: upstreamldap.GroupSearchConfig{
|
||||||
|
Base: testGroupSearchBase,
|
||||||
|
Filter: testGroupSearchFilter,
|
||||||
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Error",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
{
|
||||||
|
Type: "LDAPConnectionValid",
|
||||||
|
Status: "False",
|
||||||
|
LastTransitionTime: now,
|
||||||
|
Reason: "LDAPConnectionError",
|
||||||
|
Message: fmt.Sprintf(
|
||||||
|
`could not successfully connect to "%s" and bind as user "%s": error binding as "%s": some bind error`,
|
||||||
|
testHost, testBindUsername, testBindUsername),
|
||||||
|
ObservedGeneration: 1234,
|
||||||
|
},
|
||||||
|
searchBaseFoundInRootDSECondition(1234),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {UserSearchBase: exampleDefaultNamingContext, GroupSearchBase: testGroupSearchBase}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when testing the connection to the LDAP server fails, and querying defaultsearchbase fails, then the upstream is not added to the cache",
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Spec.UserSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("")},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
// Should perform a test dial and bind.
|
||||||
|
// Expect 3 calls to each of these: once for trying TLS and once for trying StartTLS, one before querying
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(3).Return(errors.New("some bind error"))
|
||||||
|
conn.EXPECT().Close().Times(3)
|
||||||
|
},
|
||||||
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Error",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
{
|
||||||
|
Type: "LDAPConnectionValid",
|
||||||
|
Status: "False",
|
||||||
|
LastTransitionTime: now,
|
||||||
|
Reason: "LDAPConnectionError",
|
||||||
|
Message: fmt.Sprintf(
|
||||||
|
`could not successfully connect to "%s" and bind as user "%s": error binding as "%s": some bind error`,
|
||||||
|
testHost, testBindUsername, testBindUsername),
|
||||||
|
ObservedGeneration: 1234,
|
||||||
|
},
|
||||||
|
searchBaseFoundErrorCondition(1234, "Error finding search base: error binding as \"test-bind-username\" before user search: some bind error"),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {GroupSearchBase: testGroupSearchBase}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was already validated using TLS for the current resource generation and secret version, then do not validate it again and keep using TLS",
|
name: "when the LDAP server connection was already validated using TLS for the current resource generation and secret version, then do not validate it again and keep using TLS",
|
||||||
@ -730,7 +899,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})},
|
})},
|
||||||
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
setupMocks: func(conn *mockldapconn.MockConn) {
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
// Should not perform a test dial and bind. No mocking here means the test will fail if Bind() or Close() are called.
|
// Should not perform a test dial and bind. No mocking here means the test will fail if Bind() or Close() are called.
|
||||||
},
|
},
|
||||||
@ -742,7 +911,112 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: testUserSearchBase, GroupSearchBase: testGroupSearchBase}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the LDAP server connection was already validated using TLS, but the search base wasn't, load TLS into the config and try again for the search base",
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Generation = 1234
|
||||||
|
upstream.Status.Conditions = []v1alpha1.Condition{
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
}
|
||||||
|
upstream.Spec.UserSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
|
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
conn.EXPECT().Search(expectedDefaultNamingContextSearch()).Return(exampleDefaultNamingContextSearchResult, nil).Times(1)
|
||||||
|
},
|
||||||
|
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||||
|
{
|
||||||
|
Name: testName,
|
||||||
|
Host: testHost,
|
||||||
|
ConnectionProtocol: upstreamldap.TLS,
|
||||||
|
CABundle: testCABundle,
|
||||||
|
BindUsername: testBindUsername,
|
||||||
|
BindPassword: testBindPassword,
|
||||||
|
UserSearch: upstreamldap.UserSearchConfig{
|
||||||
|
Base: exampleDefaultNamingContext,
|
||||||
|
Filter: testUserSearchFilter,
|
||||||
|
UsernameAttribute: testUsernameAttrName,
|
||||||
|
UIDAttribute: testUIDAttrName,
|
||||||
|
},
|
||||||
|
GroupSearch: upstreamldap.GroupSearchConfig{
|
||||||
|
Base: testGroupSearchBase,
|
||||||
|
Filter: testGroupSearchFilter,
|
||||||
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Ready",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundInRootDSECondition(1234),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: exampleDefaultNamingContext, GroupSearchBase: testGroupSearchBase}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the LDAP server connection was already validated using TLS, and the search base was found, load TLS and search base info into the cache",
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Generation = 1234
|
||||||
|
upstream.Status.Conditions = []v1alpha1.Condition{
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundInRootDSECondition(1234),
|
||||||
|
}
|
||||||
|
upstream.Spec.UserSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
|
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: exampleDefaultNamingContext, GroupSearchBase: testGroupSearchBase}},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
},
|
||||||
|
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||||
|
{
|
||||||
|
Name: testName,
|
||||||
|
Host: testHost,
|
||||||
|
ConnectionProtocol: upstreamldap.TLS,
|
||||||
|
CABundle: testCABundle,
|
||||||
|
BindUsername: testBindUsername,
|
||||||
|
BindPassword: testBindPassword,
|
||||||
|
UserSearch: upstreamldap.UserSearchConfig{
|
||||||
|
Base: exampleDefaultNamingContext,
|
||||||
|
Filter: testUserSearchFilter,
|
||||||
|
UsernameAttribute: testUsernameAttrName,
|
||||||
|
UIDAttribute: testUIDAttrName,
|
||||||
|
},
|
||||||
|
GroupSearch: upstreamldap.GroupSearchConfig{
|
||||||
|
Base: testGroupSearchBase,
|
||||||
|
Filter: testGroupSearchFilter,
|
||||||
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Ready",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundInRootDSECondition(1234),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: exampleDefaultNamingContext,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was already validated using StartTLS for the current resource generation and secret version, then do not validate it again and keep using StartTLS",
|
name: "when the LDAP server connection was already validated using StartTLS for the current resource generation and secret version, then do not validate it again and keep using StartTLS",
|
||||||
@ -765,7 +1039,12 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.StartTLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.StartTLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was validated for an older resource generation, then try to validate it again",
|
name: "when the LDAP server connection was validated for an older resource generation, then try to validate it again",
|
||||||
@ -775,8 +1054,13 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
activeDirectoryConnectionValidTrueCondition(1233, "4242"), // older spec generation!
|
activeDirectoryConnectionValidTrueCondition(1233, "4242"), // older spec generation!
|
||||||
}
|
}
|
||||||
})},
|
})},
|
||||||
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
setupMocks: func(conn *mockldapconn.MockConn) {
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
// Should perform a test dial and bind.
|
// Should perform a test dial and bind.
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
@ -790,7 +1074,12 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection validation previously failed for this resource generation, then try to validate it again",
|
name: "when the LDAP server connection validation previously failed for this resource generation, then try to validate it again",
|
||||||
@ -822,7 +1111,12 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was already validated for this resource generation but the bind secret has changed, then try to validate it again",
|
name: "when the LDAP server connection was already validated for this resource generation but the bind secret has changed, then try to validate it again",
|
||||||
@ -832,8 +1126,13 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
activeDirectoryConnectionValidTrueCondition(1234, "4241"), // same spec generation, old secret version
|
activeDirectoryConnectionValidTrueCondition(1234, "4241"), // same spec generation, old secret version
|
||||||
}
|
}
|
||||||
})},
|
})},
|
||||||
inputSecrets: []runtime.Object{validBindUserSecret("4242")}, // newer secret version!
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")}, // newer secret version!
|
||||||
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4241", LDAPConnectionProtocol: upstreamldap.TLS}}, // old version was validated
|
initialValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4241",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}, // old version was validated
|
||||||
setupMocks: func(conn *mockldapconn.MockConn) {
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
// Should perform a test dial and bind.
|
// Should perform a test dial and bind.
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
@ -847,7 +1146,12 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{
|
||||||
|
testName: {BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when the input activedirectoryidentityprovider leaves user attributes blank, provide default values",
|
name: "when the input activedirectoryidentityprovider leaves user attributes blank, provide default values",
|
||||||
@ -888,7 +1192,190 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the input activedirectoryidentityprovider leaves user and group search base blank, query for defaultNamingContext",
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Spec.UserSearch.Attributes = v1alpha1.ActiveDirectoryIdentityProviderUserSearchAttributes{}
|
||||||
|
upstream.Spec.UserSearch.Base = ""
|
||||||
|
upstream.Spec.GroupSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
// Should perform a test dial and bind.
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(2)
|
||||||
|
conn.EXPECT().Close().Times(2)
|
||||||
|
conn.EXPECT().Search(expectedDefaultNamingContextSearch()).Return(exampleDefaultNamingContextSearchResult, nil).Times(1)
|
||||||
|
},
|
||||||
|
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||||
|
{
|
||||||
|
Name: testName,
|
||||||
|
Host: testHost,
|
||||||
|
ConnectionProtocol: upstreamldap.TLS,
|
||||||
|
CABundle: testCABundle,
|
||||||
|
BindUsername: testBindUsername,
|
||||||
|
BindPassword: testBindPassword,
|
||||||
|
UserSearch: upstreamldap.UserSearchConfig{
|
||||||
|
Base: exampleDefaultNamingContext,
|
||||||
|
Filter: testUserSearchFilter,
|
||||||
|
UsernameAttribute: "sAMAccountName",
|
||||||
|
UIDAttribute: "objectGUID",
|
||||||
|
},
|
||||||
|
GroupSearch: upstreamldap.GroupSearchConfig{
|
||||||
|
Base: exampleDefaultNamingContext,
|
||||||
|
Filter: testGroupSearchFilter,
|
||||||
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Ready",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundInRootDSECondition(1234),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: exampleDefaultNamingContext, GroupSearchBase: exampleDefaultNamingContext}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the input activedirectoryidentityprovider leaves user search base blank but provides group search base, query for defaultNamingContext",
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Spec.UserSearch.Attributes = v1alpha1.ActiveDirectoryIdentityProviderUserSearchAttributes{}
|
||||||
|
upstream.Spec.UserSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
// Should perform a test dial and bind.
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(2)
|
||||||
|
conn.EXPECT().Close().Times(2)
|
||||||
|
conn.EXPECT().Search(expectedDefaultNamingContextSearch()).Return(exampleDefaultNamingContextSearchResult, nil).Times(1)
|
||||||
|
},
|
||||||
|
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||||
|
{
|
||||||
|
Name: testName,
|
||||||
|
Host: testHost,
|
||||||
|
ConnectionProtocol: upstreamldap.TLS,
|
||||||
|
CABundle: testCABundle,
|
||||||
|
BindUsername: testBindUsername,
|
||||||
|
BindPassword: testBindPassword,
|
||||||
|
UserSearch: upstreamldap.UserSearchConfig{
|
||||||
|
Base: exampleDefaultNamingContext,
|
||||||
|
Filter: testUserSearchFilter,
|
||||||
|
UsernameAttribute: "sAMAccountName",
|
||||||
|
UIDAttribute: "objectGUID",
|
||||||
|
},
|
||||||
|
GroupSearch: upstreamldap.GroupSearchConfig{
|
||||||
|
Base: testGroupSearchBase,
|
||||||
|
Filter: testGroupSearchFilter,
|
||||||
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Ready",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundInRootDSECondition(1234),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: exampleDefaultNamingContext, GroupSearchBase: testGroupSearchBase}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the input activedirectoryidentityprovider leaves group search base blank but provides user search base, query for defaultNamingContext",
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Spec.UserSearch.Attributes = v1alpha1.ActiveDirectoryIdentityProviderUserSearchAttributes{}
|
||||||
|
upstream.Spec.GroupSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
// Should perform a test dial and bind.
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(2)
|
||||||
|
conn.EXPECT().Close().Times(2)
|
||||||
|
conn.EXPECT().Search(expectedDefaultNamingContextSearch()).Return(exampleDefaultNamingContextSearchResult, nil).Times(1)
|
||||||
|
},
|
||||||
|
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||||
|
{
|
||||||
|
Name: testName,
|
||||||
|
Host: testHost,
|
||||||
|
ConnectionProtocol: upstreamldap.TLS,
|
||||||
|
CABundle: testCABundle,
|
||||||
|
BindUsername: testBindUsername,
|
||||||
|
BindPassword: testBindPassword,
|
||||||
|
UserSearch: upstreamldap.UserSearchConfig{
|
||||||
|
Base: testUserSearchBase,
|
||||||
|
Filter: testUserSearchFilter,
|
||||||
|
UsernameAttribute: "sAMAccountName",
|
||||||
|
UIDAttribute: "objectGUID",
|
||||||
|
},
|
||||||
|
GroupSearch: upstreamldap.GroupSearchConfig{
|
||||||
|
Base: exampleDefaultNamingContext,
|
||||||
|
Filter: testGroupSearchFilter,
|
||||||
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Ready",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundInRootDSECondition(1234),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS, UserSearchBase: testUserSearchBase, GroupSearchBase: exampleDefaultNamingContext}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the input activedirectoryidentityprovider leaves group search base blank and query for defaultNamingContext fails",
|
||||||
|
// TODO is this a fatal error? I think so because leaving the search base blank and trying anyway does not seem expected.
|
||||||
|
// it could potentially succeed but return something unexpected...
|
||||||
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
|
||||||
|
upstream.Spec.UserSearch.Attributes = v1alpha1.ActiveDirectoryIdentityProviderUserSearchAttributes{}
|
||||||
|
upstream.Spec.GroupSearch.Base = ""
|
||||||
|
})},
|
||||||
|
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
// Should perform a test dial and bind.
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(2)
|
||||||
|
conn.EXPECT().Close().Times(2)
|
||||||
|
conn.EXPECT().Search(expectedDefaultNamingContextSearch()).Return(nil, errors.New("some error")).Times(1)
|
||||||
|
},
|
||||||
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||||
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||||
|
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||||
|
Phase: "Error",
|
||||||
|
Conditions: []v1alpha1.Condition{
|
||||||
|
bindSecretValidTrueCondition(1234),
|
||||||
|
activeDirectoryConnectionValidTrueCondition(1234, "4242"),
|
||||||
|
searchBaseFoundErrorCondition(1234, "Error finding search base: error querying RootDSE for defaultNamingContext: some error"),
|
||||||
|
tlsConfigurationValidLoadedTrueCondition(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{
|
||||||
|
testName: {BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,12 @@ func (s *ldapUpstreamGenericLDAPSpec) GroupSearch() upstreamwatchers.UpstreamGen
|
|||||||
return &ldapUpstreamGenericLDAPGroupSearch{s.ldapIdentityProvider.Spec.GroupSearch}
|
return &ldapUpstreamGenericLDAPGroupSearch{s.ldapIdentityProvider.Spec.GroupSearch}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ldapUpstreamGenericLDAPSpec) DetectAndSetSearchBase(_ context.Context, config *upstreamldap.ProviderConfig) *v1alpha1.Condition {
|
||||||
|
config.GroupSearch.Base = s.ldapIdentityProvider.Spec.GroupSearch.Base
|
||||||
|
config.UserSearch.Base = s.ldapIdentityProvider.Spec.UserSearch.Base
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ldapUpstreamGenericLDAPUserSearch struct {
|
type ldapUpstreamGenericLDAPUserSearch struct {
|
||||||
userSearch v1alpha1.LDAPIdentityProviderUserSearch
|
userSearch v1alpha1.LDAPIdentityProviderUserSearch
|
||||||
}
|
}
|
||||||
|
@ -305,7 +305,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing secret",
|
name: "missing secret",
|
||||||
@ -488,8 +493,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
{
|
{
|
||||||
name: "when TLS connection fails it tries to use StartTLS instead: without a specified port it automatically switches ports",
|
name: "when TLS connection fails it tries to use StartTLS instead: without a specified port it automatically switches ports",
|
||||||
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
||||||
@ -546,8 +555,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.StartTLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.StartTLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
{
|
{
|
||||||
name: "when TLS connection fails it tries to use StartTLS instead: with a specified port it does not automatically switch ports",
|
name: "when TLS connection fails it tries to use StartTLS instead: with a specified port it does not automatically switch ports",
|
||||||
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
||||||
@ -603,6 +616,10 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-nil TLS configuration with empty CertificateAuthorityData is valid",
|
name: "non-nil TLS configuration with empty CertificateAuthorityData is valid",
|
||||||
@ -643,7 +660,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one valid upstream and one invalid upstream updates the cache to include only the valid upstream",
|
name: "one valid upstream and one invalid upstream updates the cache to include only the valid upstream",
|
||||||
@ -686,8 +708,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
{
|
{
|
||||||
name: "when testing the connection to the LDAP server fails then the upstream is still added to the cache anyway (treated like a warning)",
|
name: "when testing the connection to the LDAP server fails then the upstream is still added to the cache anyway (treated like a warning)",
|
||||||
inputUpstreams: []runtime.Object{validUpstream},
|
inputUpstreams: []runtime.Object{validUpstream},
|
||||||
@ -720,6 +746,10 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was already validated using TLS for the current resource generation and secret version, then do not validate it again and keep using TLS",
|
name: "when the LDAP server connection was already validated using TLS for the current resource generation and secret version, then do not validate it again and keep using TLS",
|
||||||
@ -742,8 +772,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was already validated using StartTLS for the current resource generation and secret version, then do not validate it again and keep using StartTLS",
|
name: "when the LDAP server connection was already validated using StartTLS for the current resource generation and secret version, then do not validate it again and keep using StartTLS",
|
||||||
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
||||||
@ -765,8 +799,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.StartTLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.StartTLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was validated for an older resource generation, then try to validate it again",
|
name: "when the LDAP server connection was validated for an older resource generation, then try to validate it again",
|
||||||
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
||||||
@ -790,8 +828,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection validation previously failed for this resource generation, then try to validate it again",
|
name: "when the LDAP server connection validation previously failed for this resource generation, then try to validate it again",
|
||||||
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
||||||
@ -822,8 +864,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
{
|
{
|
||||||
name: "when the LDAP server connection was already validated for this resource generation but the bind secret has changed, then try to validate it again",
|
name: "when the LDAP server connection was already validated for this resource generation but the bind secret has changed, then try to validate it again",
|
||||||
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
|
||||||
@ -847,8 +893,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Conditions: allConditionsTrue(1234, "4242"),
|
Conditions: allConditionsTrue(1234, "4242"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {BindSecretResourceVersion: "4242", LDAPConnectionProtocol: upstreamldap.TLS}},
|
wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: {
|
||||||
},
|
BindSecretResourceVersion: "4242",
|
||||||
|
LDAPConnectionProtocol: upstreamldap.TLS,
|
||||||
|
UserSearchBase: testUserSearchBase,
|
||||||
|
GroupSearchBase: testGroupSearchBase,
|
||||||
|
}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -36,13 +36,14 @@ const (
|
|||||||
typeBindSecretValid = "BindSecretValid"
|
typeBindSecretValid = "BindSecretValid"
|
||||||
typeTLSConfigurationValid = "TLSConfigurationValid"
|
typeTLSConfigurationValid = "TLSConfigurationValid"
|
||||||
typeLDAPConnectionValid = "LDAPConnectionValid"
|
typeLDAPConnectionValid = "LDAPConnectionValid"
|
||||||
|
TypeSearchBaseFound = "SearchBaseFound"
|
||||||
reasonLDAPConnectionError = "LDAPConnectionError"
|
reasonLDAPConnectionError = "LDAPConnectionError"
|
||||||
noTLSConfigurationMessage = "no TLS configuration provided"
|
noTLSConfigurationMessage = "no TLS configuration provided"
|
||||||
loadedTLSConfigurationMessage = "loaded TLS configuration"
|
loadedTLSConfigurationMessage = "loaded TLS configuration"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An in-memory cache with an entry for each ActiveDirectoryIdentityProvider, to keep track of which ResourceVersion
|
// An in-memory cache with an entry for each ActiveDirectoryIdentityProvider, to keep track of which ResourceVersion
|
||||||
// of the bind Secret and which TLS/StartTLS setting was used during the most recent successful validation.
|
// of the bind Secret, which TLS/StartTLS setting was used and which search base was found during the most recent successful validation.
|
||||||
type SecretVersionCache struct {
|
type SecretVersionCache struct {
|
||||||
ValidatedSettingsByName map[string]ValidatedSettings
|
ValidatedSettingsByName map[string]ValidatedSettings
|
||||||
}
|
}
|
||||||
@ -50,6 +51,8 @@ type SecretVersionCache struct {
|
|||||||
type ValidatedSettings struct {
|
type ValidatedSettings struct {
|
||||||
BindSecretResourceVersion string
|
BindSecretResourceVersion string
|
||||||
LDAPConnectionProtocol upstreamldap.LDAPConnectionProtocol
|
LDAPConnectionProtocol upstreamldap.LDAPConnectionProtocol
|
||||||
|
UserSearchBase string
|
||||||
|
GroupSearchBase string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSecretVersionCache() *SecretVersionCache {
|
func NewSecretVersionCache() *SecretVersionCache {
|
||||||
@ -71,6 +74,7 @@ type UpstreamGenericLDAPSpec interface {
|
|||||||
BindSecretName() string
|
BindSecretName() string
|
||||||
UserSearch() UpstreamGenericLDAPUserSearch
|
UserSearch() UpstreamGenericLDAPUserSearch
|
||||||
GroupSearch() UpstreamGenericLDAPGroupSearch
|
GroupSearch() UpstreamGenericLDAPGroupSearch
|
||||||
|
DetectAndSetSearchBase(ctx context.Context, config *upstreamldap.ProviderConfig) *v1alpha1.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpstreamGenericLDAPUserSearch interface {
|
type UpstreamGenericLDAPUserSearch interface {
|
||||||
@ -161,7 +165,7 @@ func TestConnection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HasPreviousSuccessfulConditionForCurrentSpecGenerationAndSecretVersion(secretVersionCache *SecretVersionCache, currentGeneration int64, upstreamStatusConditions []v1alpha1.Condition, upstreamName string, currentSecretVersion string, config *upstreamldap.ProviderConfig) bool {
|
func HasPreviousSuccessfulTLSConnectionConditionForCurrentSpecGenerationAndSecretVersion(secretVersionCache *SecretVersionCache, currentGeneration int64, upstreamStatusConditions []v1alpha1.Condition, upstreamName string, currentSecretVersion string, config *upstreamldap.ProviderConfig) bool {
|
||||||
for _, cond := range upstreamStatusConditions {
|
for _, cond := range upstreamStatusConditions {
|
||||||
if cond.Type == typeLDAPConnectionValid && cond.Status == v1alpha1.ConditionTrue && cond.ObservedGeneration == currentGeneration {
|
if cond.Type == typeLDAPConnectionValid && cond.Status == v1alpha1.ConditionTrue && cond.ObservedGeneration == currentGeneration {
|
||||||
// Found a previously successful condition for the current spec generation.
|
// Found a previously successful condition for the current spec generation.
|
||||||
@ -177,6 +181,21 @@ func HasPreviousSuccessfulConditionForCurrentSpecGenerationAndSecretVersion(secr
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HasPreviousSuccessfulSearchBaseConditionForCurrentGeneration(secretVersionCache *SecretVersionCache, currentGeneration int64, upstreamStatusConditions []v1alpha1.Condition, upstreamName string, currentSecretVersion string, config *upstreamldap.ProviderConfig) bool {
|
||||||
|
for _, cond := range upstreamStatusConditions {
|
||||||
|
if cond.Type == TypeSearchBaseFound && cond.Status == v1alpha1.ConditionTrue && cond.ObservedGeneration == currentGeneration {
|
||||||
|
// Found a previously successful condition for the current spec generation.
|
||||||
|
// Now figure out which version of the bind Secret was used during that previous validation, if any.
|
||||||
|
validatedSettings := secretVersionCache.ValidatedSettingsByName[upstreamName]
|
||||||
|
// Reload the TLS vs StartTLS setting that was previously validated.
|
||||||
|
config.UserSearch.Base = validatedSettings.UserSearchBase
|
||||||
|
config.GroupSearch.Base = validatedSettings.GroupSearchBase
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func validTLSCondition(message string) *v1alpha1.Condition {
|
func validTLSCondition(message string) *v1alpha1.Condition {
|
||||||
return &v1alpha1.Condition{
|
return &v1alpha1.Condition{
|
||||||
Type: typeTLSConfigurationValid,
|
Type: typeTLSConfigurationValid,
|
||||||
@ -267,38 +286,47 @@ func ValidateGenericLDAP(ctx context.Context, upstream UpstreamGenericLDAPIDP, s
|
|||||||
|
|
||||||
// No point in trying to connect to the server if the config was already determined to be invalid.
|
// No point in trying to connect to the server if the config was already determined to be invalid.
|
||||||
var ldapConnectionValidCondition *v1alpha1.Condition
|
var ldapConnectionValidCondition *v1alpha1.Condition
|
||||||
|
var searchBaseFoundCondition *v1alpha1.Condition
|
||||||
if secretValidCondition.Status == v1alpha1.ConditionTrue && tlsValidCondition.Status == v1alpha1.ConditionTrue {
|
if secretValidCondition.Status == v1alpha1.ConditionTrue && tlsValidCondition.Status == v1alpha1.ConditionTrue {
|
||||||
ldapConnectionValidCondition = validateAndSetLDAPServerConnectivity(ctx, validatedSecretVersionsCache, upstream, config, currentSecretVersion)
|
ldapConnectionValidCondition, searchBaseFoundCondition = validateAndSetLDAPServerConnectivity(ctx, validatedSecretVersionsCache, upstream, config, currentSecretVersion)
|
||||||
if ldapConnectionValidCondition != nil {
|
if ldapConnectionValidCondition != nil {
|
||||||
conditions.Append(ldapConnectionValidCondition, false)
|
conditions.Append(ldapConnectionValidCondition, false)
|
||||||
}
|
}
|
||||||
|
if searchBaseFoundCondition != nil {
|
||||||
|
conditions.Append(searchBaseFoundCondition, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return conditions
|
return conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAndSetLDAPServerConnectivity(ctx context.Context, validatedSecretVersionsCache *SecretVersionCache, upstream UpstreamGenericLDAPIDP, config *upstreamldap.ProviderConfig, currentSecretVersion string) *v1alpha1.Condition {
|
func validateAndSetLDAPServerConnectivity(ctx context.Context, validatedSecretVersionsCache *SecretVersionCache, upstream UpstreamGenericLDAPIDP, config *upstreamldap.ProviderConfig, currentSecretVersion string) (*v1alpha1.Condition, *v1alpha1.Condition) {
|
||||||
// TODO refactor validateAndSetLDAPServerConnectivity to be shared and take a helper function for the defaultNamingContext stuff
|
var ldapConnectionValidCondition *v1alpha1.Condition
|
||||||
// so that can be shared.
|
if !HasPreviousSuccessfulTLSConnectionConditionForCurrentSpecGenerationAndSecretVersion(validatedSecretVersionsCache, upstream.Generation(), upstream.Status().Conditions(), upstream.Name(), currentSecretVersion, config) {
|
||||||
if HasPreviousSuccessfulConditionForCurrentSpecGenerationAndSecretVersion(validatedSecretVersionsCache, upstream.Generation(), upstream.Status().Conditions(), upstream.Name(), currentSecretVersion, config) {
|
testConnectionTimeout, cancelFunc := context.WithTimeout(ctx, TestLDAPConnectionTimeout)
|
||||||
return nil
|
defer cancelFunc()
|
||||||
}
|
|
||||||
|
|
||||||
testConnectionTimeout, cancelFunc := context.WithTimeout(ctx, TestLDAPConnectionTimeout)
|
ldapConnectionValidCondition = TestConnection(testConnectionTimeout, upstream.Spec().BindSecretName(), config, currentSecretVersion)
|
||||||
defer cancelFunc()
|
|
||||||
|
|
||||||
condition := TestConnection(testConnectionTimeout, upstream.Spec().BindSecretName(), config, currentSecretVersion)
|
if ldapConnectionValidCondition.Status == v1alpha1.ConditionTrue {
|
||||||
|
// Remember (in-memory for this pod) that the controller has successfully validated the LDAP provider
|
||||||
if condition.Status == v1alpha1.ConditionTrue {
|
// using this version of the Secret. This is for performance reasons, to avoid attempting to connect to
|
||||||
// Remember (in-memory for this pod) that the controller has successfully validated the LDAP provider
|
// the LDAP server more than is needed. If the pod restarts, it will attempt this validation again.
|
||||||
// using this version of the Secret. This is for performance reasons, to avoid attempting to connect to
|
validatedSecretVersionsCache.ValidatedSettingsByName[upstream.Name()] = ValidatedSettings{
|
||||||
// the LDAP server more than is needed. If the pod restarts, it will attempt this validation again.
|
BindSecretResourceVersion: currentSecretVersion,
|
||||||
validatedSecretVersionsCache.ValidatedSettingsByName[upstream.Name()] = ValidatedSettings{
|
LDAPConnectionProtocol: config.ConnectionProtocol,
|
||||||
BindSecretResourceVersion: currentSecretVersion,
|
}
|
||||||
LDAPConnectionProtocol: config.ConnectionProtocol,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var searchBaseFoundCondition *v1alpha1.Condition
|
||||||
|
if !HasPreviousSuccessfulSearchBaseConditionForCurrentGeneration(validatedSecretVersionsCache, upstream.Generation(), upstream.Status().Conditions(), upstream.Name(), currentSecretVersion, config) {
|
||||||
|
searchBaseFoundCondition = upstream.Spec().DetectAndSetSearchBase(ctx, config)
|
||||||
|
validatedSettings := validatedSecretVersionsCache.ValidatedSettingsByName[upstream.Name()]
|
||||||
|
validatedSettings.GroupSearchBase = config.GroupSearch.Base
|
||||||
|
validatedSettings.UserSearchBase = config.UserSearch.Base
|
||||||
|
validatedSecretVersionsCache.ValidatedSettingsByName[upstream.Name()] = validatedSettings
|
||||||
|
}
|
||||||
|
|
||||||
return condition
|
return ldapConnectionValidCondition, searchBaseFoundCondition
|
||||||
}
|
}
|
||||||
|
|
||||||
func EvaluateConditions(conditions GradatedConditions, config *upstreamldap.ProviderConfig) (provider.UpstreamLDAPIdentityProviderI, bool) {
|
func EvaluateConditions(conditions GradatedConditions, config *upstreamldap.ProviderConfig) (provider.UpstreamLDAPIdentityProviderI, bool) {
|
||||||
|
@ -392,6 +392,35 @@ func (p *Provider) validateConfig() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) SearchForDefaultNamingContext(ctx context.Context) (string, error) {
|
||||||
|
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
|
||||||
|
|
||||||
|
conn, err := p.dial(ctx)
|
||||||
|
if err != nil {
|
||||||
|
p.traceAuthFailure(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.traceAuthFailure(t, err)
|
||||||
|
return "", fmt.Errorf(`error binding as "%s" before user search: %w`, p.c.BindUsername, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResult, err := conn.Search(p.defaultNamingContextRequest())
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf(`error querying RootDSE for defaultNamingContext: %w`, err)
|
||||||
|
}
|
||||||
|
// TODO handle getting empty entry back-- I think this is possible but we might want to
|
||||||
|
// treat it as an error
|
||||||
|
// TODO handle getting no entries back
|
||||||
|
// TODO handle getting more than 1 result back
|
||||||
|
// TODO handle getting no values for defaultNamingContext attribute back in entry
|
||||||
|
return searchResult.Entries[0].GetAttributeValue("defaultNamingContext"), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(conn Conn, foundUserDN string) error) (string, string, []string, error) {
|
func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(conn Conn, foundUserDN string) error) (string, string, []string, error) {
|
||||||
searchResult, err := conn.Search(p.userSearchRequest(username))
|
searchResult, err := conn.Search(p.userSearchRequest(username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -462,6 +491,20 @@ func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(c
|
|||||||
return mappedUsername, mappedUID, mappedGroupNames, nil
|
return mappedUsername, mappedUID, mappedGroupNames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) defaultNamingContextRequest() *ldap.SearchRequest {
|
||||||
|
return &ldap.SearchRequest{
|
||||||
|
BaseDN: "",
|
||||||
|
Scope: ldap.ScopeBaseObject,
|
||||||
|
DerefAliases: ldap.NeverDerefAliases,
|
||||||
|
SizeLimit: 2,
|
||||||
|
TimeLimit: 90,
|
||||||
|
TypesOnly: false,
|
||||||
|
Filter: "(objectClass=*)",
|
||||||
|
Attributes: []string{"defaultNamingContext"},
|
||||||
|
Controls: nil, // don't need paging because we set the SizeLimit so small
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) userSearchRequest(username string) *ldap.SearchRequest {
|
func (p *Provider) userSearchRequest(username string) *ldap.SearchRequest {
|
||||||
// See https://ldap.com/the-ldap-search-operation for general documentation of LDAP search options.
|
// See https://ldap.com/the-ldap-search-operation for general documentation of LDAP search options.
|
||||||
return &ldap.SearchRequest{
|
return &ldap.SearchRequest{
|
||||||
|
@ -254,12 +254,6 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
TLS: &idpv1alpha1.TLSSpec{
|
TLS: &idpv1alpha1.TLSSpec{
|
||||||
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamActiveDirectory.CABundle)),
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamActiveDirectory.CABundle)),
|
||||||
},
|
},
|
||||||
UserSearch: idpv1alpha1.ActiveDirectoryIdentityProviderUserSearch{
|
|
||||||
Base: "dc=activedirectory,dc=test,dc=pinniped,dc=dev",
|
|
||||||
},
|
|
||||||
GroupSearch: idpv1alpha1.ActiveDirectoryIdentityProviderGroupSearch{
|
|
||||||
Base: "dc=activedirectory,dc=test,dc=pinniped,dc=dev",
|
|
||||||
},
|
|
||||||
Bind: idpv1alpha1.ActiveDirectoryIdentityProviderBind{
|
Bind: idpv1alpha1.ActiveDirectoryIdentityProviderBind{
|
||||||
SecretName: secret.Name,
|
SecretName: secret.Name,
|
||||||
},
|
},
|
||||||
@ -269,7 +263,7 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
env.SupervisorUpstreamActiveDirectory.Host, env.SupervisorUpstreamActiveDirectory.BindUsername,
|
env.SupervisorUpstreamActiveDirectory.Host, env.SupervisorUpstreamActiveDirectory.BindUsername,
|
||||||
secret.Name, secret.ResourceVersion,
|
secret.Name, secret.ResourceVersion,
|
||||||
)
|
)
|
||||||
requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg) // TODO refactor to be same as LDAP func
|
requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg)
|
||||||
},
|
},
|
||||||
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
|
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
|
||||||
requestAuthorizationUsingLDAPIdentityProvider(t,
|
requestAuthorizationUsingLDAPIdentityProvider(t,
|
||||||
@ -282,7 +276,7 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
|
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
|
||||||
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
|
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
|
||||||
"ldaps://" + env.SupervisorUpstreamActiveDirectory.Host +
|
"ldaps://" + env.SupervisorUpstreamActiveDirectory.Host +
|
||||||
"?base=" + url.QueryEscape("dc=activedirectory,dc=test,dc=pinniped,dc=dev") +
|
"?base=" + url.QueryEscape("DC=activedirectory,DC=test,DC=pinniped,DC=dev") +
|
||||||
"&sub=" + env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue,
|
"&sub=" + env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue,
|
||||||
),
|
),
|
||||||
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
||||||
@ -331,7 +325,7 @@ func requireSuccessfulLDAPIdentityProviderConditions(t *testing.T, ldapIDP *idpv
|
|||||||
}, conditionsSummary)
|
}, conditionsSummary)
|
||||||
}
|
}
|
||||||
func requireSuccessfulActiveDirectoryIdentityProviderConditions(t *testing.T, adIDP *idpv1alpha1.ActiveDirectoryIdentityProvider, expectedActiveDirectoryConnectionValidMessage string) {
|
func requireSuccessfulActiveDirectoryIdentityProviderConditions(t *testing.T, adIDP *idpv1alpha1.ActiveDirectoryIdentityProvider, expectedActiveDirectoryConnectionValidMessage string) {
|
||||||
require.Len(t, adIDP.Status.Conditions, 3)
|
require.Len(t, adIDP.Status.Conditions, 4)
|
||||||
|
|
||||||
conditionsSummary := [][]string{}
|
conditionsSummary := [][]string{}
|
||||||
for _, condition := range adIDP.Status.Conditions {
|
for _, condition := range adIDP.Status.Conditions {
|
||||||
@ -352,6 +346,7 @@ func requireSuccessfulActiveDirectoryIdentityProviderConditions(t *testing.T, ad
|
|||||||
{"BindSecretValid", "True", "Success"},
|
{"BindSecretValid", "True", "Success"},
|
||||||
{"TLSConfigurationValid", "True", "Success"},
|
{"TLSConfigurationValid", "True", "Success"},
|
||||||
{"LDAPConnectionValid", "True", "Success"},
|
{"LDAPConnectionValid", "True", "Success"},
|
||||||
|
{"SearchBaseFound", "True", "Success"},
|
||||||
}, conditionsSummary)
|
}, conditionsSummary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user