From fdac4d16f0721f324cc9db7a5982a79e0dacac01 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Tue, 1 Feb 2022 08:31:29 -0800 Subject: [PATCH] Only run group refresh when the skipGroupRefresh boolean isn't set for AD and LDAP --- ...es_activedirectoryidentityprovider.go.tmpl | 7 +- .../types_ldapidentityprovider.go.tmpl | 7 +- ....dev_activedirectoryidentityproviders.yaml | 8 +++ ...or.pinniped.dev_ldapidentityproviders.yaml | 8 +++ generated/1.17/README.adoc | 2 + .../types_activedirectoryidentityprovider.go | 7 +- .../v1alpha1/types_ldapidentityprovider.go | 7 +- ....dev_activedirectoryidentityproviders.yaml | 8 +++ ...or.pinniped.dev_ldapidentityproviders.yaml | 8 +++ generated/1.18/README.adoc | 2 + .../types_activedirectoryidentityprovider.go | 7 +- .../v1alpha1/types_ldapidentityprovider.go | 7 +- ....dev_activedirectoryidentityproviders.yaml | 8 +++ ...or.pinniped.dev_ldapidentityproviders.yaml | 8 +++ generated/1.19/README.adoc | 2 + .../types_activedirectoryidentityprovider.go | 7 +- .../v1alpha1/types_ldapidentityprovider.go | 7 +- ....dev_activedirectoryidentityproviders.yaml | 8 +++ ...or.pinniped.dev_ldapidentityproviders.yaml | 8 +++ generated/1.20/README.adoc | 2 + .../types_activedirectoryidentityprovider.go | 7 +- .../v1alpha1/types_ldapidentityprovider.go | 7 +- ....dev_activedirectoryidentityproviders.yaml | 8 +++ ...or.pinniped.dev_ldapidentityproviders.yaml | 8 +++ .../types_activedirectoryidentityprovider.go | 7 +- .../v1alpha1/types_ldapidentityprovider.go | 7 +- .../active_directory_upstream_watcher.go | 1 + .../active_directory_upstream_watcher_test.go | 70 +++++++++++++++++++ .../ldap_upstream_watcher.go | 1 + .../ldap_upstream_watcher_test.go | 62 ++++++++++++++++ .../provider/dynamic_upstream_idp_provider.go | 1 + internal/oidc/token/token_handler.go | 32 ++++++++- internal/upstreamldap/upstreamldap.go | 9 +++ internal/upstreamldap/upstreamldap_test.go | 31 ++++++++ 34 files changed, 366 insertions(+), 13 deletions(-) diff --git a/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go.tmpl b/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go.tmpl index d4e481b9..2994b491 100644 --- a/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go.tmpl +++ b/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go.tmpl @@ -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 package v1alpha1 @@ -131,6 +131,11 @@ type ActiveDirectoryIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an ActiveDirectory identity provider. diff --git a/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go.tmpl b/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go.tmpl index 74570a92..efbb14ae 100644 --- a/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go.tmpl +++ b/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go.tmpl @@ -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 package v1alpha1 @@ -119,6 +119,11 @@ type LDAPIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an LDAP identity provider. diff --git a/deploy/supervisor/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml b/deploy/supervisor/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml index 9929b2fc..3cc4ead0 100644 --- a/deploy/supervisor/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml +++ b/deploy/supervisor/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml @@ -119,6 +119,14 @@ spec: search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the AD server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this Active Directory identity diff --git a/deploy/supervisor/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml b/deploy/supervisor/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml index ed9780b5..617b23bf 100644 --- a/deploy/supervisor/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml +++ b/deploy/supervisor/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml @@ -111,6 +111,14 @@ spec: an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the LDAP server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this LDAP identity provider, diff --git a/generated/1.17/README.adoc b/generated/1.17/README.adoc index d18e7610..cd97eb52 100644 --- a/generated/1.17/README.adoc +++ b/generated/1.17/README.adoc @@ -801,6 +801,7 @@ ActiveDirectoryIdentityProvider describes the configuration of an upstream Micro | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for groups. It may make sense to specify a subtree as a search base if you wish to exclude some groups for security reasons or to make searches faster. | *`filter`* __string__ | Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". This searches nested groups by default. Note that nested group search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes[$$ActiveDirectoryIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD server. |=== @@ -988,6 +989,7 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and authenticated users will not belong to any groups from the LDAP provider. Also, when not specified, the values of Filter and Attributes are ignored. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes[$$LDAPIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each LDAP entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP server. |=== diff --git a/generated/1.17/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go b/generated/1.17/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go index d4e481b9..2994b491 100644 --- a/generated/1.17/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go +++ b/generated/1.17/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go @@ -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 package v1alpha1 @@ -131,6 +131,11 @@ type ActiveDirectoryIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an ActiveDirectory identity provider. diff --git a/generated/1.17/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go b/generated/1.17/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go index 74570a92..efbb14ae 100644 --- a/generated/1.17/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go +++ b/generated/1.17/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go @@ -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 package v1alpha1 @@ -119,6 +119,11 @@ type LDAPIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an LDAP identity provider. diff --git a/generated/1.17/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml b/generated/1.17/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml index 9929b2fc..3cc4ead0 100644 --- a/generated/1.17/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml +++ b/generated/1.17/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml @@ -119,6 +119,14 @@ spec: search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the AD server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this Active Directory identity diff --git a/generated/1.17/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml b/generated/1.17/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml index ed9780b5..617b23bf 100644 --- a/generated/1.17/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml +++ b/generated/1.17/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml @@ -111,6 +111,14 @@ spec: an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the LDAP server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this LDAP identity provider, diff --git a/generated/1.18/README.adoc b/generated/1.18/README.adoc index 417b57ac..cb9a4738 100644 --- a/generated/1.18/README.adoc +++ b/generated/1.18/README.adoc @@ -801,6 +801,7 @@ ActiveDirectoryIdentityProvider describes the configuration of an upstream Micro | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for groups. It may make sense to specify a subtree as a search base if you wish to exclude some groups for security reasons or to make searches faster. | *`filter`* __string__ | Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". This searches nested groups by default. Note that nested group search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes[$$ActiveDirectoryIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD server. |=== @@ -988,6 +989,7 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and authenticated users will not belong to any groups from the LDAP provider. Also, when not specified, the values of Filter and Attributes are ignored. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes[$$LDAPIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each LDAP entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP server. |=== diff --git a/generated/1.18/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go b/generated/1.18/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go index d4e481b9..2994b491 100644 --- a/generated/1.18/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go +++ b/generated/1.18/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go @@ -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 package v1alpha1 @@ -131,6 +131,11 @@ type ActiveDirectoryIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an ActiveDirectory identity provider. diff --git a/generated/1.18/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go b/generated/1.18/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go index 74570a92..efbb14ae 100644 --- a/generated/1.18/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go +++ b/generated/1.18/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go @@ -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 package v1alpha1 @@ -119,6 +119,11 @@ type LDAPIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an LDAP identity provider. diff --git a/generated/1.18/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml b/generated/1.18/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml index 9929b2fc..3cc4ead0 100644 --- a/generated/1.18/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml +++ b/generated/1.18/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml @@ -119,6 +119,14 @@ spec: search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the AD server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this Active Directory identity diff --git a/generated/1.18/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml b/generated/1.18/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml index ed9780b5..617b23bf 100644 --- a/generated/1.18/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml +++ b/generated/1.18/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml @@ -111,6 +111,14 @@ spec: an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the LDAP server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this LDAP identity provider, diff --git a/generated/1.19/README.adoc b/generated/1.19/README.adoc index 0317faed..3a4a0b36 100644 --- a/generated/1.19/README.adoc +++ b/generated/1.19/README.adoc @@ -801,6 +801,7 @@ ActiveDirectoryIdentityProvider describes the configuration of an upstream Micro | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for groups. It may make sense to specify a subtree as a search base if you wish to exclude some groups for security reasons or to make searches faster. | *`filter`* __string__ | Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". This searches nested groups by default. Note that nested group search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes[$$ActiveDirectoryIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD server. |=== @@ -988,6 +989,7 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and authenticated users will not belong to any groups from the LDAP provider. Also, when not specified, the values of Filter and Attributes are ignored. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes[$$LDAPIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each LDAP entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP server. |=== diff --git a/generated/1.19/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go b/generated/1.19/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go index d4e481b9..2994b491 100644 --- a/generated/1.19/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go +++ b/generated/1.19/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go @@ -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 package v1alpha1 @@ -131,6 +131,11 @@ type ActiveDirectoryIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an ActiveDirectory identity provider. diff --git a/generated/1.19/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go b/generated/1.19/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go index 74570a92..efbb14ae 100644 --- a/generated/1.19/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go +++ b/generated/1.19/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go @@ -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 package v1alpha1 @@ -119,6 +119,11 @@ type LDAPIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an LDAP identity provider. diff --git a/generated/1.19/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml b/generated/1.19/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml index 9929b2fc..3cc4ead0 100644 --- a/generated/1.19/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml +++ b/generated/1.19/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml @@ -119,6 +119,14 @@ spec: search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the AD server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this Active Directory identity diff --git a/generated/1.19/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml b/generated/1.19/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml index ed9780b5..617b23bf 100644 --- a/generated/1.19/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml +++ b/generated/1.19/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml @@ -111,6 +111,14 @@ spec: an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the LDAP server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this LDAP identity provider, diff --git a/generated/1.20/README.adoc b/generated/1.20/README.adoc index faf7ad54..4b7ce7e1 100644 --- a/generated/1.20/README.adoc +++ b/generated/1.20/README.adoc @@ -801,6 +801,7 @@ ActiveDirectoryIdentityProvider describes the configuration of an upstream Micro | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for groups. It may make sense to specify a subtree as a search base if you wish to exclude some groups for security reasons or to make searches faster. | *`filter`* __string__ | Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". This searches nested groups by default. Note that nested group search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes[$$ActiveDirectoryIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD server. |=== @@ -988,6 +989,7 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and authenticated users will not belong to any groups from the LDAP provider. Also, when not specified, the values of Filter and Attributes are ignored. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes[$$LDAPIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each LDAP entry which was found as the result of the group search. +| *`skipGroupRefresh`* __boolean__ | SkipGroupRefresh skips the group refresh operation that occurs with each refresh (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP server. |=== diff --git a/generated/1.20/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go b/generated/1.20/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go index d4e481b9..2994b491 100644 --- a/generated/1.20/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go +++ b/generated/1.20/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go @@ -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 package v1alpha1 @@ -131,6 +131,11 @@ type ActiveDirectoryIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an ActiveDirectory identity provider. diff --git a/generated/1.20/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go b/generated/1.20/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go index 74570a92..efbb14ae 100644 --- a/generated/1.20/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go +++ b/generated/1.20/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go @@ -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 package v1alpha1 @@ -119,6 +119,11 @@ type LDAPIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an LDAP identity provider. diff --git a/generated/1.20/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml b/generated/1.20/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml index 9929b2fc..3cc4ead0 100644 --- a/generated/1.20/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml +++ b/generated/1.20/crds/idp.supervisor.pinniped.dev_activedirectoryidentityproviders.yaml @@ -119,6 +119,14 @@ spec: search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})" type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the AD server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this Active Directory identity diff --git a/generated/1.20/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml b/generated/1.20/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml index ed9780b5..617b23bf 100644 --- a/generated/1.20/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml +++ b/generated/1.20/crds/idp.supervisor.pinniped.dev_ldapidentityproviders.yaml @@ -111,6 +111,14 @@ spec: an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}". type: string + skipGroupRefresh: + description: SkipGroupRefresh skips the group refresh operation + that occurs with each refresh (every 5 minutes). This can be + done if group search is very slow or resource intensive for + the LDAP server. + type: boolean + required: + - skipGroupRefresh type: object host: description: 'Host is the hostname of this LDAP identity provider, diff --git a/generated/latest/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go b/generated/latest/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go index d4e481b9..2994b491 100644 --- a/generated/latest/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go +++ b/generated/latest/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go @@ -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 package v1alpha1 @@ -131,6 +131,11 @@ type ActiveDirectoryIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the AD + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an ActiveDirectory identity provider. diff --git a/generated/latest/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go b/generated/latest/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go index 74570a92..efbb14ae 100644 --- a/generated/latest/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go +++ b/generated/latest/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go @@ -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 package v1alpha1 @@ -119,6 +119,11 @@ type LDAPIdentityProviderGroupSearch struct { // the result of the group search. // +optional Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"` + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP + // server. + SkipGroupRefresh bool `json:"skipGroupRefresh"` } // Spec for configuring an LDAP identity provider. diff --git a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go index 33d1557a..4fb699b7 100644 --- a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go +++ b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher.go @@ -333,6 +333,7 @@ func (c *activeDirectoryWatcherController) validateUpstream(ctx context.Context, Base: spec.GroupSearch.Base, Filter: adUpstreamImpl.Spec().GroupSearch().Filter(), GroupNameAttribute: adUpstreamImpl.Spec().GroupSearch().GroupNameAttribute(), + SkipGroupRefresh: spec.GroupSearch.SkipGroupRefresh, }, Dialer: c.ldapDialer, UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){ diff --git a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go index 7777bfec..ce69b4af 100644 --- a/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go +++ b/internal/controller/supervisorconfig/activedirectoryupstreamwatcher/active_directory_upstream_watcher_test.go @@ -192,6 +192,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) { Attributes: v1alpha1.ActiveDirectoryIdentityProviderGroupSearchAttributes{ GroupName: testGroupNameAttrName, }, + SkipGroupRefresh: false, }, }, } @@ -1907,6 +1908,75 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) { SearchBaseFoundCondition: condPtr(withoutTime(searchBaseFoundInRootDSECondition(0))), }}, }, + { + name: "skipping group refresh is valid", + inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) { + upstream.Spec.GroupSearch.SkipGroupRefresh = true + })}, + inputSecrets: []runtime.Object{validBindUserSecret("4242")}, + setupMocks: func(conn *mockldapconn.MockConn) { + // Should perform a test dial and bind. + conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) + conn.EXPECT().Close().Times(1) + }, + wantResultingCache: []*upstreamldap.ProviderConfig{ + { + Name: testName, + ResourceUID: testResourceUID, + Host: testHost, + ConnectionProtocol: upstreamldap.TLS, + CABundle: testCABundle, + BindUsername: testBindUsername, + BindPassword: testBindPassword, + UserSearch: upstreamldap.UserSearchConfig{ + Base: testUserSearchBase, + Filter: testUserSearchFilter, + UsernameAttribute: testUsernameAttrName, + UIDAttribute: testUIDAttrName, + }, + GroupSearch: upstreamldap.GroupSearchConfig{ + Base: testGroupSearchBase, + Filter: testGroupSearchFilter, + GroupNameAttribute: testGroupNameAttrName, + SkipGroupRefresh: true, + }, + UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": microsoftUUIDFromBinaryAttr("objectGUID")}, + RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ + "pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"), + "userAccountControl": validUserAccountControl, + "msDS-User-Account-Control-Computed": validComputedUserAccountControl, + }, + }, + }, + wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID}, + Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{ + Phase: "Ready", + Conditions: []v1alpha1.Condition{ + bindSecretValidTrueCondition(1234), + activeDirectoryConnectionValidTrueCondition(1234, "4242"), + searchBaseFoundInConfigCondition(1234), + { + Type: "TLSConfigurationValid", + Status: "True", + LastTransitionTime: now, + Reason: "Success", + Message: "loaded TLS configuration", + ObservedGeneration: 1234, + }, + }, + }, + }}, + wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: { + BindSecretResourceVersion: "4242", + LDAPConnectionProtocol: upstreamldap.TLS, + UserSearchBase: testUserSearchBase, + GroupSearchBase: testGroupSearchBase, + IDPSpecGeneration: 1234, + ConnectionValidCondition: condPtr(activeDirectoryConnectionValidTrueConditionWithoutTimeOrGeneration("4242")), + SearchBaseFoundCondition: condPtr(withoutTime(searchBaseFoundInConfigCondition(0))), + }}, + }, } for _, tt := range tests { diff --git a/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher.go b/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher.go index 82a9fdec..fd57cc28 100644 --- a/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher.go +++ b/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher.go @@ -239,6 +239,7 @@ func (c *ldapWatcherController) validateUpstream(ctx context.Context, upstream * Base: spec.GroupSearch.Base, Filter: spec.GroupSearch.Filter, GroupNameAttribute: spec.GroupSearch.Attributes.GroupName, + SkipGroupRefresh: spec.GroupSearch.SkipGroupRefresh, }, Dialer: c.ldapDialer, } diff --git a/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher_test.go b/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher_test.go index d7f82edf..0f57bc2f 100644 --- a/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher_test.go +++ b/internal/controller/supervisorconfig/ldapupstreamwatcher/ldap_upstream_watcher_test.go @@ -196,6 +196,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) { Attributes: v1alpha1.LDAPIdentityProviderGroupSearchAttributes{ GroupName: testGroupNameAttrName, }, + SkipGroupRefresh: false, }, }, } @@ -1053,6 +1054,67 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) { IDPSpecGeneration: 1234, ConnectionValidCondition: condPtr(ldapConnectionValidTrueConditionWithoutTimeOrGeneration("4242")), }}}, + { + name: "skipping group refresh is valid", + inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) { + upstream.Spec.GroupSearch.SkipGroupRefresh = true + })}, + inputSecrets: []runtime.Object{validBindUserSecret("4242")}, + setupMocks: func(conn *mockldapconn.MockConn) { + // Should perform a test dial and bind. + conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) + conn.EXPECT().Close().Times(1) + }, + wantResultingCache: []*upstreamldap.ProviderConfig{ + { + Name: testName, + ResourceUID: testResourceUID, + Host: testHost, + ConnectionProtocol: upstreamldap.TLS, + CABundle: testCABundle, + BindUsername: testBindUsername, + BindPassword: testBindPassword, + UserSearch: upstreamldap.UserSearchConfig{ + Base: testUserSearchBase, + Filter: testUserSearchFilter, + UsernameAttribute: testUsernameAttrName, + UIDAttribute: testUIDAttrName, + }, + GroupSearch: upstreamldap.GroupSearchConfig{ + Base: testGroupSearchBase, + Filter: testGroupSearchFilter, + GroupNameAttribute: testGroupNameAttrName, + SkipGroupRefresh: true, + }, + }, + }, + wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID}, + Status: v1alpha1.LDAPIdentityProviderStatus{ + Phase: "Ready", + Conditions: []v1alpha1.Condition{ + bindSecretValidTrueCondition(1234), + ldapConnectionValidTrueCondition(1234, "4242"), + { + Type: "TLSConfigurationValid", + Status: "True", + LastTransitionTime: now, + Reason: "Success", + Message: "loaded TLS configuration", + ObservedGeneration: 1234, + }, + }, + }, + }}, + wantValidatedSettings: map[string]upstreamwatchers.ValidatedSettings{testName: { + BindSecretResourceVersion: "4242", + LDAPConnectionProtocol: upstreamldap.TLS, + UserSearchBase: testUserSearchBase, + GroupSearchBase: testGroupSearchBase, + IDPSpecGeneration: 1234, + ConnectionValidCondition: condPtr(ldapConnectionValidTrueConditionWithoutTimeOrGeneration("4242")), + }}, + }, } for _, tt := range tests { diff --git a/internal/oidc/provider/dynamic_upstream_idp_provider.go b/internal/oidc/provider/dynamic_upstream_idp_provider.go index 6f0ced3d..79771943 100644 --- a/internal/oidc/provider/dynamic_upstream_idp_provider.go +++ b/internal/oidc/provider/dynamic_upstream_idp_provider.go @@ -115,6 +115,7 @@ type StoredRefreshAttributes struct { Username string Subject string DN string + Groups []string AdditionalAttributes map[string]string } diff --git a/internal/oidc/token/token_handler.go b/internal/oidc/token/token_handler.go index eab5ed11..55e73352 100644 --- a/internal/oidc/token/token_handler.go +++ b/internal/oidc/token/token_handler.go @@ -275,6 +275,10 @@ func upstreamLDAPRefresh(ctx context.Context, providerCache oidc.UpstreamIdentit return err } subject := session.Fosite.Claims.Subject + oldGroups, err := getDownstreamGroupsFromPinnipedSession(session) + if err != nil { + return err + } s := session.Custom @@ -305,6 +309,7 @@ func upstreamLDAPRefresh(ctx context.Context, providerCache oidc.UpstreamIdentit Username: username, Subject: subject, DN: dn, + Groups: oldGroups, AdditionalAttributes: additionalAttributes, }) if err != nil { @@ -353,7 +358,7 @@ func getDownstreamUsernameFromPinnipedSession(session *psession.PinnipedSession) if extra == nil { return "", errorsx.WithStack(errMissingUpstreamSessionInternalError) } - downstreamUsernameInterface := extra["username"] + downstreamUsernameInterface := extra[oidc.DownstreamUsernameClaim] if downstreamUsernameInterface == nil { return "", errorsx.WithStack(errMissingUpstreamSessionInternalError) } @@ -363,3 +368,28 @@ func getDownstreamUsernameFromPinnipedSession(session *psession.PinnipedSession) } return downstreamUsername, nil } + +func getDownstreamGroupsFromPinnipedSession(session *psession.PinnipedSession) ([]string, error) { + extra := session.Fosite.Claims.Extra + if extra == nil { + return nil, errorsx.WithStack(errMissingUpstreamSessionInternalError) + } + downstreamGroupsInterface := extra[oidc.DownstreamGroupsClaim] + if downstreamGroupsInterface == nil { + return nil, errorsx.WithStack(errMissingUpstreamSessionInternalError) + } + downstreamGroupsInterfaceList, ok := downstreamGroupsInterface.([]interface{}) + if !ok { + return nil, errorsx.WithStack(errMissingUpstreamSessionInternalError) + } + + downstreamGroups := make([]string, 0, len(downstreamGroupsInterfaceList)) + for _, downstreamGroupInterface := range downstreamGroupsInterfaceList { + downstreamGroup, ok := downstreamGroupInterface.(string) + if !ok || len(downstreamGroup) == 0 { + return nil, errorsx.WithStack(errMissingUpstreamSessionInternalError) + } + downstreamGroups = append(downstreamGroups, downstreamGroup) + } + return downstreamGroups, nil +} diff --git a/internal/upstreamldap/upstreamldap.go b/internal/upstreamldap/upstreamldap.go index 6ea38b6b..136fc023 100644 --- a/internal/upstreamldap/upstreamldap.go +++ b/internal/upstreamldap/upstreamldap.go @@ -150,6 +150,11 @@ type GroupSearchConfig struct { // GroupNameAttribute is the attribute in the LDAP group entry from which the group name should be // retrieved. Empty means to use 'cn'. GroupNameAttribute string + + // SkipGroupRefresh skips the group refresh operation that occurs with each refresh + // (every 5 minutes). This can be done if group search is very slow or resource intensive for the LDAP + // server. + SkipGroupRefresh bool } type Provider struct { @@ -230,6 +235,10 @@ func (p *Provider) PerformRefresh(ctx context.Context, storedRefreshAttributes p } } + if p.c.GroupSearch.SkipGroupRefresh { + return storedRefreshAttributes.Groups, nil + } + mappedGroupNames, err := p.searchGroupsForUserDN(conn, userDN) if err != nil { return nil, err diff --git a/internal/upstreamldap/upstreamldap_test.go b/internal/upstreamldap/upstreamldap_test.go index 8b899568..bb837339 100644 --- a/internal/upstreamldap/upstreamldap_test.go +++ b/internal/upstreamldap/upstreamldap_test.go @@ -1298,6 +1298,37 @@ func TestUpstreamRefresh(t *testing.T) { }, wantGroups: []string{}, }, + { + 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), + }, + }, + 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().Close().Times(1) + }, + wantGroups: nil, // do not update groups + }, { name: "error where dial fails", providerConfig: providerConfig,