Merge branch 'initial_ldap_group_support' into ldap_starttls

This commit is contained in:
Ryan Richard 2021-05-18 16:39:59 -07:00
commit b5063e59ab
29 changed files with 1784 additions and 424 deletions

View File

@ -39,14 +39,14 @@ type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and // SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be // password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value // of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". // should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty. // The password must be non-empty.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"` SecretName string `json:"secretName"`
} }
type LDAPIdentityProviderUserSearchAttributes struct { type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // Username specifies the name of the attribute in the LDAP entry whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". // the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP // The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
@ -64,14 +64,26 @@ type LDAPIdentityProviderUserSearchAttributes struct {
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
} }
type LDAPIdentityProviderGroupSearchAttributes struct {
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
// in the user's list of groups after a successful authentication.
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
// +optional
GroupName string `json:"groupName,omitempty"`
}
type LDAPIdentityProviderUserSearch struct { type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". // Base is the dn (distinguished name) that should be used as the search base when searching for users.
// E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"` Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur // Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" // in the filter at least once and will be dynamically replaced by the username for which the search is being run.
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. // E.g. "mail={}" or "&(objectClass=person)(uid={})". 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. // 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 the value from // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be // Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
@ -85,6 +97,30 @@ type LDAPIdentityProviderUserSearch struct {
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"` Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
} }
type LDAPIdentityProviderGroupSearch struct {
// 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.
// +optional
Base string `json:"base,omitempty"`
// 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={}".
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the group's information should be read from each LDAP entry which was found as
// the result of the group search.
// +optional
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider. // Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct { type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636. // Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
@ -100,6 +136,9 @@ type LDAPIdentityProviderSpec struct {
// UserSearch contains the configuration for searching for a user by name in the LDAP provider. // UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"` UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
} }
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access // LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access

View File

@ -63,13 +63,55 @@ spec:
LDAP bind user. This account will be used to perform LDAP searches. LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". should be the full dn (distinguished name) of your bind account,
The password must be non-empty. e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password
must be non-empty.
minLength: 1 minLength: 1
type: string type: string
required: required:
- secretName - secretName
type: object type: object
groupSearch:
description: GroupSearch contains the configuration for searching
for a user's group membership in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the group's information
should be read from each LDAP entry which was found as the result
of the group search.
properties:
groupName:
description: GroupName specifies the name of the attribute
in the LDAP entries whose value shall become a group name
in the user's list of groups after a successful authentication.
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". Optional. When not specified,
the default will act as if the GroupName were specified
as "cn" (common name).
type: string
type: object
base:
description: 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.
type: string
filter:
description: 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={}".
type: string
type: object
host: host:
description: 'Host is the hostname of this LDAP identity provider, description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.' i.e., where to connect. For example: ldap.example.com:636.'
@ -104,37 +146,39 @@ spec:
minLength: 1 minLength: 1
type: string type: string
username: username:
description: Username specifies the name of attribute in the description: Username specifies the name of the attribute
LDAP entry which whose value shall become the username of in the LDAP entry whose value shall become the username
the user after a successful authentication. This would typically of the user after a successful authentication. This would
be the same attribute name used in the user search filter, typically be the same attribute name used in the user search
although it can be different. E.g. "mail" or "uid" or "userPrincipalName". filter, although it can be different. E.g. "mail" or "uid"
The value of this field is case-sensitive and must match or "userPrincipalName". The value of this field is case-sensitive
the case of the attribute name returned by the LDAP server and must match the case of the attribute name returned by
in the user's entry. Distinguished names can be used by the LDAP server in the user's entry. Distinguished names
specifying lower-case "dn". When this field is set to "dn" can be used by specifying lower-case "dn". When this field
then the LDAPIdentityProviderUserSearch's Filter field cannot is set to "dn" then the LDAPIdentityProviderUserSearch's
be blank, since the default value of "dn={}" would not work. Filter field cannot be blank, since the default value of
"dn={}" would not work.
minLength: 1 minLength: 1
type: string type: string
type: object type: object
base: base:
description: Base is the DN that should be used as the search description: Base is the dn (distinguished name) that should be
base when searching for users. E.g. "ou=users,dc=example,dc=com". used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1 minLength: 1
type: string type: string
filter: filter:
description: Filter is the LDAP search filter which should be description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username in the filter at least once and will be dynamically replaced
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". by the username for which the search is being run. E.g. "mail={}"
For more information about LDAP filters, see https://ldap.com/ldap-filters. or "&(objectClass=person)(uid={})". For more information about
Note that the dn (distinguished name) is not an attribute of LDAP filters, see https://ldap.com/ldap-filters. Note that the
an entry, so "dn={}" cannot be used. Optional. When not specified, dn (distinguished name) is not an attribute of an entry, so
the default will act as if the Filter were specified as the "dn={}" cannot be used. Optional. When not specified, the default
value from Attributes.Username appended by "={}". When the Attributes.Username will act as if the Filter were specified as the value from Attributes.Username
is set to "dn" then the Filter must be explicitly specified, appended by "={}". When the Attributes.Username is set to "dn"
since the default value of "dn={}" would not work. then the Filter must be explicitly specified, since the default
value of "dn={}" would not work.
type: string type: string
type: object type: object
required: required:

View File

@ -734,7 +734,43 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty. | *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch"]
==== LDAPIdentityProviderGroupSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`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.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes"]
==== LDAPIdentityProviderGroupSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groupName`* __string__ | GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name in the user's list of groups after a successful authentication. The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
|=== |===
@ -757,6 +793,7 @@ Spec for configuring an LDAP identity provider.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt. | *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider. | *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
| *`groupSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]__ | GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
|=== |===
@ -791,8 +828,8 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search. | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===
@ -810,7 +847,7 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work. | *`username`* __string__ | Username specifies the name of the attribute in the LDAP entry whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". | *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|=== |===

View File

@ -39,14 +39,14 @@ type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and // SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be // password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value // of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". // should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty. // The password must be non-empty.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"` SecretName string `json:"secretName"`
} }
type LDAPIdentityProviderUserSearchAttributes struct { type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // Username specifies the name of the attribute in the LDAP entry whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". // the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP // The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
@ -64,14 +64,26 @@ type LDAPIdentityProviderUserSearchAttributes struct {
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
} }
type LDAPIdentityProviderGroupSearchAttributes struct {
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
// in the user's list of groups after a successful authentication.
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
// +optional
GroupName string `json:"groupName,omitempty"`
}
type LDAPIdentityProviderUserSearch struct { type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". // Base is the dn (distinguished name) that should be used as the search base when searching for users.
// E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"` Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur // Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" // in the filter at least once and will be dynamically replaced by the username for which the search is being run.
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. // E.g. "mail={}" or "&(objectClass=person)(uid={})". 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. // 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 the value from // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be // Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
@ -85,6 +97,30 @@ type LDAPIdentityProviderUserSearch struct {
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"` Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
} }
type LDAPIdentityProviderGroupSearch struct {
// 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.
// +optional
Base string `json:"base,omitempty"`
// 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={}".
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the group's information should be read from each LDAP entry which was found as
// the result of the group search.
// +optional
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider. // Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct { type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636. // Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
@ -100,6 +136,9 @@ type LDAPIdentityProviderSpec struct {
// UserSearch contains the configuration for searching for a user by name in the LDAP provider. // UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"` UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
} }
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access // LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access

View File

@ -72,6 +72,39 @@ func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearch) DeepCopyInto(out *LDAPIdentityProviderGroupSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearch.
func (in *LDAPIdentityProviderGroupSearch) DeepCopy() *LDAPIdentityProviderGroupSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderGroupSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearchAttributes.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopy() *LDAPIdentityProviderGroupSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) { func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in *out = *in
@ -115,6 +148,7 @@ func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec)
} }
out.Bind = in.Bind out.Bind = in.Bind
out.UserSearch = in.UserSearch out.UserSearch = in.UserSearch
out.GroupSearch = in.GroupSearch
return return
} }

View File

@ -63,13 +63,55 @@ spec:
LDAP bind user. This account will be used to perform LDAP searches. LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". should be the full dn (distinguished name) of your bind account,
The password must be non-empty. e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password
must be non-empty.
minLength: 1 minLength: 1
type: string type: string
required: required:
- secretName - secretName
type: object type: object
groupSearch:
description: GroupSearch contains the configuration for searching
for a user's group membership in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the group's information
should be read from each LDAP entry which was found as the result
of the group search.
properties:
groupName:
description: GroupName specifies the name of the attribute
in the LDAP entries whose value shall become a group name
in the user's list of groups after a successful authentication.
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". Optional. When not specified,
the default will act as if the GroupName were specified
as "cn" (common name).
type: string
type: object
base:
description: 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.
type: string
filter:
description: 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={}".
type: string
type: object
host: host:
description: 'Host is the hostname of this LDAP identity provider, description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.' i.e., where to connect. For example: ldap.example.com:636.'
@ -104,37 +146,39 @@ spec:
minLength: 1 minLength: 1
type: string type: string
username: username:
description: Username specifies the name of attribute in the description: Username specifies the name of the attribute
LDAP entry which whose value shall become the username of in the LDAP entry whose value shall become the username
the user after a successful authentication. This would typically of the user after a successful authentication. This would
be the same attribute name used in the user search filter, typically be the same attribute name used in the user search
although it can be different. E.g. "mail" or "uid" or "userPrincipalName". filter, although it can be different. E.g. "mail" or "uid"
The value of this field is case-sensitive and must match or "userPrincipalName". The value of this field is case-sensitive
the case of the attribute name returned by the LDAP server and must match the case of the attribute name returned by
in the user's entry. Distinguished names can be used by the LDAP server in the user's entry. Distinguished names
specifying lower-case "dn". When this field is set to "dn" can be used by specifying lower-case "dn". When this field
then the LDAPIdentityProviderUserSearch's Filter field cannot is set to "dn" then the LDAPIdentityProviderUserSearch's
be blank, since the default value of "dn={}" would not work. Filter field cannot be blank, since the default value of
"dn={}" would not work.
minLength: 1 minLength: 1
type: string type: string
type: object type: object
base: base:
description: Base is the DN that should be used as the search description: Base is the dn (distinguished name) that should be
base when searching for users. E.g. "ou=users,dc=example,dc=com". used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1 minLength: 1
type: string type: string
filter: filter:
description: Filter is the LDAP search filter which should be description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username in the filter at least once and will be dynamically replaced
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". by the username for which the search is being run. E.g. "mail={}"
For more information about LDAP filters, see https://ldap.com/ldap-filters. or "&(objectClass=person)(uid={})". For more information about
Note that the dn (distinguished name) is not an attribute of LDAP filters, see https://ldap.com/ldap-filters. Note that the
an entry, so "dn={}" cannot be used. Optional. When not specified, dn (distinguished name) is not an attribute of an entry, so
the default will act as if the Filter were specified as the "dn={}" cannot be used. Optional. When not specified, the default
value from Attributes.Username appended by "={}". When the Attributes.Username will act as if the Filter were specified as the value from Attributes.Username
is set to "dn" then the Filter must be explicitly specified, appended by "={}". When the Attributes.Username is set to "dn"
since the default value of "dn={}" would not work. then the Filter must be explicitly specified, since the default
value of "dn={}" would not work.
type: string type: string
type: object type: object
required: required:

View File

@ -734,7 +734,43 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty. | *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch"]
==== LDAPIdentityProviderGroupSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`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.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes"]
==== LDAPIdentityProviderGroupSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groupName`* __string__ | GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name in the user's list of groups after a successful authentication. The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
|=== |===
@ -757,6 +793,7 @@ Spec for configuring an LDAP identity provider.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt. | *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider. | *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
| *`groupSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]__ | GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
|=== |===
@ -791,8 +828,8 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search. | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===
@ -810,7 +847,7 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work. | *`username`* __string__ | Username specifies the name of the attribute in the LDAP entry whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". | *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|=== |===

View File

@ -39,14 +39,14 @@ type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and // SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be // password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value // of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". // should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty. // The password must be non-empty.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"` SecretName string `json:"secretName"`
} }
type LDAPIdentityProviderUserSearchAttributes struct { type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // Username specifies the name of the attribute in the LDAP entry whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". // the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP // The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
@ -64,14 +64,26 @@ type LDAPIdentityProviderUserSearchAttributes struct {
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
} }
type LDAPIdentityProviderGroupSearchAttributes struct {
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
// in the user's list of groups after a successful authentication.
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
// +optional
GroupName string `json:"groupName,omitempty"`
}
type LDAPIdentityProviderUserSearch struct { type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". // Base is the dn (distinguished name) that should be used as the search base when searching for users.
// E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"` Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur // Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" // in the filter at least once and will be dynamically replaced by the username for which the search is being run.
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. // E.g. "mail={}" or "&(objectClass=person)(uid={})". 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. // 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 the value from // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be // Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
@ -85,6 +97,30 @@ type LDAPIdentityProviderUserSearch struct {
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"` Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
} }
type LDAPIdentityProviderGroupSearch struct {
// 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.
// +optional
Base string `json:"base,omitempty"`
// 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={}".
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the group's information should be read from each LDAP entry which was found as
// the result of the group search.
// +optional
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider. // Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct { type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636. // Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
@ -100,6 +136,9 @@ type LDAPIdentityProviderSpec struct {
// UserSearch contains the configuration for searching for a user by name in the LDAP provider. // UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"` UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
} }
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access // LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access

View File

@ -72,6 +72,39 @@ func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearch) DeepCopyInto(out *LDAPIdentityProviderGroupSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearch.
func (in *LDAPIdentityProviderGroupSearch) DeepCopy() *LDAPIdentityProviderGroupSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderGroupSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearchAttributes.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopy() *LDAPIdentityProviderGroupSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) { func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in *out = *in
@ -115,6 +148,7 @@ func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec)
} }
out.Bind = in.Bind out.Bind = in.Bind
out.UserSearch = in.UserSearch out.UserSearch = in.UserSearch
out.GroupSearch = in.GroupSearch
return return
} }

View File

@ -63,13 +63,55 @@ spec:
LDAP bind user. This account will be used to perform LDAP searches. LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". should be the full dn (distinguished name) of your bind account,
The password must be non-empty. e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password
must be non-empty.
minLength: 1 minLength: 1
type: string type: string
required: required:
- secretName - secretName
type: object type: object
groupSearch:
description: GroupSearch contains the configuration for searching
for a user's group membership in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the group's information
should be read from each LDAP entry which was found as the result
of the group search.
properties:
groupName:
description: GroupName specifies the name of the attribute
in the LDAP entries whose value shall become a group name
in the user's list of groups after a successful authentication.
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". Optional. When not specified,
the default will act as if the GroupName were specified
as "cn" (common name).
type: string
type: object
base:
description: 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.
type: string
filter:
description: 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={}".
type: string
type: object
host: host:
description: 'Host is the hostname of this LDAP identity provider, description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.' i.e., where to connect. For example: ldap.example.com:636.'
@ -104,37 +146,39 @@ spec:
minLength: 1 minLength: 1
type: string type: string
username: username:
description: Username specifies the name of attribute in the description: Username specifies the name of the attribute
LDAP entry which whose value shall become the username of in the LDAP entry whose value shall become the username
the user after a successful authentication. This would typically of the user after a successful authentication. This would
be the same attribute name used in the user search filter, typically be the same attribute name used in the user search
although it can be different. E.g. "mail" or "uid" or "userPrincipalName". filter, although it can be different. E.g. "mail" or "uid"
The value of this field is case-sensitive and must match or "userPrincipalName". The value of this field is case-sensitive
the case of the attribute name returned by the LDAP server and must match the case of the attribute name returned by
in the user's entry. Distinguished names can be used by the LDAP server in the user's entry. Distinguished names
specifying lower-case "dn". When this field is set to "dn" can be used by specifying lower-case "dn". When this field
then the LDAPIdentityProviderUserSearch's Filter field cannot is set to "dn" then the LDAPIdentityProviderUserSearch's
be blank, since the default value of "dn={}" would not work. Filter field cannot be blank, since the default value of
"dn={}" would not work.
minLength: 1 minLength: 1
type: string type: string
type: object type: object
base: base:
description: Base is the DN that should be used as the search description: Base is the dn (distinguished name) that should be
base when searching for users. E.g. "ou=users,dc=example,dc=com". used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1 minLength: 1
type: string type: string
filter: filter:
description: Filter is the LDAP search filter which should be description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username in the filter at least once and will be dynamically replaced
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". by the username for which the search is being run. E.g. "mail={}"
For more information about LDAP filters, see https://ldap.com/ldap-filters. or "&(objectClass=person)(uid={})". For more information about
Note that the dn (distinguished name) is not an attribute of LDAP filters, see https://ldap.com/ldap-filters. Note that the
an entry, so "dn={}" cannot be used. Optional. When not specified, dn (distinguished name) is not an attribute of an entry, so
the default will act as if the Filter were specified as the "dn={}" cannot be used. Optional. When not specified, the default
value from Attributes.Username appended by "={}". When the Attributes.Username will act as if the Filter were specified as the value from Attributes.Username
is set to "dn" then the Filter must be explicitly specified, appended by "={}". When the Attributes.Username is set to "dn"
since the default value of "dn={}" would not work. then the Filter must be explicitly specified, since the default
value of "dn={}" would not work.
type: string type: string
type: object type: object
required: required:

View File

@ -734,7 +734,43 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty. | *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch"]
==== LDAPIdentityProviderGroupSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`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.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes"]
==== LDAPIdentityProviderGroupSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groupName`* __string__ | GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name in the user's list of groups after a successful authentication. The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
|=== |===
@ -757,6 +793,7 @@ Spec for configuring an LDAP identity provider.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt. | *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider. | *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
| *`groupSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]__ | GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
|=== |===
@ -791,8 +828,8 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search. | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===
@ -810,7 +847,7 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work. | *`username`* __string__ | Username specifies the name of the attribute in the LDAP entry whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". | *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|=== |===

View File

@ -39,14 +39,14 @@ type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and // SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be // password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value // of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". // should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty. // The password must be non-empty.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"` SecretName string `json:"secretName"`
} }
type LDAPIdentityProviderUserSearchAttributes struct { type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // Username specifies the name of the attribute in the LDAP entry whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". // the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP // The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
@ -64,14 +64,26 @@ type LDAPIdentityProviderUserSearchAttributes struct {
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
} }
type LDAPIdentityProviderGroupSearchAttributes struct {
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
// in the user's list of groups after a successful authentication.
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
// +optional
GroupName string `json:"groupName,omitempty"`
}
type LDAPIdentityProviderUserSearch struct { type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". // Base is the dn (distinguished name) that should be used as the search base when searching for users.
// E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"` Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur // Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" // in the filter at least once and will be dynamically replaced by the username for which the search is being run.
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. // E.g. "mail={}" or "&(objectClass=person)(uid={})". 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. // 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 the value from // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be // Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
@ -85,6 +97,30 @@ type LDAPIdentityProviderUserSearch struct {
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"` Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
} }
type LDAPIdentityProviderGroupSearch struct {
// 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.
// +optional
Base string `json:"base,omitempty"`
// 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={}".
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the group's information should be read from each LDAP entry which was found as
// the result of the group search.
// +optional
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider. // Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct { type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636. // Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
@ -100,6 +136,9 @@ type LDAPIdentityProviderSpec struct {
// UserSearch contains the configuration for searching for a user by name in the LDAP provider. // UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"` UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
} }
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access // LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access

View File

@ -72,6 +72,39 @@ func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearch) DeepCopyInto(out *LDAPIdentityProviderGroupSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearch.
func (in *LDAPIdentityProviderGroupSearch) DeepCopy() *LDAPIdentityProviderGroupSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderGroupSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearchAttributes.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopy() *LDAPIdentityProviderGroupSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) { func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in *out = *in
@ -115,6 +148,7 @@ func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec)
} }
out.Bind = in.Bind out.Bind = in.Bind
out.UserSearch = in.UserSearch out.UserSearch = in.UserSearch
out.GroupSearch = in.GroupSearch
return return
} }

View File

@ -63,13 +63,55 @@ spec:
LDAP bind user. This account will be used to perform LDAP searches. LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". should be the full dn (distinguished name) of your bind account,
The password must be non-empty. e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password
must be non-empty.
minLength: 1 minLength: 1
type: string type: string
required: required:
- secretName - secretName
type: object type: object
groupSearch:
description: GroupSearch contains the configuration for searching
for a user's group membership in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the group's information
should be read from each LDAP entry which was found as the result
of the group search.
properties:
groupName:
description: GroupName specifies the name of the attribute
in the LDAP entries whose value shall become a group name
in the user's list of groups after a successful authentication.
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". Optional. When not specified,
the default will act as if the GroupName were specified
as "cn" (common name).
type: string
type: object
base:
description: 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.
type: string
filter:
description: 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={}".
type: string
type: object
host: host:
description: 'Host is the hostname of this LDAP identity provider, description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.' i.e., where to connect. For example: ldap.example.com:636.'
@ -104,37 +146,39 @@ spec:
minLength: 1 minLength: 1
type: string type: string
username: username:
description: Username specifies the name of attribute in the description: Username specifies the name of the attribute
LDAP entry which whose value shall become the username of in the LDAP entry whose value shall become the username
the user after a successful authentication. This would typically of the user after a successful authentication. This would
be the same attribute name used in the user search filter, typically be the same attribute name used in the user search
although it can be different. E.g. "mail" or "uid" or "userPrincipalName". filter, although it can be different. E.g. "mail" or "uid"
The value of this field is case-sensitive and must match or "userPrincipalName". The value of this field is case-sensitive
the case of the attribute name returned by the LDAP server and must match the case of the attribute name returned by
in the user's entry. Distinguished names can be used by the LDAP server in the user's entry. Distinguished names
specifying lower-case "dn". When this field is set to "dn" can be used by specifying lower-case "dn". When this field
then the LDAPIdentityProviderUserSearch's Filter field cannot is set to "dn" then the LDAPIdentityProviderUserSearch's
be blank, since the default value of "dn={}" would not work. Filter field cannot be blank, since the default value of
"dn={}" would not work.
minLength: 1 minLength: 1
type: string type: string
type: object type: object
base: base:
description: Base is the DN that should be used as the search description: Base is the dn (distinguished name) that should be
base when searching for users. E.g. "ou=users,dc=example,dc=com". used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1 minLength: 1
type: string type: string
filter: filter:
description: Filter is the LDAP search filter which should be description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username in the filter at least once and will be dynamically replaced
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". by the username for which the search is being run. E.g. "mail={}"
For more information about LDAP filters, see https://ldap.com/ldap-filters. or "&(objectClass=person)(uid={})". For more information about
Note that the dn (distinguished name) is not an attribute of LDAP filters, see https://ldap.com/ldap-filters. Note that the
an entry, so "dn={}" cannot be used. Optional. When not specified, dn (distinguished name) is not an attribute of an entry, so
the default will act as if the Filter were specified as the "dn={}" cannot be used. Optional. When not specified, the default
value from Attributes.Username appended by "={}". When the Attributes.Username will act as if the Filter were specified as the value from Attributes.Username
is set to "dn" then the Filter must be explicitly specified, appended by "={}". When the Attributes.Username is set to "dn"
since the default value of "dn={}" would not work. then the Filter must be explicitly specified, since the default
value of "dn={}" would not work.
type: string type: string
type: object type: object
required: required:

View File

@ -734,7 +734,43 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty. | *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch"]
==== LDAPIdentityProviderGroupSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`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.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes"]
==== LDAPIdentityProviderGroupSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groupName`* __string__ | GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name in the user's list of groups after a successful authentication. The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
|=== |===
@ -757,6 +793,7 @@ Spec for configuring an LDAP identity provider.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt. | *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider. | *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
| *`groupSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]__ | GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
|=== |===
@ -791,8 +828,8 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". | *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work. | *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". 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 the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search. | *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===
@ -810,7 +847,7 @@ Status of an LDAP identity provider.
[cols="25a,75a", options="header"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work. | *`username`* __string__ | Username specifies the name of the attribute in the LDAP entry whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". | *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|=== |===

View File

@ -39,14 +39,14 @@ type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and // SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be // password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value // of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". // should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty. // The password must be non-empty.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"` SecretName string `json:"secretName"`
} }
type LDAPIdentityProviderUserSearchAttributes struct { type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // Username specifies the name of the attribute in the LDAP entry whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". // the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP // The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
@ -64,14 +64,26 @@ type LDAPIdentityProviderUserSearchAttributes struct {
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
} }
type LDAPIdentityProviderGroupSearchAttributes struct {
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
// in the user's list of groups after a successful authentication.
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
// +optional
GroupName string `json:"groupName,omitempty"`
}
type LDAPIdentityProviderUserSearch struct { type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". // Base is the dn (distinguished name) that should be used as the search base when searching for users.
// E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"` Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur // Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" // in the filter at least once and will be dynamically replaced by the username for which the search is being run.
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. // E.g. "mail={}" or "&(objectClass=person)(uid={})". 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. // 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 the value from // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be // Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
@ -85,6 +97,30 @@ type LDAPIdentityProviderUserSearch struct {
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"` Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
} }
type LDAPIdentityProviderGroupSearch struct {
// 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.
// +optional
Base string `json:"base,omitempty"`
// 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={}".
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the group's information should be read from each LDAP entry which was found as
// the result of the group search.
// +optional
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider. // Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct { type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636. // Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
@ -100,6 +136,9 @@ type LDAPIdentityProviderSpec struct {
// UserSearch contains the configuration for searching for a user by name in the LDAP provider. // UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"` UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
} }
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access // LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access

View File

@ -72,6 +72,39 @@ func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearch) DeepCopyInto(out *LDAPIdentityProviderGroupSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearch.
func (in *LDAPIdentityProviderGroupSearch) DeepCopy() *LDAPIdentityProviderGroupSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderGroupSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearchAttributes.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopy() *LDAPIdentityProviderGroupSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) { func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in *out = *in
@ -115,6 +148,7 @@ func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec)
} }
out.Bind = in.Bind out.Bind = in.Bind
out.UserSearch = in.UserSearch out.UserSearch = in.UserSearch
out.GroupSearch = in.GroupSearch
return return
} }

View File

@ -63,13 +63,55 @@ spec:
LDAP bind user. This account will be used to perform LDAP searches. LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". should be the full dn (distinguished name) of your bind account,
The password must be non-empty. e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password
must be non-empty.
minLength: 1 minLength: 1
type: string type: string
required: required:
- secretName - secretName
type: object type: object
groupSearch:
description: GroupSearch contains the configuration for searching
for a user's group membership in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the group's information
should be read from each LDAP entry which was found as the result
of the group search.
properties:
groupName:
description: GroupName specifies the name of the attribute
in the LDAP entries whose value shall become a group name
in the user's list of groups after a successful authentication.
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". Optional. When not specified,
the default will act as if the GroupName were specified
as "cn" (common name).
type: string
type: object
base:
description: 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.
type: string
filter:
description: 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={}".
type: string
type: object
host: host:
description: 'Host is the hostname of this LDAP identity provider, description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.' i.e., where to connect. For example: ldap.example.com:636.'
@ -104,37 +146,39 @@ spec:
minLength: 1 minLength: 1
type: string type: string
username: username:
description: Username specifies the name of attribute in the description: Username specifies the name of the attribute
LDAP entry which whose value shall become the username of in the LDAP entry whose value shall become the username
the user after a successful authentication. This would typically of the user after a successful authentication. This would
be the same attribute name used in the user search filter, typically be the same attribute name used in the user search
although it can be different. E.g. "mail" or "uid" or "userPrincipalName". filter, although it can be different. E.g. "mail" or "uid"
The value of this field is case-sensitive and must match or "userPrincipalName". The value of this field is case-sensitive
the case of the attribute name returned by the LDAP server and must match the case of the attribute name returned by
in the user's entry. Distinguished names can be used by the LDAP server in the user's entry. Distinguished names
specifying lower-case "dn". When this field is set to "dn" can be used by specifying lower-case "dn". When this field
then the LDAPIdentityProviderUserSearch's Filter field cannot is set to "dn" then the LDAPIdentityProviderUserSearch's
be blank, since the default value of "dn={}" would not work. Filter field cannot be blank, since the default value of
"dn={}" would not work.
minLength: 1 minLength: 1
type: string type: string
type: object type: object
base: base:
description: Base is the DN that should be used as the search description: Base is the dn (distinguished name) that should be
base when searching for users. E.g. "ou=users,dc=example,dc=com". used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1 minLength: 1
type: string type: string
filter: filter:
description: Filter is the LDAP search filter which should be description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username in the filter at least once and will be dynamically replaced
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". by the username for which the search is being run. E.g. "mail={}"
For more information about LDAP filters, see https://ldap.com/ldap-filters. or "&(objectClass=person)(uid={})". For more information about
Note that the dn (distinguished name) is not an attribute of LDAP filters, see https://ldap.com/ldap-filters. Note that the
an entry, so "dn={}" cannot be used. Optional. When not specified, dn (distinguished name) is not an attribute of an entry, so
the default will act as if the Filter were specified as the "dn={}" cannot be used. Optional. When not specified, the default
value from Attributes.Username appended by "={}". When the Attributes.Username will act as if the Filter were specified as the value from Attributes.Username
is set to "dn" then the Filter must be explicitly specified, appended by "={}". When the Attributes.Username is set to "dn"
since the default value of "dn={}" would not work. then the Filter must be explicitly specified, since the default
value of "dn={}" would not work.
type: string type: string
type: object type: object
required: required:

View File

@ -39,14 +39,14 @@ type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and // SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be // password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value // of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". // should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty. // The password must be non-empty.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"` SecretName string `json:"secretName"`
} }
type LDAPIdentityProviderUserSearchAttributes struct { type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // Username specifies the name of the attribute in the LDAP entry whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". // the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP // The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
@ -64,14 +64,26 @@ type LDAPIdentityProviderUserSearchAttributes struct {
UID string `json:"uid,omitempty"` UID string `json:"uid,omitempty"`
} }
type LDAPIdentityProviderGroupSearchAttributes struct {
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
// in the user's list of groups after a successful authentication.
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// Optional. When not specified, the default will act as if the GroupName were specified as "cn" (common name).
// +optional
GroupName string `json:"groupName,omitempty"`
}
type LDAPIdentityProviderUserSearch struct { type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". // Base is the dn (distinguished name) that should be used as the search base when searching for users.
// E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"` Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur // Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" // in the filter at least once and will be dynamically replaced by the username for which the search is being run.
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. // E.g. "mail={}" or "&(objectClass=person)(uid={})". 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. // 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 the value from // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be // Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
@ -85,6 +97,30 @@ type LDAPIdentityProviderUserSearch struct {
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"` Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
} }
type LDAPIdentityProviderGroupSearch struct {
// 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.
// +optional
Base string `json:"base,omitempty"`
// 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={}".
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the group's information should be read from each LDAP entry which was found as
// the result of the group search.
// +optional
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider. // Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct { type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636. // Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
@ -100,6 +136,9 @@ type LDAPIdentityProviderSpec struct {
// UserSearch contains the configuration for searching for a user by name in the LDAP provider. // UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"` UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
} }
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access // LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access

View File

@ -72,6 +72,39 @@ func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearch) DeepCopyInto(out *LDAPIdentityProviderGroupSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearch.
func (in *LDAPIdentityProviderGroupSearch) DeepCopy() *LDAPIdentityProviderGroupSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderGroupSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderGroupSearchAttributes.
func (in *LDAPIdentityProviderGroupSearchAttributes) DeepCopy() *LDAPIdentityProviderGroupSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderGroupSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) { func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in *out = *in
@ -115,6 +148,7 @@ func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec)
} }
out.Bind = in.Bind out.Bind = in.Bind
out.UserSearch = in.UserSearch out.UserSearch = in.UserSearch
out.GroupSearch = in.GroupSearch
return return
} }

View File

@ -160,6 +160,11 @@ func (c *ldapWatcherController) validateUpstream(ctx context.Context, upstream *
UsernameAttribute: spec.UserSearch.Attributes.Username, UsernameAttribute: spec.UserSearch.Attributes.Username,
UIDAttribute: spec.UserSearch.Attributes.UID, UIDAttribute: spec.UserSearch.Attributes.UID,
}, },
GroupSearch: upstreamldap.GroupSearchConfig{
Base: spec.GroupSearch.Base,
Filter: spec.GroupSearch.Filter,
GroupNameAttribute: spec.GroupSearch.Attributes.GroupName,
},
Dialer: c.ldapDialer, Dialer: c.ldapDialer,
} }

View File

@ -145,16 +145,19 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
now := metav1.NewTime(time.Now().UTC()) now := metav1.NewTime(time.Now().UTC())
const ( const (
testNamespace = "test-namespace" testNamespace = "test-namespace"
testName = "test-name" testName = "test-name"
testSecretName = "test-bind-secret" testSecretName = "test-bind-secret"
testBindUsername = "test-bind-username" testBindUsername = "test-bind-username"
testBindPassword = "test-bind-password" testBindPassword = "test-bind-password"
testHost = "ldap.example.com:123" testHost = "ldap.example.com:123"
testUserSearchBase = "test-user-search-base" testUserSearchBase = "test-user-search-base"
testUserSearchFilter = "test-user-search-filter" testUserSearchFilter = "test-user-search-filter"
testUsernameAttrName = "test-username-attr" testGroupSearchBase = "test-group-search-base"
testUIDAttrName = "test-uid-attr" testGroupSearchFilter = "test-group-search-filter"
testUsernameAttrName = "test-username-attr"
testGroupNameAttrName = "test-group-name-attr"
testUIDAttrName = "test-uid-attr"
) )
testValidSecretData := map[string][]byte{"username": []byte(testBindUsername), "password": []byte(testBindPassword)} testValidSecretData := map[string][]byte{"username": []byte(testBindUsername), "password": []byte(testBindPassword)}
@ -178,6 +181,13 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
UID: testUIDAttrName, UID: testUIDAttrName,
}, },
}, },
GroupSearch: v1alpha1.LDAPIdentityProviderGroupSearch{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
Attributes: v1alpha1.LDAPIdentityProviderGroupSearchAttributes{
GroupName: testGroupNameAttrName,
},
},
}, },
} }
editedValidUpstream := func(editFunc func(*v1alpha1.LDAPIdentityProvider)) *v1alpha1.LDAPIdentityProvider { editedValidUpstream := func(editFunc func(*v1alpha1.LDAPIdentityProvider)) *v1alpha1.LDAPIdentityProvider {
@ -198,6 +208,11 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
UsernameAttribute: testUsernameAttrName, UsernameAttribute: testUsernameAttrName,
UIDAttribute: testUIDAttrName, UIDAttribute: testUIDAttrName,
}, },
GroupSearch: upstreamldap.GroupSearchConfig{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
GroupNameAttribute: testGroupNameAttrName,
},
} }
bindSecretValidTrueCondition := func(gen int64) v1alpha1.Condition { bindSecretValidTrueCondition := func(gen int64) v1alpha1.Condition {
@ -438,6 +453,11 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
UsernameAttribute: testUsernameAttrName, UsernameAttribute: testUsernameAttrName,
UIDAttribute: testUIDAttrName, UIDAttribute: testUIDAttrName,
}, },
GroupSearch: upstreamldap.GroupSearchConfig{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
GroupNameAttribute: testGroupNameAttrName,
},
}, },
}, },
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{ wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
@ -484,6 +504,11 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
UsernameAttribute: testUsernameAttrName, UsernameAttribute: testUsernameAttrName,
UIDAttribute: testUIDAttrName, UIDAttribute: testUIDAttrName,
}, },
GroupSearch: upstreamldap.GroupSearchConfig{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
GroupNameAttribute: testGroupNameAttrName,
},
}, },
}, },
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{ wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{

View File

@ -78,3 +78,18 @@ func (mr *MockConnMockRecorder) Search(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockConn)(nil).Search), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockConn)(nil).Search), arg0)
} }
// SearchWithPaging mocks base method.
func (m *MockConn) SearchWithPaging(arg0 *ldap.SearchRequest, arg1 uint32) (*ldap.SearchResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SearchWithPaging", arg0, arg1)
ret0, _ := ret[0].(*ldap.SearchResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SearchWithPaging indicates an expected call of SearchWithPaging.
func (mr *MockConnMockRecorder) SearchWithPaging(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchWithPaging", reflect.TypeOf((*MockConn)(nil).SearchWithPaging), arg0, arg1)
}

View File

@ -11,6 +11,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sort"
"strings" "strings"
"time" "time"
@ -24,9 +25,11 @@ import (
) )
const ( const (
ldapsScheme = "ldaps" ldapsScheme = "ldaps"
distinguishedNameAttributeName = "dn" distinguishedNameAttributeName = "dn"
userSearchFilterInterpolationLocationMarker = "{}" commonNameAttributeName = "cn"
searchFilterInterpolationLocationMarker = "{}"
groupSearchPageSize = uint32(250)
) )
// Conn abstracts the upstream LDAP communication protocol (mostly for testing). // Conn abstracts the upstream LDAP communication protocol (mostly for testing).
@ -35,6 +38,8 @@ type Conn interface {
Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error)
SearchWithPaging(searchRequest *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error)
Close() Close()
} }
@ -78,6 +83,9 @@ type ProviderConfig struct {
// UserSearch contains information about how to search for users in the upstream LDAP IDP. // UserSearch contains information about how to search for users in the upstream LDAP IDP.
UserSearch UserSearchConfig UserSearch UserSearchConfig
// GroupSearch contains information about how to search for group membership in the upstream LDAP IDP.
GroupSearch GroupSearchConfig
// Dialer exists to enable testing. When nil, will use a default appropriate for production use. // Dialer exists to enable testing. When nil, will use a default appropriate for production use.
Dialer LDAPDialer Dialer LDAPDialer
} }
@ -99,6 +107,20 @@ type UserSearchConfig struct {
UIDAttribute string UIDAttribute string
} }
// GroupSearchConfig contains information about how to search for group membership for users in the upstream LDAP IDP.
type GroupSearchConfig struct {
// Base is the base DN to use for the group search in the upstream LDAP IDP. Empty means to skip group search
// entirely, in which case authenticated users will not belong to any groups from the upstream LDAP IDP.
Base string
// Filter is the filter to use for the group search in the upstream LDAP IDP. Empty means to use `member={}`.
Filter string
// GroupNameAttribute is the attribute in the LDAP group entry from which the group name should be
// retrieved. Empty means to use 'cn'.
GroupNameAttribute string
}
type Provider struct { type Provider struct {
c ProviderConfig c ProviderConfig
} }
@ -257,7 +279,7 @@ func (p *Provider) authenticateUserImpl(ctx context.Context, username string, bi
return nil, false, fmt.Errorf(`error binding as "%s" before user search: %w`, p.c.BindUsername, err) return nil, false, fmt.Errorf(`error binding as "%s" before user search: %w`, p.c.BindUsername, err)
} }
mappedUsername, mappedUID, err := p.searchAndBindUser(conn, username, bindFunc) mappedUsername, mappedUID, mappedGroupNames, err := p.searchAndBindUser(conn, username, bindFunc)
if err != nil { if err != nil {
p.traceAuthFailure(t, err) p.traceAuthFailure(t, err)
return nil, false, err return nil, false, err
@ -272,13 +294,39 @@ func (p *Provider) authenticateUserImpl(ctx context.Context, username string, bi
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: mappedUsername, Name: mappedUsername,
UID: mappedUID, UID: mappedUID,
Groups: []string{}, // Support for group search coming soon. Groups: mappedGroupNames,
}, },
} }
p.traceAuthSuccess(t) p.traceAuthSuccess(t)
return response, true, nil return response, true, nil
} }
func (p *Provider) searchGroupsForUserDN(conn Conn, userDN string) ([]string, error) {
searchResult, err := conn.SearchWithPaging(p.groupSearchRequest(userDN), groupSearchPageSize)
if err != nil {
return nil, fmt.Errorf(`error searching for group memberships for user with DN %q: %w`, userDN, err)
}
groupAttributeName := p.c.GroupSearch.GroupNameAttribute
if len(groupAttributeName) == 0 {
groupAttributeName = commonNameAttributeName
}
groups := []string{}
for _, groupEntry := range searchResult.Entries {
if len(groupEntry.DN) == 0 {
return nil, fmt.Errorf(`searching for group memberships for user with DN %q resulted in search result without DN`, userDN)
}
mappedGroupName, err := p.getSearchResultAttributeValue(groupAttributeName, groupEntry, userDN)
if err != nil {
return nil, fmt.Errorf(`error searching for group memberships for user with DN %q: %w`, userDN, err)
}
groups = append(groups, mappedGroupName)
}
return groups, nil
}
func (p *Provider) validateConfig() error { func (p *Provider) validateConfig() error {
if p.c.UserSearch.UsernameAttribute == distinguishedNameAttributeName && len(p.c.UserSearch.Filter) == 0 { if p.c.UserSearch.UsernameAttribute == distinguishedNameAttributeName && len(p.c.UserSearch.Filter) == 0 {
// LDAP search filters do not allow searching by DN, so we would have no reasonable default for Filter. // LDAP search filters do not allow searching by DN, so we would have no reasonable default for Filter.
@ -287,36 +335,45 @@ func (p *Provider) validateConfig() error {
return nil return nil
} }
func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(conn Conn, foundUserDN string) error) (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 {
return "", "", fmt.Errorf(`error searching for user "%s": %w`, username, err) return "", "", nil, fmt.Errorf(`error searching for user "%s": %w`, username, err)
} }
if len(searchResult.Entries) == 0 { if len(searchResult.Entries) == 0 {
plog.Debug("error finding user: user not found (if this username is valid, please check the user search configuration)", plog.Debug("error finding user: user not found (if this username is valid, please check the user search configuration)",
"upstreamName", p.GetName(), "username", username) "upstreamName", p.GetName(), "username", username)
return "", "", nil return "", "", nil, nil
} }
if len(searchResult.Entries) > 1 { if len(searchResult.Entries) > 1 {
return "", "", fmt.Errorf(`searching for user "%s" resulted in %d search results, but expected 1 result`, return "", "", nil, fmt.Errorf(`searching for user "%s" resulted in %d search results, but expected 1 result`,
username, len(searchResult.Entries), username, len(searchResult.Entries),
) )
} }
userEntry := searchResult.Entries[0] userEntry := searchResult.Entries[0]
if len(userEntry.DN) == 0 { if len(userEntry.DN) == 0 {
return "", "", fmt.Errorf(`searching for user "%s" resulted in search result without DN`, username) return "", "", nil, fmt.Errorf(`searching for user "%s" resulted in search result without DN`, username)
} }
mappedUsername, err := p.getSearchResultAttributeValue(p.c.UserSearch.UsernameAttribute, userEntry, username) mappedUsername, err := p.getSearchResultAttributeValue(p.c.UserSearch.UsernameAttribute, userEntry, username)
if err != nil { if err != nil {
return "", "", err return "", "", nil, err
} }
mappedUID, err := p.getSearchResultAttributeValue(p.c.UserSearch.UIDAttribute, userEntry, username) mappedUID, err := p.getSearchResultAttributeValue(p.c.UserSearch.UIDAttribute, userEntry, username)
if err != nil { if err != nil {
return "", "", err return "", "", nil, err
} }
mappedGroupNames := []string{}
if len(p.c.GroupSearch.Base) > 0 {
mappedGroupNames, err = p.searchGroupsForUserDN(conn, userEntry.DN)
if err != nil {
return "", "", nil, err
}
}
sort.Strings(mappedGroupNames)
// Caution: Note that any other LDAP commands after this bind will be run as this user instead of as the configured BindUsername! // Caution: Note that any other LDAP commands after this bind will be run as this user instead of as the configured BindUsername!
err = bindFunc(conn, userEntry.DN) err = bindFunc(conn, userEntry.DN)
if err != nil { if err != nil {
@ -324,12 +381,12 @@ func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(c
err, "upstreamName", p.GetName(), "username", username, "dn", userEntry.DN) err, "upstreamName", p.GetName(), "username", username, "dn", userEntry.DN)
ldapErr := &ldap.Error{} ldapErr := &ldap.Error{}
if errors.As(err, &ldapErr) && ldapErr.ResultCode == ldap.LDAPResultInvalidCredentials { if errors.As(err, &ldapErr) && ldapErr.ResultCode == ldap.LDAPResultInvalidCredentials {
return "", "", nil return "", "", nil, nil
} }
return "", "", fmt.Errorf(`error binding for user "%s" using provided password against DN "%s": %w`, username, userEntry.DN, err) return "", "", nil, fmt.Errorf(`error binding for user "%s" using provided password against DN "%s": %w`, username, userEntry.DN, err)
} }
return mappedUsername, mappedUID, nil return mappedUsername, mappedUID, mappedGroupNames, nil
} }
func (p *Provider) userSearchRequest(username string) *ldap.SearchRequest { func (p *Provider) userSearchRequest(username string) *ldap.SearchRequest {
@ -347,6 +404,21 @@ func (p *Provider) userSearchRequest(username string) *ldap.SearchRequest {
} }
} }
func (p *Provider) groupSearchRequest(userDN string) *ldap.SearchRequest {
// See https://ldap.com/the-ldap-search-operation for general documentation of LDAP search options.
return &ldap.SearchRequest{
BaseDN: p.c.GroupSearch.Base,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
SizeLimit: 0, // unlimited size because we will search with paging
TimeLimit: 90,
TypesOnly: false,
Filter: p.groupSearchFilter(userDN),
Attributes: p.groupSearchRequestedAttributes(),
Controls: nil, // nil because ldap.SearchWithPaging() will set the appropriate controls for us
}
}
func (p *Provider) userSearchRequestedAttributes() []string { func (p *Provider) userSearchRequestedAttributes() []string {
attributes := []string{} attributes := []string{}
if p.c.UserSearch.UsernameAttribute != distinguishedNameAttributeName { if p.c.UserSearch.UsernameAttribute != distinguishedNameAttributeName {
@ -358,12 +430,34 @@ func (p *Provider) userSearchRequestedAttributes() []string {
return attributes return attributes
} }
func (p *Provider) groupSearchRequestedAttributes() []string {
switch p.c.GroupSearch.GroupNameAttribute {
case "":
return []string{commonNameAttributeName}
case distinguishedNameAttributeName:
return []string{}
default:
return []string{p.c.GroupSearch.GroupNameAttribute}
}
}
func (p *Provider) userSearchFilter(username string) string { func (p *Provider) userSearchFilter(username string) string {
safeUsername := p.escapeUsernameForSearchFilter(username) safeUsername := p.escapeUsernameForSearchFilter(username)
if len(p.c.UserSearch.Filter) == 0 { if len(p.c.UserSearch.Filter) == 0 {
return fmt.Sprintf("(%s=%s)", p.c.UserSearch.UsernameAttribute, safeUsername) return fmt.Sprintf("(%s=%s)", p.c.UserSearch.UsernameAttribute, safeUsername)
} }
filter := strings.ReplaceAll(p.c.UserSearch.Filter, userSearchFilterInterpolationLocationMarker, safeUsername) return interpolateSearchFilter(p.c.UserSearch.Filter, safeUsername)
}
func (p *Provider) groupSearchFilter(userDN string) string {
if len(p.c.GroupSearch.Filter) == 0 {
return fmt.Sprintf("(member=%s)", userDN)
}
return interpolateSearchFilter(p.c.GroupSearch.Filter, userDN)
}
func interpolateSearchFilter(filterFormat, valueToInterpolateIntoFilter string) string {
filter := strings.ReplaceAll(filterFormat, searchFilterInterpolationLocationMarker, valueToInterpolateIntoFilter)
if strings.HasPrefix(filter, "(") && strings.HasSuffix(filter, ")") { if strings.HasPrefix(filter, "(") && strings.HasSuffix(filter, ")") {
return filter return filter
} }
@ -375,12 +469,12 @@ func (p *Provider) escapeUsernameForSearchFilter(username string) string {
return ldap.EscapeFilter(username) return ldap.EscapeFilter(username)
} }
func (p *Provider) getSearchResultAttributeValue(attributeName string, fromUserEntry *ldap.Entry, username string) (string, error) { func (p *Provider) getSearchResultAttributeValue(attributeName string, entry *ldap.Entry, username string) (string, error) {
if attributeName == distinguishedNameAttributeName { if attributeName == distinguishedNameAttributeName {
return fromUserEntry.DN, nil return entry.DN, nil
} }
attributeValues := fromUserEntry.GetAttributeValues(attributeName) attributeValues := entry.GetAttributeValues(attributeName)
if len(attributeValues) != 1 { if len(attributeValues) != 1 {
return "", fmt.Errorf(`found %d values for attribute "%s" while searching for user "%s", but expected 1 result`, return "", fmt.Errorf(`found %d values for attribute "%s" while searching for user "%s", but expected 1 result`,

View File

@ -24,22 +24,32 @@ import (
) )
const ( const (
testHost = "ldap.example.com:8443" testHost = "ldap.example.com:8443"
testBindUsername = "cn=some-bind-username,dc=pinniped,dc=dev" testBindUsername = "cn=some-bind-username,dc=pinniped,dc=dev"
testBindPassword = "some-bind-password" testBindPassword = "some-bind-password"
testUpstreamUsername = "some-upstream-username" testUpstreamUsername = "some-upstream-username"
testUpstreamPassword = "some-upstream-password" testUpstreamPassword = "some-upstream-password"
testUserSearchBase = "some-upstream-base-dn" testUserSearchBase = "some-upstream-user-base-dn"
testUserSearchFilter = "some-filter={}-and-more-filter={}" testGroupSearchBase = "some-upstream-group-base-dn"
testUserSearchUsernameAttribute = "some-upstream-username-attribute" testUserSearchFilter = "some-user-filter={}-and-more-filter={}"
testUserSearchUIDAttribute = "some-upstream-uid-attribute" testGroupSearchFilter = "some-group-filter={}-and-more-filter={}"
testSearchResultDNValue = "some-upstream-user-dn" testUserSearchUsernameAttribute = "some-upstream-username-attribute"
testSearchResultUsernameAttributeValue = "some-upstream-username-value" testUserSearchUIDAttribute = "some-upstream-uid-attribute"
testSearchResultUIDAttributeValue = "some-upstream-uid-value" testGroupSearchGroupNameAttribute = "some-upstream-group-name-attribute"
testUserSearchResultDNValue = "some-upstream-user-dn"
testGroupSearchResultDNValue1 = "some-upstream-group-dn1"
testGroupSearchResultDNValue2 = "some-upstream-group-dn2"
testUserSearchResultUsernameAttributeValue = "some-upstream-username-value"
testUserSearchResultUIDAttributeValue = "some-upstream-uid-value"
testGroupSearchResultGroupNameAttributeValue1 = "some-upstream-group-name-value1"
testGroupSearchResultGroupNameAttributeValue2 = "some-upstream-group-name-value2"
expectedGroupSearchPageSize = uint32(250)
) )
var ( var (
testUserSearchFilterInterpolated = fmt.Sprintf("(some-filter=%s-and-more-filter=%s)", testUpstreamUsername, testUpstreamUsername) testUserSearchFilterInterpolated = fmt.Sprintf("(some-user-filter=%s-and-more-filter=%s)", testUpstreamUsername, testUpstreamUsername)
testGroupSearchFilterInterpolated = fmt.Sprintf("(some-group-filter=%s-and-more-filter=%s)", testUserSearchResultDNValue, testUserSearchResultDNValue)
) )
func TestEndUserAuthentication(t *testing.T) { func TestEndUserAuthentication(t *testing.T) {
@ -56,6 +66,11 @@ func TestEndUserAuthentication(t *testing.T) {
UsernameAttribute: testUserSearchUsernameAttribute, UsernameAttribute: testUserSearchUsernameAttribute,
UIDAttribute: testUserSearchUIDAttribute, UIDAttribute: testUserSearchUIDAttribute,
}, },
GroupSearch: GroupSearchConfig{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
GroupNameAttribute: testGroupSearchGroupNameAttribute,
},
} }
if editFunc != nil { if editFunc != nil {
editFunc(config) editFunc(config)
@ -63,7 +78,7 @@ func TestEndUserAuthentication(t *testing.T) {
return config return config
} }
expectedSearch := func(editFunc func(r *ldap.SearchRequest)) *ldap.SearchRequest { expectedUserSearch := func(editFunc func(r *ldap.SearchRequest)) *ldap.SearchRequest {
request := &ldap.SearchRequest{ request := &ldap.SearchRequest{
BaseDN: testUserSearchBase, BaseDN: testUserSearchBase,
Scope: ldap.ScopeWholeSubtree, Scope: ldap.ScopeWholeSubtree,
@ -73,7 +88,7 @@ func TestEndUserAuthentication(t *testing.T) {
TypesOnly: false, TypesOnly: false,
Filter: testUserSearchFilterInterpolated, Filter: testUserSearchFilterInterpolated,
Attributes: []string{testUserSearchUsernameAttribute, testUserSearchUIDAttribute}, Attributes: []string{testUserSearchUsernameAttribute, testUserSearchUIDAttribute},
Controls: nil, Controls: nil, // don't need paging because we set the SizeLimit so small
} }
if editFunc != nil { if editFunc != nil {
editFunc(request) editFunc(request)
@ -81,6 +96,68 @@ func TestEndUserAuthentication(t *testing.T) {
return request return request
} }
expectedGroupSearch := func(editFunc func(r *ldap.SearchRequest)) *ldap.SearchRequest {
request := &ldap.SearchRequest{
BaseDN: testGroupSearchBase,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
SizeLimit: 0, // unlimited size because we will search with paging
TimeLimit: 90,
TypesOnly: false,
Filter: testGroupSearchFilterInterpolated,
Attributes: []string{testGroupSearchGroupNameAttribute},
Controls: nil, // nil because ldap.SearchWithPaging() will set the appropriate controls for us
}
if editFunc != nil {
editFunc(request)
}
return request
}
exampleUserSearchResult := &ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}),
},
},
},
}
exampleGroupSearchResult := &ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue1}),
},
},
{
DN: testGroupSearchResultDNValue2,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue2}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{},
}
// The auth response which matches the exampleUserSearchResult and exampleGroupSearchResult.
expectedAuthResponse := func(editFunc func(r *user.DefaultInfo)) *authenticator.Response {
u := &user.DefaultInfo{
Name: testUserSearchResultUsernameAttributeValue,
UID: testUserSearchResultUIDAttributeValue,
Groups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
}
if editFunc != nil {
editFunc(u)
}
return &authenticator.Response{User: u}
}
tests := []struct { tests := []struct {
name string name string
username string username string
@ -102,31 +179,15 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
Entries: []*ldap.Entry{ conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
{ Return(exampleGroupSearchResult, nil).Times(1)
DN: testSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{}, // TODO are there any response controls that we need to be able to handle?
}, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue,
UID: testSearchResultUIDAttributeValue,
Groups: []string{},
},
}, },
wantAuthResponse: expectedAuthResponse(nil),
}, },
{ {
name: "when the user search filter is already wrapped by parenthesis then it is not wrapped again", name: "when the user search filter is already wrapped by parenthesis then it is not wrapped again",
@ -137,29 +198,53 @@ func TestEndUserAuthentication(t *testing.T) {
}), }),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
Entries: []*ldap.Entry{ conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
{ Return(exampleGroupSearchResult, nil).Times(1)
DN: testSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
},
},
},
}, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
}, },
wantAuthResponse: &authenticator.Response{ wantAuthResponse: expectedAuthResponse(nil),
User: &user.DefaultInfo{ },
Name: testSearchResultUsernameAttributeValue, {
UID: testSearchResultUIDAttributeValue, name: "when the group search filter is already wrapped by parenthesis then it is not wrapped again",
Groups: []string{}, username: testUpstreamUsername,
}, password: testUpstreamPassword,
providerConfig: providerConfig(func(p *ProviderConfig) {
p.GroupSearch.Filter = "(" + testGroupSearchFilter + ")"
}),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: expectedAuthResponse(nil),
},
{
name: "when the group search base is empty then skip the group search entirely",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(func(p *ProviderConfig) {
p.GroupSearch.Base = "" // this configuration means that the user does not want group search to happen
}),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
r.Groups = []string{}
}),
}, },
{ {
name: "when the UsernameAttribute is dn and there is a user search filter provided", name: "when the UsernameAttribute is dn and there is a user search filter provided",
@ -170,30 +255,28 @@ func TestEndUserAuthentication(t *testing.T) {
}), }),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(func(r *ldap.SearchRequest) { conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) {
r.Attributes = []string{testUserSearchUIDAttribute} r.Attributes = []string{testUserSearchUIDAttribute}
})).Return(&ldap.SearchResult{ })).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}),
}, },
}, },
}, },
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: testSearchResultDNValue,
UID: testSearchResultUIDAttributeValue,
Groups: []string{},
},
}, },
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
r.Name = testUserSearchResultDNValue
}),
}, },
{ {
name: "when the UIDAttribute is dn", name: "when the UIDAttribute is dn",
@ -204,33 +287,92 @@ func TestEndUserAuthentication(t *testing.T) {
}), }),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(func(r *ldap.SearchRequest) { conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) {
r.Attributes = []string{testUserSearchUsernameAttribute} r.Attributes = []string{testUserSearchUsernameAttribute}
})).Return(&ldap.SearchResult{ })).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
}, },
}, },
}, },
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue,
UID: testSearchResultDNValue,
Groups: []string{},
},
}, },
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
r.UID = testUserSearchResultDNValue
}),
}, },
{ {
name: "when Filter is blank it derives a search filter from the UsernameAttribute", name: "when the GroupNameAttribute is dn",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(func(p *ProviderConfig) {
p.GroupSearch.GroupNameAttribute = "dn"
}),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
r.Attributes = []string{}
}), expectedGroupSearchPageSize).
Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
r.Groups = []string{testGroupSearchResultDNValue1, testGroupSearchResultDNValue2}
}),
},
{
name: "when the GroupNameAttribute is empty then it defaults to cn",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(func(p *ProviderConfig) {
p.GroupSearch.GroupNameAttribute = "" // blank means to use cn
}),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
r.Attributes = []string{"cn"}
}), expectedGroupSearchPageSize).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute("cn", []string{testGroupSearchResultGroupNameAttributeValue1}),
},
},
{
DN: testGroupSearchResultDNValue2,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute("cn", []string{testGroupSearchResultGroupNameAttributeValue2}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{},
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: expectedAuthResponse(nil),
},
{
name: "when user search Filter is blank it derives a search filter from the UsernameAttribute",
username: testUpstreamUsername, username: testUpstreamUsername,
password: testUpstreamPassword, password: testUpstreamPassword,
providerConfig: providerConfig(func(p *ProviderConfig) { providerConfig: providerConfig(func(p *ProviderConfig) {
@ -238,62 +380,101 @@ func TestEndUserAuthentication(t *testing.T) {
}), }),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(func(r *ldap.SearchRequest) { conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) {
r.Filter = "(" + testUserSearchUsernameAttribute + "=" + testUpstreamUsername + ")" r.Filter = "(" + testUserSearchUsernameAttribute + "=" + testUpstreamUsername + ")"
})).Return(&ldap.SearchResult{ })).Return(exampleUserSearchResult, nil).Times(1)
Entries: []*ldap.Entry{ conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
{ Return(exampleGroupSearchResult, nil).Times(1)
DN: testSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
},
},
},
}, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue,
UID: testSearchResultUIDAttributeValue,
Groups: []string{},
},
}, },
wantAuthResponse: expectedAuthResponse(nil),
}, },
{ {
name: "when the username has special LDAP search filter characters then they must be properly escaped in the search filter", name: "when group search Filter is blank it uses a default search filter of member={}",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(func(p *ProviderConfig) {
p.GroupSearch.Filter = ""
}),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
r.Filter = "(member=" + testUserSearchResultDNValue + ")"
}), expectedGroupSearchPageSize).
Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: expectedAuthResponse(nil),
},
{
name: "when the username has special LDAP search filter characters then they must be properly escaped in the search filter, because the username is end-user input",
username: `a&b|c(d)e\f*g`, username: `a&b|c(d)e\f*g`,
password: testUpstreamPassword, password: testUpstreamPassword,
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(func(r *ldap.SearchRequest) { conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) {
r.Filter = fmt.Sprintf("(some-filter=%s-and-more-filter=%s)", `a&b|c\28d\29e\5cf\2ag`, `a&b|c\28d\29e\5cf\2ag`) r.Filter = fmt.Sprintf("(some-user-filter=%s-and-more-filter=%s)", `a&b|c\28d\29e\5cf\2ag`, `a&b|c\28d\29e\5cf\2ag`)
})).Return(&ldap.SearchResult{ })).Return(exampleUserSearchResult, nil).Times(1)
Entries: []*ldap.Entry{ conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
{ Return(exampleGroupSearchResult, nil).Times(1)
DN: testSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
},
},
},
}, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
},
wantAuthResponse: expectedAuthResponse(nil),
},
{
name: "group names are sorted to make the result more stable/predictable",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{"c"}),
},
},
{
DN: testGroupSearchResultDNValue2,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{"a"}),
},
},
{
DN: testGroupSearchResultDNValue2,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{"b"}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{},
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
}, },
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue, Name: testUserSearchResultUsernameAttributeValue,
UID: testSearchResultUIDAttributeValue, UID: testUserSearchResultUIDAttributeValue,
Groups: []string{}, Groups: []string{"a", "b", "c"},
}, },
}, },
}, },
@ -334,10 +515,24 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(nil, errors.New("some search error")).Times(1) conn.EXPECT().Search(expectedUserSearch(nil)).Return(nil, errors.New("some user search error")).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantError: fmt.Sprintf(`error searching for user "%s": some search error`, testUpstreamUsername), wantError: fmt.Sprintf(`error searching for user "%s": some user search error`, testUpstreamUsername),
},
{
name: "when searching for the user's groups returns an error",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(nil, errors.New("some group search error")).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`error searching for group memberships for user with DN "%s": some group search error`, testUserSearchResultDNValue),
}, },
{ {
name: "when searching for the user returns no results", name: "when searching for the user returns no results",
@ -346,7 +541,7 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{}, Entries: []*ldap.Entry{},
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
@ -360,9 +555,9 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{DN: testSearchResultDNValue}, {DN: testUserSearchResultDNValue},
{DN: "some-other-dn"}, {DN: "some-other-dn"},
}, },
}, nil).Times(1) }, nil).Times(1)
@ -377,7 +572,7 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{DN: ""}, {DN: ""},
}, },
@ -386,6 +581,39 @@ func TestEndUserAuthentication(t *testing.T) {
}, },
wantError: fmt.Sprintf(`searching for user "%s" resulted in search result without DN`, testUpstreamUsername), wantError: fmt.Sprintf(`searching for user "%s" resulted in search result without DN`, testUpstreamUsername),
}, },
{
name: "when searching for the user's groups returns a group without a DN",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue1}),
},
},
{
DN: "",
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue2}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{},
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
`searching for group memberships for user with DN "%s" resulted in search result without DN`,
testUserSearchResultDNValue),
},
{ {
name: "when searching for the user returns a user without an expected username attribute", name: "when searching for the user returns a user without an expected username attribute",
username: testUpstreamUsername, username: testUpstreamUsername,
@ -393,19 +621,54 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}),
}, },
}, },
}, },
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantError: fmt.Sprintf(`found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUsernameAttribute, testUpstreamUsername), wantError: fmt.Sprintf(
`found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchUsernameAttribute, testUpstreamUsername),
},
{
name: "when searching for the group memberships returns a group without an expected group name attribute",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue1}),
},
},
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute("unrelated attribute", []string{"anything"}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{},
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
`error searching for group memberships for user with DN "%s": found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchResultDNValue, testGroupSearchGroupNameAttribute, testUserSearchResultDNValue),
}, },
{ {
name: "when searching for the user returns a user with too many values for the expected username attribute", name: "when searching for the user returns a user with too many values for the expected username attribute",
@ -414,23 +677,61 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{ ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{
testSearchResultUsernameAttributeValue, testUserSearchResultUsernameAttributeValue,
"unexpected-additional-value", "unexpected-additional-value",
}), }),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}),
}, },
}, },
}, },
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantError: fmt.Sprintf(`found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUsernameAttribute, testUpstreamUsername), wantError: fmt.Sprintf(
`found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchUsernameAttribute, testUpstreamUsername),
},
{
name: "when searching for the group memberships returns a group with too many values for the expected group name attribute",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue1}),
},
},
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{
testGroupSearchResultGroupNameAttributeValue1,
"unexpected-additional-value",
}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{},
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
`error searching for group memberships for user with DN "%s": found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchResultDNValue, testGroupSearchGroupNameAttribute, testUserSearchResultDNValue),
}, },
{ {
name: "when searching for the user returns a user with an empty value for the expected username attribute", name: "when searching for the user returns a user with an empty value for the expected username attribute",
@ -439,20 +740,55 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{""}), ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{""}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}),
}, },
}, },
}, },
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantError: fmt.Sprintf(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`, testUserSearchUsernameAttribute, testUpstreamUsername), wantError: fmt.Sprintf(
`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`,
testUserSearchUsernameAttribute, testUpstreamUsername),
},
{
name: "when searching for the group memberships returns a group with an empty value for for the expected group name attribute",
username: testUpstreamUsername,
password: testUpstreamPassword,
providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{testGroupSearchResultGroupNameAttributeValue1}),
},
},
{
DN: testGroupSearchResultDNValue1,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testGroupSearchGroupNameAttribute, []string{""}),
},
},
},
Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{},
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
`error searching for group memberships for user with DN "%s": found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`,
testUserSearchResultDNValue, testGroupSearchGroupNameAttribute, testUserSearchResultDNValue),
}, },
{ {
name: "when searching for the user returns a user without an expected UID attribute", name: "when searching for the user returns a user without an expected UID attribute",
@ -461,12 +797,12 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
}, },
}, },
}, },
@ -482,14 +818,14 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{ ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{
testSearchResultUIDAttributeValue, testUserSearchResultUIDAttributeValue,
"unexpected-additional-value", "unexpected-additional-value",
}), }),
}, },
@ -507,12 +843,12 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testSearchResultDNValue, DN: testUserSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{""}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{""}),
}, },
}, },
@ -529,24 +865,16 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
Entries: []*ldap.Entry{ conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
{ Return(exampleGroupSearchResult, nil).Times(1)
DN: testSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
},
},
},
}, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Return(errors.New("some bind error")).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Return(errors.New("some bind error")).Times(1)
}, },
skipDryRunAuthenticateUser: true, skipDryRunAuthenticateUser: true,
wantError: fmt.Sprintf(`error binding for user "%s" using provided password against DN "%s": some bind error`, testUpstreamUsername, testSearchResultDNValue), wantError: fmt.Sprintf(`error binding for user "%s" using provided password against DN "%s": some bind error`, testUpstreamUsername, testUserSearchResultDNValue),
}, },
{ {
name: "when binding as the found user returns a specific invalid credentials error", name: "when binding as the found user returns a specific invalid credentials error",
@ -555,17 +883,9 @@ func TestEndUserAuthentication(t *testing.T) {
providerConfig: providerConfig(nil), providerConfig: providerConfig(nil),
searchMocks: func(conn *mockldapconn.MockConn) { searchMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
Entries: []*ldap.Entry{ conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
{ Return(exampleGroupSearchResult, nil).Times(1)
DN: testSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
},
},
},
}, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantUnauthenticated: true, wantUnauthenticated: true,
@ -575,7 +895,7 @@ func TestEndUserAuthentication(t *testing.T) {
Err: errors.New("some bind error"), Err: errors.New("some bind error"),
ResultCode: ldap.LDAPResultInvalidCredentials, ResultCode: ldap.LDAPResultInvalidCredentials,
} }
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Return(err).Times(1) conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Return(err).Times(1)
}, },
}, },
{ {

View File

@ -278,7 +278,7 @@ func TestE2EFullIntegration(t *testing.T) {
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands. // Add an LDAP upstream IDP and try using it to authenticate during kubectl commands.
t.Run("with Supervisor LDAP upstream IDP", func(t *testing.T) { t.Run("with Supervisor LDAP upstream IDP", func(t *testing.T) {
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
expectedGroups := []string{} // LDAP groups are not implemented yet expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs
// Create a ClusterRoleBinding to give our test user from the upstream read-only access to the cluster. // Create a ClusterRoleBinding to give our test user from the upstream read-only access to the cluster.
library.CreateTestClusterRoleBinding(t, library.CreateTestClusterRoleBinding(t,
@ -317,6 +317,13 @@ func TestE2EFullIntegration(t *testing.T) {
UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName, UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName,
}, },
}, },
GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{
Base: env.SupervisorUpstreamLDAP.GroupSearchBase,
Filter: "", // use the default value of "member={}"
Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{
GroupName: "", // use the default value of "cn"
},
},
}, idpv1alpha1.LDAPPhaseReady) }, idpv1alpha1.LDAPPhaseReady)
// Use a specific session cache for this test. // Use a specific session cache for this test.
@ -438,17 +445,10 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
for _, g := range expectedGroups { for _, g := range expectedGroups {
expectedGroupsAsEmptyInterfaces = append(expectedGroupsAsEmptyInterfaces, g) expectedGroupsAsEmptyInterfaces = append(expectedGroupsAsEmptyInterfaces, g)
} }
require.Equal(t, expectedGroupsAsEmptyInterfaces, idTokenClaims[oidc.DownstreamGroupsClaim]) require.ElementsMatch(t, expectedGroupsAsEmptyInterfaces, idTokenClaims[oidc.DownstreamGroupsClaim])
expectedYAMLGroups := func() string { expectedGroupsPlusAuthenticated := append([]string{}, expectedGroups...)
var b strings.Builder expectedGroupsPlusAuthenticated = append(expectedGroupsPlusAuthenticated, "system:authenticated")
for _, g := range expectedGroups {
b.WriteString("\n")
b.WriteString(` - `)
b.WriteString(g)
}
return b.String()
}()
// Confirm we are the right user according to Kube by calling the whoami API. // Confirm we are the right user according to Kube by calling the whoami API.
kubectlCmd3 := exec.CommandContext(ctx, "kubectl", "create", "-f", "-", "-o", "yaml", "--kubeconfig", kubeconfigPath) kubectlCmd3 := exec.CommandContext(ctx, "kubectl", "create", "-f", "-", "-o", "yaml", "--kubeconfig", kubeconfigPath)
@ -456,28 +456,15 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
kubectlCmd3.Stdin = strings.NewReader(here.Docf(` kubectlCmd3.Stdin = strings.NewReader(here.Docf(`
apiVersion: identity.concierge.%s/v1alpha1 apiVersion: identity.concierge.%s/v1alpha1
kind: WhoAmIRequest kind: WhoAmIRequest
`, env.APIGroupSuffix)) `, env.APIGroupSuffix))
kubectlOutput3, err := kubectlCmd3.CombinedOutput() kubectlOutput3, err := kubectlCmd3.CombinedOutput()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, here.Docf(` whoAmI := deserializeWhoAmIRequest(t, string(kubectlOutput3), env.APIGroupSuffix)
apiVersion: identity.concierge.%s/v1alpha1 require.Equal(t, expectedUsername, whoAmI.Status.KubernetesUserInfo.User.Username)
kind: WhoAmIRequest require.ElementsMatch(t, expectedGroupsPlusAuthenticated, whoAmI.Status.KubernetesUserInfo.User.Groups)
metadata:
creationTimestamp: null
spec: {}
status:
kubernetesUserInfo:
user:
groups:%s
- system:authenticated
username: %s
`, env.APIGroupSuffix, expectedYAMLGroups, expectedUsername),
string(kubectlOutput3))
expectedGroupsPlusAuthenticated := append([]string{}, expectedGroups...)
expectedGroupsPlusAuthenticated = append(expectedGroupsPlusAuthenticated, "system:authenticated")
// Validate that `pinniped whoami` returns the correct identity. // Validate that `pinniped whoami` returns the correct identity.
assertWhoami( assertWhoami(
ctx, ctx,

View File

@ -15,7 +15,6 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
@ -70,7 +69,7 @@ func TestLDAPSearch(t *testing.T) {
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(nil)), provider: upstreamldap.New(*providerConfig(nil)),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -79,7 +78,7 @@ func TestLDAPSearch(t *testing.T) {
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "dc=pinniped,dc=dev" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "dc=pinniped,dc=dev" })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -88,7 +87,7 @@ func TestLDAPSearch(t *testing.T) {
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "(cn={})" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "(cn={})" })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -100,7 +99,7 @@ func TestLDAPSearch(t *testing.T) {
p.UserSearch.Filter = "cn={}" p.UserSearch.Filter = "cn={}"
})), })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: "1000", Groups: []string{}}, User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -111,7 +110,7 @@ func TestLDAPSearch(t *testing.T) {
p.UserSearch.Filter = "(|(cn={})(mail={}))" p.UserSearch.Filter = "(|(cn={})(mail={}))"
})), })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -122,7 +121,7 @@ func TestLDAPSearch(t *testing.T) {
p.UserSearch.Filter = "(|(cn={})(mail={}))" p.UserSearch.Filter = "(|(cn={})(mail={}))"
})), })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -131,7 +130,7 @@ func TestLDAPSearch(t *testing.T) {
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "dn" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "dn" })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "cn=pinny,ou=users,dc=pinniped,dc=dev", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "cn=pinny,ou=users,dc=pinniped,dc=dev", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -140,7 +139,7 @@ func TestLDAPSearch(t *testing.T) {
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "sn" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "sn" })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "Seal", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "Seal", Groups: []string{"ball-game-players", "seals"}},
}, },
}, },
{ {
@ -149,7 +148,7 @@ func TestLDAPSearch(t *testing.T) {
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UsernameAttribute = "sn" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UsernameAttribute = "sn" })),
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "Seal", UID: "1000", Groups: []string{}}, // note that the final answer has case preserved from the entry User: &user.DefaultInfo{Name: "Seal", UID: "1000", Groups: []string{"ball-game-players", "seals"}}, // note that the final answer has case preserved from the entry
}, },
}, },
{ {
@ -162,6 +161,75 @@ func TestLDAPSearch(t *testing.T) {
})), })),
wantError: `must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`, wantError: `must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`,
}, },
{
name: "group search disabled",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
p.GroupSearch.Base = ""
})),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{
name: "group search base causes no groups to be found for user",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
p.GroupSearch.Base = "ou=users,dc=pinniped,dc=dev" // there are no groups under this part of the tree
})),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{
name: "using dn as the group name attribute",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
p.GroupSearch.GroupNameAttribute = "dn"
})),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{
"cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev",
"cn=seals,ou=groups,dc=pinniped,dc=dev",
}},
},
},
{
name: "using some other custom group name attribute",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
p.GroupSearch.GroupNameAttribute = "objectClass" // silly example, but still a meaningful test
})),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"groupOfNames", "groupOfNames"}},
},
},
{
name: "using a more complex group search filter",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
p.GroupSearch.Filter = "(&(&(objectClass=groupOfNames)(member={}))(cn=seals))"
})),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"seals"}},
},
},
{
name: "using a group filter which causes no groups to be found for the user",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
p.GroupSearch.Filter = "foobar={}" // foobar is not a valid attribute name for this LDAP server's schema
})),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{ {
name: "when the bind user username is not a valid DN", name: "when the bind user username is not a valid DN",
username: "pinny", username: "pinny",
@ -211,6 +279,13 @@ func TestLDAPSearch(t *testing.T) {
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "*" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "*" })),
wantError: `error searching for user "pinny": LDAP Result Code 201 "Filter Compile Error": ldap: error parsing filter`, wantError: `error searching for user "pinny": LDAP Result Code 201 "Filter Compile Error": ldap: error parsing filter`,
}, },
{
name: "when the group search filter does not compile",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.Filter = "*" })),
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 201 "Filter Compile Error": ldap: error parsing filter`,
},
{ {
name: "when there are too many search results for the user", name: "when there are too many search results for the user",
username: "pinny", username: "pinny",
@ -293,6 +368,13 @@ func TestLDAPSearch(t *testing.T) {
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "SN" })), // this is case-sensitive provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "SN" })), // this is case-sensitive
wantError: `found 0 values for attribute "SN" while searching for user "pinny", but expected 1 result`, wantError: `found 0 values for attribute "SN" while searching for user "pinny", but expected 1 result`,
}, },
{
name: "when the GroupNameAttribute has the wrong case",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.GroupNameAttribute = "CN" })), // this is case-sensitive
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": found 0 values for attribute "CN" while searching for user "cn=pinny,ou=users,dc=pinniped,dc=dev", but expected 1 result`,
},
{ {
name: "when the UsernameAttribute is DN and has the wrong case", name: "when the UsernameAttribute is DN and has the wrong case",
username: "pinny", username: "pinny",
@ -313,21 +395,44 @@ func TestLDAPSearch(t *testing.T) {
wantError: `found 0 values for attribute "DN" while searching for user "pinny", but expected 1 result`, wantError: `found 0 values for attribute "DN" while searching for user "pinny", but expected 1 result`,
}, },
{ {
name: "when the search base is invalid", name: "when the GroupNameAttribute is DN and has the wrong case",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
p.GroupSearch.GroupNameAttribute = "DN" // dn must be lower-case
})),
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": found 0 values for attribute "DN" while searching for user "cn=pinny,ou=users,dc=pinniped,dc=dev", but expected 1 result`,
},
{
name: "when the user search base is invalid",
username: "pinny", username: "pinny",
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "invalid-base" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "invalid-base" })),
wantError: `error searching for user "pinny": LDAP Result Code 34 "Invalid DN Syntax": invalid DN`, wantError: `error searching for user "pinny": LDAP Result Code 34 "Invalid DN Syntax": invalid DN`,
}, },
{ {
name: "when the search base does not exist", name: "when the group search base is invalid",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.Base = "invalid-base" })),
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 34 "Invalid DN Syntax": invalid DN`,
},
{
name: "when the user search base does not exist",
username: "pinny", username: "pinny",
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "ou=does-not-exist,dc=pinniped,dc=dev" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "ou=does-not-exist,dc=pinniped,dc=dev" })),
wantError: `error searching for user "pinny": LDAP Result Code 32 "No Such Object": `, wantError: `error searching for user "pinny": LDAP Result Code 32 "No Such Object": `,
}, },
{ {
name: "when the search base causes no search results", name: "when the group search base does not exist",
username: "pinny",
password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.Base = "ou=does-not-exist,dc=pinniped,dc=dev" })),
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 32 "No Such Object": `,
},
{
name: "when the user search base causes no search results",
username: "pinny", username: "pinny",
password: pinnyPassword, password: pinnyPassword,
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "ou=groups,dc=pinniped,dc=dev" })), provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "ou=groups,dc=pinniped,dc=dev" })),
@ -425,7 +530,7 @@ func TestSimultaneousRequestsOnSingleProvider(t *testing.T) {
assert.NoError(t, result.err) assert.NoError(t, result.err)
assert.True(t, result.authenticated, "expected the user to be authenticated, but they were not") assert.True(t, result.authenticated, "expected the user to be authenticated, but they were not")
assert.Equal(t, &authenticator.Response{ assert.Equal(t, &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}}, User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
}, result.response) }, result.response)
} }
} }
@ -449,6 +554,11 @@ func defaultProviderConfig(env *library.TestEnv, ldapHostPort string) *upstreaml
UsernameAttribute: "cn", UsernameAttribute: "cn",
UIDAttribute: "uidNumber", UIDAttribute: "uidNumber",
}, },
GroupSearch: upstreamldap.GroupSearchConfig{
Base: "ou=groups,dc=pinniped,dc=dev",
Filter: "", // defaults to member={}
GroupNameAttribute: "", // defaults to cn
},
} }
} }

View File

@ -45,6 +45,7 @@ func TestSupervisorLogin(t *testing.T) {
requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client) requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client)
wantDownstreamIDTokenSubjectToMatch string wantDownstreamIDTokenSubjectToMatch string
wantDownstreamIDTokenUsernameToMatch string wantDownstreamIDTokenUsernameToMatch string
wantDownstreamIDTokenGroups []string
}{ }{
{ {
name: "oidc", name: "oidc",
@ -65,9 +66,10 @@ func TestSupervisorLogin(t *testing.T) {
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+",
// the ID token Username should include the upstream user ID after the upstream issuer name // the ID token Username should include the upstream user ID after the upstream issuer name
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+",
wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups,
}, },
{ {
name: "ldap with email as username", name: "ldap with email as username and groups names as DNs",
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) {
t.Helper() t.Helper()
secret := library.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, secret := library.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
@ -92,6 +94,13 @@ func TestSupervisorLogin(t *testing.T) {
UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName, UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName,
}, },
}, },
GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{
Base: env.SupervisorUpstreamLDAP.GroupSearchBase,
Filter: "",
Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{
GroupName: "dn",
},
},
}, idpv1alpha1.LDAPPhaseReady) }, idpv1alpha1.LDAPPhaseReady)
expectedMsg := fmt.Sprintf( expectedMsg := fmt.Sprintf(
`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`, `successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
@ -114,9 +123,10 @@ func TestSupervisorLogin(t *testing.T) {
), ),
// 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
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue), wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue),
wantDownstreamIDTokenGroups: env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs,
}, },
{ {
name: "ldap with CN as username ", // try another variation of configuration options name: "ldap with CN as username and group names as CNs", // try another variation of configuration options
createIDP: func(t *testing.T) { createIDP: func(t *testing.T) {
t.Helper() t.Helper()
secret := library.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth, secret := library.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
@ -141,6 +151,13 @@ func TestSupervisorLogin(t *testing.T) {
UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName, UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName,
}, },
}, },
GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{
Base: env.SupervisorUpstreamLDAP.GroupSearchBase,
Filter: "",
Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{
GroupName: "cn",
},
},
}, idpv1alpha1.LDAPPhaseReady) }, idpv1alpha1.LDAPPhaseReady)
expectedMsg := fmt.Sprintf( expectedMsg := fmt.Sprintf(
`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`, `successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
@ -163,6 +180,7 @@ func TestSupervisorLogin(t *testing.T) {
), ),
// 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
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserDN), wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserDN),
wantDownstreamIDTokenGroups: env.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs,
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -173,6 +191,7 @@ func TestSupervisorLogin(t *testing.T) {
test.requestAuthorization, test.requestAuthorization,
test.wantDownstreamIDTokenSubjectToMatch, test.wantDownstreamIDTokenSubjectToMatch,
test.wantDownstreamIDTokenUsernameToMatch, test.wantDownstreamIDTokenUsernameToMatch,
test.wantDownstreamIDTokenGroups,
) )
}) })
} }
@ -207,7 +226,7 @@ func testSupervisorLogin(
t *testing.T, t *testing.T,
createIDP func(t *testing.T), createIDP func(t *testing.T),
requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client), requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client),
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string,
) { ) {
env := library.IntegrationEnv(t) env := library.IntegrationEnv(t)
@ -350,7 +369,7 @@ func testSupervisorLogin(
expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "username", "groups"} expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "username", "groups"}
verifyTokenResponse(t, verifyTokenResponse(t,
tokenResponse, discovery, downstreamOAuth2Config, nonceParam, tokenResponse, discovery, downstreamOAuth2Config, nonceParam,
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch) expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch, wantDownstreamIDTokenGroups)
// token exchange on the original token // token exchange on the original token
doTokenExchange(t, &downstreamOAuth2Config, tokenResponse, httpClient, discovery) doTokenExchange(t, &downstreamOAuth2Config, tokenResponse, httpClient, discovery)
@ -363,7 +382,7 @@ func testSupervisorLogin(
expectedIDTokenClaims = append(expectedIDTokenClaims, "at_hash") expectedIDTokenClaims = append(expectedIDTokenClaims, "at_hash")
verifyTokenResponse(t, verifyTokenResponse(t,
refreshedTokenResponse, discovery, downstreamOAuth2Config, "", refreshedTokenResponse, discovery, downstreamOAuth2Config, "",
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch) expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch, wantDownstreamIDTokenGroups)
require.NotEqual(t, tokenResponse.AccessToken, refreshedTokenResponse.AccessToken) require.NotEqual(t, tokenResponse.AccessToken, refreshedTokenResponse.AccessToken)
require.NotEqual(t, tokenResponse.RefreshToken, refreshedTokenResponse.RefreshToken) require.NotEqual(t, tokenResponse.RefreshToken, refreshedTokenResponse.RefreshToken)
@ -380,7 +399,7 @@ func verifyTokenResponse(
downstreamOAuth2Config oauth2.Config, downstreamOAuth2Config oauth2.Config,
nonceParam nonce.Nonce, nonceParam nonce.Nonce,
expectedIDTokenClaims []string, expectedIDTokenClaims []string,
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string,
) { ) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute) ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel() defer cancel()
@ -415,6 +434,9 @@ func verifyTokenResponse(
// Check username claim of the ID token. // Check username claim of the ID token.
require.Regexp(t, wantDownstreamIDTokenUsernameToMatch, idTokenClaims["username"].(string)) require.Regexp(t, wantDownstreamIDTokenUsernameToMatch, idTokenClaims["username"].(string))
// Check the groups claim.
require.ElementsMatch(t, wantDownstreamIDTokenGroups, idTokenClaims["groups"])
// Some light verification of the other tokens that were returned. // Some light verification of the other tokens that were returned.
require.NotEmpty(t, tokenResponse.AccessToken) require.NotEmpty(t, tokenResponse.AccessToken)
require.Equal(t, "bearer", tokenResponse.TokenType) require.Equal(t, "bearer", tokenResponse.TokenType)

View File

@ -7,6 +7,7 @@ import (
"encoding/base64" "encoding/base64"
"io/ioutil" "io/ioutil"
"os" "os"
"sort"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@ -71,18 +72,21 @@ type TestOIDCUpstream struct {
} }
type TestLDAPUpstream struct { type TestLDAPUpstream struct {
Host string `json:"host"` Host string `json:"host"`
CABundle string `json:"caBundle"` CABundle string `json:"caBundle"`
BindUsername string `json:"bindUsername"` BindUsername string `json:"bindUsername"`
BindPassword string `json:"bindPassword"` BindPassword string `json:"bindPassword"`
UserSearchBase string `json:"userSearchBase"` UserSearchBase string `json:"userSearchBase"`
TestUserDN string `json:"testUserDN"` GroupSearchBase string `json:"groupSearchBase"`
TestUserCN string `json:"testUserCN"` TestUserDN string `json:"testUserDN"`
TestUserPassword string `json:"testUserPassword"` TestUserCN string `json:"testUserCN"`
TestUserMailAttributeName string `json:"testUserMailAttributeName"` TestUserPassword string `json:"testUserPassword"`
TestUserMailAttributeValue string `json:"testUserMailAttributeValue"` TestUserMailAttributeName string `json:"testUserMailAttributeName"`
TestUserUniqueIDAttributeName string `json:"testUserUniqueIDAttributeName"` TestUserMailAttributeValue string `json:"testUserMailAttributeValue"`
TestUserUniqueIDAttributeValue string `json:"testUserUniqueIDAttributeValue"` TestUserUniqueIDAttributeName string `json:"testUserUniqueIDAttributeName"`
TestUserUniqueIDAttributeValue string `json:"testUserUniqueIDAttributeValue"`
TestUserDirectGroupsCNs []string `json:"testUserDirectGroupsCNs"`
TestUserDirectGroupsDNs []string `json:"testUserDirectGroupsDNs"` //nolint:golint // this is "distinguished names", not "DNS"
} }
// ProxyEnv returns a set of environment variable strings (e.g., to combine with os.Environ()) which set up the configured test HTTP proxy. // ProxyEnv returns a set of environment variable strings (e.g., to combine with os.Environ()) which set up the configured test HTTP proxy.
@ -240,14 +244,20 @@ func loadEnvVars(t *testing.T, result *TestEnv) {
BindUsername: needEnv(t, "PINNIPED_TEST_LDAP_BIND_ACCOUNT_USERNAME"), BindUsername: needEnv(t, "PINNIPED_TEST_LDAP_BIND_ACCOUNT_USERNAME"),
BindPassword: needEnv(t, "PINNIPED_TEST_LDAP_BIND_ACCOUNT_PASSWORD"), BindPassword: needEnv(t, "PINNIPED_TEST_LDAP_BIND_ACCOUNT_PASSWORD"),
UserSearchBase: needEnv(t, "PINNIPED_TEST_LDAP_USERS_SEARCH_BASE"), UserSearchBase: needEnv(t, "PINNIPED_TEST_LDAP_USERS_SEARCH_BASE"),
GroupSearchBase: needEnv(t, "PINNIPED_TEST_LDAP_GROUPS_SEARCH_BASE"),
TestUserDN: needEnv(t, "PINNIPED_TEST_LDAP_USER_DN"), TestUserDN: needEnv(t, "PINNIPED_TEST_LDAP_USER_DN"),
TestUserCN: needEnv(t, "PINNIPED_TEST_LDAP_USER_CN"), TestUserCN: needEnv(t, "PINNIPED_TEST_LDAP_USER_CN"),
TestUserUniqueIDAttributeName: needEnv(t, "PINNIPED_TEST_LDAP_USER_UNIQUE_ID_ATTRIBUTE_NAME"), TestUserUniqueIDAttributeName: needEnv(t, "PINNIPED_TEST_LDAP_USER_UNIQUE_ID_ATTRIBUTE_NAME"),
TestUserUniqueIDAttributeValue: needEnv(t, "PINNIPED_TEST_LDAP_USER_UNIQUE_ID_ATTRIBUTE_VALUE"), TestUserUniqueIDAttributeValue: needEnv(t, "PINNIPED_TEST_LDAP_USER_UNIQUE_ID_ATTRIBUTE_VALUE"),
TestUserMailAttributeName: needEnv(t, "PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_NAME"), TestUserMailAttributeName: needEnv(t, "PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_NAME"),
TestUserMailAttributeValue: needEnv(t, "PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_VALUE"), TestUserMailAttributeValue: needEnv(t, "PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_VALUE"),
TestUserDirectGroupsCNs: filterEmpty(strings.Split(needEnv(t, "PINNIPED_TEST_LDAP_EXPECTED_DIRECT_GROUPS_CN"), ";")),
TestUserDirectGroupsDNs: filterEmpty(strings.Split(needEnv(t, "PINNIPED_TEST_LDAP_EXPECTED_DIRECT_GROUPS_DN"), ";")),
TestUserPassword: needEnv(t, "PINNIPED_TEST_LDAP_USER_PASSWORD"), TestUserPassword: needEnv(t, "PINNIPED_TEST_LDAP_USER_PASSWORD"),
} }
sort.Strings(result.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs)
sort.Strings(result.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs)
} }
func (e *TestEnv) HasCapability(cap Capability) bool { func (e *TestEnv) HasCapability(cap Capability) bool {