Add integration test for upstreamldap.Provider

- The unit tests for upstreamldap.Provider need to mock the LDAP server,
  so add an integration test which allows us to get fast feedback for
  this code against a real LDAP server.
- Automatically wrap the user search filter in parenthesis if it is not
  already wrapped in parens.
- More special handling for using "dn" as the username or UID attribute
  name.
- Also added some more comments to types_ldapidentityprovider.go.tmpl
This commit is contained in:
Ryan Richard 2021-04-13 15:23:14 -07:00
parent 7b8c86b38e
commit fec3d92f26
18 changed files with 845 additions and 68 deletions

View File

@ -54,12 +54,18 @@ type LDAPIdentityProviderBindSpec struct {
type LDAPIdentityProviderUserSearchAttributesSpec struct { type LDAPIdentityProviderUserSearchAttributesSpec struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // 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 // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter. 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
// 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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// UniqueID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely // UniqueID 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". // 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".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
UniqueID string `json:"uniqueID,omitempty"` UniqueID string `json:"uniqueID,omitempty"`
} }
@ -72,8 +78,10 @@ type LDAPIdentityProviderUserSearchSpec struct {
// 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 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. // 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 // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". // 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.
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`

View File

@ -97,15 +97,26 @@ spec:
description: UniqueID specifies the name of the attribute description: UniqueID specifies the name of the attribute
in the LDAP entry which whose value shall be used to uniquely in the LDAP entry which whose value shall be used to uniquely
identify the user within this LDAP provider after a successful identify the user within this LDAP provider after a successful
authentication. E.g. "uidNumber" or "objectGUID". 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".
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 attribute in the
LDAP entry which whose value shall become the username of LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically the user after a successful authentication. This would typically
be the same attribute name used in the user search filter. be the same attribute name used in the user search filter,
E.g. "mail" or "uid" or "userPrincipalName". 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 LDAPIdentityProviderUserSearchSpec's 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
@ -120,9 +131,12 @@ spec:
in the filter and will be dynamically replaced by the username 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 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. For more information about LDAP filters, see https://ldap.com/ldap-filters.
Optional. When not specified, the default will act as if the Note that the dn (distinguished name) is not an attribute of
Filter were specified as the value from Attributes.Username an entry, so "dn={}" cannot be used. Optional. When not specified,
appended by "={}". 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.
type: string type: string
type: object type: object
required: required:

View File

@ -808,8 +808,8 @@ 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. E.g. "mail" or "uid" or "userPrincipalName". | *`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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uniqueID`* __string__ | UniqueID 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". | *`uniqueID`* __string__ | UniqueID 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".
|=== |===
@ -827,7 +827,7 @@ Status of an LDAP identity provider.
|=== |===
| 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 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. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". | *`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.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | 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-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===

View File

@ -54,12 +54,18 @@ type LDAPIdentityProviderBindSpec struct {
type LDAPIdentityProviderUserSearchAttributesSpec struct { type LDAPIdentityProviderUserSearchAttributesSpec struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // 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 // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter. 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
// 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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// UniqueID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely // UniqueID 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". // 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".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
UniqueID string `json:"uniqueID,omitempty"` UniqueID string `json:"uniqueID,omitempty"`
} }
@ -72,8 +78,10 @@ type LDAPIdentityProviderUserSearchSpec struct {
// 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 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. // 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 // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". // 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.
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`

View File

@ -97,15 +97,26 @@ spec:
description: UniqueID specifies the name of the attribute description: UniqueID specifies the name of the attribute
in the LDAP entry which whose value shall be used to uniquely in the LDAP entry which whose value shall be used to uniquely
identify the user within this LDAP provider after a successful identify the user within this LDAP provider after a successful
authentication. E.g. "uidNumber" or "objectGUID". 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".
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 attribute in the
LDAP entry which whose value shall become the username of LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically the user after a successful authentication. This would typically
be the same attribute name used in the user search filter. be the same attribute name used in the user search filter,
E.g. "mail" or "uid" or "userPrincipalName". 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 LDAPIdentityProviderUserSearchSpec's 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
@ -120,9 +131,12 @@ spec:
in the filter and will be dynamically replaced by the username 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 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. For more information about LDAP filters, see https://ldap.com/ldap-filters.
Optional. When not specified, the default will act as if the Note that the dn (distinguished name) is not an attribute of
Filter were specified as the value from Attributes.Username an entry, so "dn={}" cannot be used. Optional. When not specified,
appended by "={}". 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.
type: string type: string
type: object type: object
required: required:

View File

@ -808,8 +808,8 @@ 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. E.g. "mail" or "uid" or "userPrincipalName". | *`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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uniqueID`* __string__ | UniqueID 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". | *`uniqueID`* __string__ | UniqueID 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".
|=== |===
@ -827,7 +827,7 @@ Status of an LDAP identity provider.
|=== |===
| 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 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. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". | *`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.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | 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-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===

View File

@ -54,12 +54,18 @@ type LDAPIdentityProviderBindSpec struct {
type LDAPIdentityProviderUserSearchAttributesSpec struct { type LDAPIdentityProviderUserSearchAttributesSpec struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // 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 // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter. 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
// 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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// UniqueID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely // UniqueID 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". // 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".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
UniqueID string `json:"uniqueID,omitempty"` UniqueID string `json:"uniqueID,omitempty"`
} }
@ -72,8 +78,10 @@ type LDAPIdentityProviderUserSearchSpec struct {
// 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 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. // 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 // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". // 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.
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`

View File

@ -97,15 +97,26 @@ spec:
description: UniqueID specifies the name of the attribute description: UniqueID specifies the name of the attribute
in the LDAP entry which whose value shall be used to uniquely in the LDAP entry which whose value shall be used to uniquely
identify the user within this LDAP provider after a successful identify the user within this LDAP provider after a successful
authentication. E.g. "uidNumber" or "objectGUID". 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".
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 attribute in the
LDAP entry which whose value shall become the username of LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically the user after a successful authentication. This would typically
be the same attribute name used in the user search filter. be the same attribute name used in the user search filter,
E.g. "mail" or "uid" or "userPrincipalName". 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 LDAPIdentityProviderUserSearchSpec's 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
@ -120,9 +131,12 @@ spec:
in the filter and will be dynamically replaced by the username 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 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. For more information about LDAP filters, see https://ldap.com/ldap-filters.
Optional. When not specified, the default will act as if the Note that the dn (distinguished name) is not an attribute of
Filter were specified as the value from Attributes.Username an entry, so "dn={}" cannot be used. Optional. When not specified,
appended by "={}". 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.
type: string type: string
type: object type: object
required: required:

View File

@ -808,8 +808,8 @@ 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. E.g. "mail" or "uid" or "userPrincipalName". | *`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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uniqueID`* __string__ | UniqueID 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". | *`uniqueID`* __string__ | UniqueID 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".
|=== |===
@ -827,7 +827,7 @@ Status of an LDAP identity provider.
|=== |===
| 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 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. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". | *`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.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | 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-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===

View File

@ -54,12 +54,18 @@ type LDAPIdentityProviderBindSpec struct {
type LDAPIdentityProviderUserSearchAttributesSpec struct { type LDAPIdentityProviderUserSearchAttributesSpec struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // 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 // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter. 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
// 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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// UniqueID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely // UniqueID 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". // 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".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
UniqueID string `json:"uniqueID,omitempty"` UniqueID string `json:"uniqueID,omitempty"`
} }
@ -72,8 +78,10 @@ type LDAPIdentityProviderUserSearchSpec struct {
// 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 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. // 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 // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". // 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.
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`

View File

@ -97,15 +97,26 @@ spec:
description: UniqueID specifies the name of the attribute description: UniqueID specifies the name of the attribute
in the LDAP entry which whose value shall be used to uniquely in the LDAP entry which whose value shall be used to uniquely
identify the user within this LDAP provider after a successful identify the user within this LDAP provider after a successful
authentication. E.g. "uidNumber" or "objectGUID". 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".
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 attribute in the
LDAP entry which whose value shall become the username of LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically the user after a successful authentication. This would typically
be the same attribute name used in the user search filter. be the same attribute name used in the user search filter,
E.g. "mail" or "uid" or "userPrincipalName". 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 LDAPIdentityProviderUserSearchSpec's 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
@ -120,9 +131,12 @@ spec:
in the filter and will be dynamically replaced by the username 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 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. For more information about LDAP filters, see https://ldap.com/ldap-filters.
Optional. When not specified, the default will act as if the Note that the dn (distinguished name) is not an attribute of
Filter were specified as the value from Attributes.Username an entry, so "dn={}" cannot be used. Optional. When not specified,
appended by "={}". 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.
type: string type: string
type: object type: object
required: required:

View File

@ -808,8 +808,8 @@ 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. E.g. "mail" or "uid" or "userPrincipalName". | *`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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uniqueID`* __string__ | UniqueID 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". | *`uniqueID`* __string__ | UniqueID 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".
|=== |===
@ -827,7 +827,7 @@ Status of an LDAP identity provider.
|=== |===
| 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 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. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". | *`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.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | 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-ldapidentityproviderusersearchattributesspec[$$LDAPIdentityProviderUserSearchAttributesSpec$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|=== |===

View File

@ -54,12 +54,18 @@ type LDAPIdentityProviderBindSpec struct {
type LDAPIdentityProviderUserSearchAttributesSpec struct { type LDAPIdentityProviderUserSearchAttributesSpec struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // 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 // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter. 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
// 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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// UniqueID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely // UniqueID 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". // 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".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
UniqueID string `json:"uniqueID,omitempty"` UniqueID string `json:"uniqueID,omitempty"`
} }
@ -72,8 +78,10 @@ type LDAPIdentityProviderUserSearchSpec struct {
// 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 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. // 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 // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". // 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.
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`

View File

@ -97,15 +97,26 @@ spec:
description: UniqueID specifies the name of the attribute description: UniqueID specifies the name of the attribute
in the LDAP entry which whose value shall be used to uniquely in the LDAP entry which whose value shall be used to uniquely
identify the user within this LDAP provider after a successful identify the user within this LDAP provider after a successful
authentication. E.g. "uidNumber" or "objectGUID". 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".
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 attribute in the
LDAP entry which whose value shall become the username of LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically the user after a successful authentication. This would typically
be the same attribute name used in the user search filter. be the same attribute name used in the user search filter,
E.g. "mail" or "uid" or "userPrincipalName". 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 LDAPIdentityProviderUserSearchSpec's 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
@ -120,9 +131,12 @@ spec:
in the filter and will be dynamically replaced by the username 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 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. For more information about LDAP filters, see https://ldap.com/ldap-filters.
Optional. When not specified, the default will act as if the Note that the dn (distinguished name) is not an attribute of
Filter were specified as the value from Attributes.Username an entry, so "dn={}" cannot be used. Optional. When not specified,
appended by "={}". 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.
type: string type: string
type: object type: object
required: required:

View File

@ -54,12 +54,18 @@ type LDAPIdentityProviderBindSpec struct {
type LDAPIdentityProviderUserSearchAttributesSpec struct { type LDAPIdentityProviderUserSearchAttributesSpec struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username // 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 // of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter. 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
// 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 LDAPIdentityProviderUserSearchSpec's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// UniqueID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely // UniqueID 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". // 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".
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
UniqueID string `json:"uniqueID,omitempty"` UniqueID string `json:"uniqueID,omitempty"`
} }
@ -72,8 +78,10 @@ type LDAPIdentityProviderUserSearchSpec struct {
// 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 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. // 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 // Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". // 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.
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`

View File

@ -180,6 +180,11 @@ func (p *Provider) TestAuthenticateUser(ctx context.Context, testUsername string
// Authenticate a user and return their mapped username, groups, and UID. Implements authenticators.UserAuthenticator. // Authenticate a user and return their mapped username, groups, and UID. Implements authenticators.UserAuthenticator.
func (p *Provider) AuthenticateUser(ctx context.Context, username, password string) (*authenticator.Response, bool, error) { func (p *Provider) AuthenticateUser(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
if p.UserSearch.UsernameAttribute == distinguishedNameAttributeName && len(p.UserSearch.Filter) == 0 {
// LDAP search filters do not allow searching by DN.
return nil, false, fmt.Errorf(`must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`)
}
conn, err := p.dial(ctx) conn, err := p.dial(ctx)
if err != nil { if err != nil {
return nil, false, fmt.Errorf(`error dialing host "%s": %w`, p.Host, err) return nil, false, fmt.Errorf(`error dialing host "%s": %w`, p.Host, err)
@ -269,9 +274,13 @@ func (p *Provider) userSearchRequestedAttributes() []string {
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.UserSearch.Filter) == 0 { if len(p.UserSearch.Filter) == 0 {
return fmt.Sprintf("%s=%s", p.UserSearch.UsernameAttribute, safeUsername) return fmt.Sprintf("(%s=%s)", p.UserSearch.UsernameAttribute, safeUsername)
} }
return strings.ReplaceAll(p.UserSearch.Filter, userSearchFilterInterpolationLocationMarker, safeUsername) filter := strings.ReplaceAll(p.UserSearch.Filter, userSearchFilterInterpolationLocationMarker, safeUsername)
if strings.HasPrefix(filter, "(") && strings.HasSuffix(filter, ")") {
return filter
}
return "(" + filter + ")"
} }
func (p *Provider) escapeUsernameForSearchFilter(username string) string { func (p *Provider) escapeUsernameForSearchFilter(username string) string {

View File

@ -25,7 +25,7 @@ import (
const ( const (
testHost = "ldap.example.com:8443" testHost = "ldap.example.com:8443"
testBindUsername = "some-bind-username" 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"
@ -39,7 +39,7 @@ const (
) )
var ( var (
testUserSearchFilterInterpolated = fmt.Sprintf("some-filter=%s-and-more-filter=%s", testUpstreamUsername, testUpstreamUsername) testUserSearchFilterInterpolated = fmt.Sprintf("(some-filter=%s-and-more-filter=%s)", testUpstreamUsername, testUpstreamUsername)
) )
func TestAuthenticateUser(t *testing.T) { func TestAuthenticateUser(t *testing.T) {
@ -87,6 +87,7 @@ func TestAuthenticateUser(t *testing.T) {
setupMocks func(conn *mockldapconn.MockConn) setupMocks func(conn *mockldapconn.MockConn)
dialError error dialError error
wantError string wantError string
wantToSkipDial bool
wantAuthResponse *authenticator.Response wantAuthResponse *authenticator.Response
}{ }{
{ {
@ -115,13 +116,44 @@ func TestAuthenticateUser(t *testing.T) {
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue, Name: testSearchResultUsernameAttributeValue,
Groups: []string{}, // We don't support group search yet. Coming soon!
UID: testSearchResultUIDAttributeValue, UID: testSearchResultUIDAttributeValue,
Groups: []string{},
}, },
}, },
}, },
{ {
name: "when the UsernameAttribute is dn", name: "when the user search filter is already wrapped by parenthesis then it is not wrapped again",
username: testUpstreamUsername,
password: testUpstreamPassword,
provider: provider(func(p *Provider) {
p.UserSearch.Filter = "(" + testUserSearchFilter + ")"
}),
setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testSearchResultDNValue,
Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
},
},
},
}, nil).Times(1)
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue,
UID: testSearchResultUIDAttributeValue,
Groups: []string{},
},
},
},
{
name: "when the UsernameAttribute is dn and there is a user search filter provided",
username: testUpstreamUsername, username: testUpstreamUsername,
password: testUpstreamPassword, password: testUpstreamPassword,
provider: provider(func(p *Provider) { provider: provider(func(p *Provider) {
@ -147,8 +179,8 @@ func TestAuthenticateUser(t *testing.T) {
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: testSearchResultDNValue, Name: testSearchResultDNValue,
Groups: []string{}, // We don't support group search yet. Coming soon!
UID: testSearchResultUIDAttributeValue, UID: testSearchResultUIDAttributeValue,
Groups: []string{},
}, },
}, },
}, },
@ -179,8 +211,8 @@ func TestAuthenticateUser(t *testing.T) {
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue, Name: testSearchResultUsernameAttributeValue,
Groups: []string{}, // We don't support group search yet. Coming soon!
UID: testSearchResultDNValue, UID: testSearchResultDNValue,
Groups: []string{},
}, },
}, },
}, },
@ -194,7 +226,7 @@ func TestAuthenticateUser(t *testing.T) {
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: 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(expectedSearch(func(r *ldap.SearchRequest) {
r.Filter = testUserSearchUsernameAttribute + "=" + testUpstreamUsername r.Filter = "(" + testUserSearchUsernameAttribute + "=" + testUpstreamUsername + ")"
})).Return(&ldap.SearchResult{ })).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
@ -212,8 +244,8 @@ func TestAuthenticateUser(t *testing.T) {
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue, Name: testSearchResultUsernameAttributeValue,
Groups: []string{}, // We don't support group search yet. Coming soon!
UID: testSearchResultUIDAttributeValue, UID: testSearchResultUIDAttributeValue,
Groups: []string{},
}, },
}, },
}, },
@ -225,7 +257,7 @@ func TestAuthenticateUser(t *testing.T) {
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: 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(expectedSearch(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-filter=%s-and-more-filter=%s)", `a&b|c\28d\29e\5cf\2ag`, `a&b|c\28d\29e\5cf\2ag`)
})).Return(&ldap.SearchResult{ })).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
@ -243,18 +275,28 @@ func TestAuthenticateUser(t *testing.T) {
wantAuthResponse: &authenticator.Response{ wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: testSearchResultUsernameAttributeValue, Name: testSearchResultUsernameAttributeValue,
Groups: []string{}, // We don't support group search yet. Coming soon!
UID: testSearchResultUIDAttributeValue, UID: testSearchResultUIDAttributeValue,
Groups: []string{},
}, },
}, },
}, },
// TODO are LDAP attribute names case sensitive? do we need any special handling for case?
{ {
name: "when dial fails", name: "when dial fails",
provider: provider(nil), provider: provider(nil),
dialError: errors.New("some dial error"), dialError: errors.New("some dial error"),
wantError: fmt.Sprintf(`error dialing host "%s": some dial error`, testHost), wantError: fmt.Sprintf(`error dialing host "%s": some dial error`, testHost),
}, },
{
name: "when the UsernameAttribute is dn and there is not a user search filter provided",
username: testUpstreamUsername,
password: testUpstreamPassword,
provider: provider(func(p *Provider) {
p.UserSearch.UsernameAttribute = "dn"
p.UserSearch.Filter = ""
}),
wantToSkipDial: true,
wantError: `must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`,
},
{ {
name: "when binding as the bind user returns an error", name: "when binding as the bind user returns an error",
provider: provider(nil), provider: provider(nil),
@ -507,7 +549,7 @@ func TestAuthenticateUser(t *testing.T) {
authResponse, authenticated, err := tt.provider.AuthenticateUser(context.Background(), tt.username, tt.password) authResponse, authenticated, err := tt.provider.AuthenticateUser(context.Background(), tt.username, tt.password)
require.True(t, dialWasAttempted, "AuthenticateUser was supposed to try to dial, but didn't") require.Equal(t, !tt.wantToSkipDial, dialWasAttempted)
if tt.wantError != "" { if tt.wantError != "" {
require.EqualError(t, err, tt.wantError) require.EqualError(t, err, tt.wantError)

View File

@ -0,0 +1,608 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
import (
"context"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"path"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/upstreamldap"
)
func TestLDAPSearch(t *testing.T) {
ctx, cancelFunc := context.WithCancel(context.Background())
t.Cleanup(func() {
cancelFunc() // this will send SIGKILL to the docker process, just in case
})
port := localhostPort(t)
caBundle := dockerRunLDAPServer(ctx, t, port)
provider := func(editFunc func(p *upstreamldap.Provider)) *upstreamldap.Provider {
provider := &upstreamldap.Provider{
Name: "test-ldap-provider",
Host: "127.0.0.1:" + port,
CABundle: caBundle,
BindUsername: "cn=admin,dc=pinniped,dc=dev",
BindPassword: "password",
UserSearch: &upstreamldap.UserSearch{
Base: "ou=users,dc=pinniped,dc=dev",
Filter: "", // defaults to UsernameAttribute={}, i.e. "cn={}" in this case
UsernameAttribute: "cn",
UIDAttribute: "uidNumber",
},
}
if editFunc != nil {
editFunc(provider)
}
return provider
}
pinnyPassword := "password123" // from the LDIF file below
wallyPassword := "password456" // from the LDIF file below
tests := []struct {
name string
username string
password string
provider *upstreamldap.Provider
wantError string
wantAuthResponse *authenticator.Response
}{
{
name: "happy path",
username: "pinny",
password: pinnyPassword,
provider: provider(nil),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{
name: "happy path as a different user",
username: "wally",
password: wallyPassword,
provider: provider(nil),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "wally", UID: "1001", Groups: []string{}},
},
},
{
name: "using a different user search base",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.Base = "dc=pinniped,dc=dev" }),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{
name: "when the user search filter is already wrapped by parenthesis",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.Filter = "(cn={})" }),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{
name: "when the UsernameAttribute is dn and a user search filter is provided",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.UsernameAttribute = "dn"
p.UserSearch.Filter = "cn={}"
}),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: "1000", Groups: []string{}},
},
},
{
name: "when the user search filter allows for different ways of logging in and the first one is used",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.Filter = "(|(cn={})(mail={}))"
}),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{
name: "when the user search filter allows for different ways of logging in and the second one is used",
username: "pinny.ldap@example.com",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.Filter = "(|(cn={})(mail={}))"
}),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
},
},
{
name: "when the UIDAttribute is dn",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UIDAttribute = "dn" }),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "cn=pinny,ou=users,dc=pinniped,dc=dev", Groups: []string{}},
},
},
{
name: "when the UIDAttribute is sn",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UIDAttribute = "sn" }),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "pinny", UID: "Seal", Groups: []string{}},
},
},
{
name: "when the UsernameAttribute is sn",
username: "seAl", // note that this is not case-sensitive! sn=Seal
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UsernameAttribute = "sn" }),
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{Name: "Seal", UID: "1000", Groups: []string{}}, // note that the final answer is case-sensitive
},
},
{
name: "when the UsernameAttribute is dn and there is no user search filter provided",
username: "cn=pinny,ou=users,dc=pinniped,dc=dev",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.UsernameAttribute = "dn"
p.UserSearch.Filter = ""
}),
wantError: `must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`,
},
{
name: "when the bind user username is not a valid DN",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.BindUsername = "invalid-dn" }),
wantError: `error binding as "invalid-dn" before user search: LDAP Result Code 34 "Invalid DN Syntax": invalid DN`,
},
{
name: "when the bind user username is wrong",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.BindUsername = "cn=wrong,dc=pinniped,dc=dev" }),
wantError: `error binding as "cn=wrong,dc=pinniped,dc=dev" before user search: LDAP Result Code 49 "Invalid Credentials": `,
},
{
name: "when the bind user password is wrong",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.BindPassword = "wrong-password" }),
wantError: `error binding as "cn=admin,dc=pinniped,dc=dev" before user search: LDAP Result Code 49 "Invalid Credentials": `,
},
{
name: "when the end user password is wrong",
username: "pinny",
password: "wrong-pinny-password",
provider: provider(nil),
wantError: `error binding for user "pinny" using provided password against DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 49 "Invalid Credentials": `,
},
{
name: "when the end user username is wrong",
username: "wrong-username",
password: pinnyPassword,
provider: provider(nil),
wantError: `searching for user "wrong-username" resulted in 0 search results, but expected 1 result`,
},
{
name: "when the user search filter does not compile",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.Filter = "*" }),
wantError: `error searching for user "pinny": LDAP Result Code 201 "Filter Compile Error": ldap: error parsing filter`,
},
{
name: "when there are too many search results for the user",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.Filter = "objectClass=*" // overly broad search filter
}),
wantError: `error searching for user "pinny": LDAP Result Code 4 "Size Limit Exceeded": `,
},
{
name: "when the server is unreachable",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.Host = "127.0.0.1:27534" }), // hopefully this port is not in use on the host running tests
wantError: `error dialing host "127.0.0.1:27534": LDAP Result Code 200 "Network Error": dial tcp 127.0.0.1:27534: connect: connection refused`,
},
{
name: "when the server is not parsable",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.Host = "too:many:ports" }),
wantError: `error dialing host "too:many:ports": LDAP Result Code 200 "Network Error": address too:many:ports: too many colons in address`,
},
{
name: "when the CA bundle is not parsable",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.CABundle = []byte("invalid-pem") }),
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": could not parse CA bundle`, port),
},
{
name: "when the CA bundle does not cause the host to be trusted",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.CABundle = nil }),
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": x509: certificate signed by unknown authority`, port),
},
{
name: "when the UsernameAttribute attribute has multiple values in the entry",
username: "wally.ldap@example.com",
password: wallyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UsernameAttribute = "mail" }),
wantError: `found 2 values for attribute "mail" while searching for user "wally.ldap@example.com", but expected 1 result`,
},
{
name: "when the UIDAttribute attribute has multiple values in the entry",
username: "wally",
password: wallyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UIDAttribute = "mail" }),
wantError: `found 2 values for attribute "mail" while searching for user "wally", but expected 1 result`,
},
{
name: "when the UsernameAttribute attribute is not found in the entry",
username: "wally",
password: wallyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.Filter = "cn={}"
p.UserSearch.UsernameAttribute = "attr-does-not-exist"
}),
wantError: `found 0 values for attribute "attr-does-not-exist" while searching for user "wally", but expected 1 result`,
},
{
name: "when the UIDAttribute attribute is not found in the entry",
username: "wally",
password: wallyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UIDAttribute = "attr-does-not-exist" }),
wantError: `found 0 values for attribute "attr-does-not-exist" while searching for user "wally", but expected 1 result`,
},
{
name: "when the UsernameAttribute has the wrong case",
username: "Seal",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UsernameAttribute = "SN" }), // this is case-sensitive
wantError: `found 0 values for attribute "SN" while searching for user "Seal", but expected 1 result`,
},
{
name: "when the UIDAttribute has the wrong case",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.UIDAttribute = "SN" }), // this is case-sensitive
wantError: `found 0 values for attribute "SN" while searching for user "pinny", but expected 1 result`,
},
{
name: "when the UsernameAttribute is DN and has the wrong case",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.UsernameAttribute = "DN" // dn must be lower-case
p.UserSearch.Filter = "cn={}"
}),
wantError: `found 0 values for attribute "DN" while searching for user "pinny", but expected 1 result`,
},
{
name: "when the UIDAttribute is DN and has the wrong case",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) {
p.UserSearch.UIDAttribute = "DN" // dn must be lower-case
}),
wantError: `found 0 values for attribute "DN" while searching for user "pinny", but expected 1 result`,
},
{
name: "when the search base is invalid",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.Base = "invalid-base" }),
wantError: `error searching for user "pinny": LDAP Result Code 34 "Invalid DN Syntax": invalid DN`,
},
{
name: "when the search base does not exist",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.Base = "ou=does-not-exist,dc=pinniped,dc=dev" }),
wantError: `error searching for user "pinny": LDAP Result Code 32 "No Such Object": `,
},
{
name: "when the search base causes no search results",
username: "pinny",
password: pinnyPassword,
provider: provider(func(p *upstreamldap.Provider) { p.UserSearch.Base = "ou=groups,dc=pinniped,dc=dev" }),
wantError: `searching for user "pinny" resulted in 0 search results, but expected 1 result`,
},
{
name: "when there is no username specified",
username: "",
password: pinnyPassword,
provider: provider(nil),
wantError: `searching for user "" resulted in 0 search results, but expected 1 result`,
},
{
name: "when there is no password specified",
username: "pinny",
password: "",
provider: provider(nil),
wantError: `error binding for user "pinny" using provided password against DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 206 "Empty password not allowed by the client": ldap: empty password not allowed by the client`,
},
{
name: "when the user has no password in their entry",
username: "olive",
password: "anything",
provider: provider(nil),
wantError: `error binding for user "olive" using provided password against DN "cn=olive,ou=users,dc=pinniped,dc=dev": LDAP Result Code 49 "Invalid Credentials": `,
},
}
for _, test := range tests {
tt := test
t.Run(tt.name, func(t *testing.T) {
authResponse, authenticated, err := tt.provider.AuthenticateUser(ctx, tt.username, tt.password)
if tt.wantError != "" {
require.EqualError(t, err, tt.wantError)
require.False(t, authenticated)
require.Nil(t, authResponse)
} else {
require.NoError(t, err)
require.True(t, authenticated)
require.Equal(t, tt.wantAuthResponse, authResponse)
}
})
}
}
func localhostPort(t *testing.T) string {
t.Helper()
unusedPortGrabbingListener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
recentlyClaimedHostAndPort := unusedPortGrabbingListener.Addr().String()
require.NoError(t, unusedPortGrabbingListener.Close())
splitHostAndPort := strings.Split(recentlyClaimedHostAndPort, ":")
require.Len(t, splitHostAndPort, 2)
return splitHostAndPort[1]
}
func dockerRunLDAPServer(ctx context.Context, t *testing.T, hostPort string) []byte {
t.Helper()
_, err := exec.LookPath("docker")
require.NoError(t, err)
ca, err := certauthority.New("Test LDAP CA", time.Hour*24)
require.NoError(t, err)
certPEM, keyPEM, err := ca.IssueServerCertPEM(nil, []net.IP{net.ParseIP("127.0.0.1")}, time.Hour*24)
require.NoError(t, err)
tempDir, err := ioutil.TempDir("", "pinniped-test-*")
require.NoError(t, err)
t.Cleanup(func() {
err := os.Remove(tempDir)
require.NoError(t, err)
})
writeToNewTempFile(t, tempDir, "cert.pem", certPEM)
writeToNewTempFile(t, tempDir, "key.pem", keyPEM)
writeToNewTempFile(t, tempDir, "ca.pem", ca.Bundle())
writeToNewTempFile(t, tempDir, "test.ldif", []byte(testLDIF))
dockerArgs := []string{
"run",
"-e", "BITNAMI_DEBUG=true",
"-e", "LDAP_ADMIN_USERNAME=admin",
"-e", "LDAP_ADMIN_PASSWORD=password",
"-e", "LDAP_ENABLE_TLS=yes",
"-e", "LDAP_TLS_CERT_FILE=/inputs/cert.pem",
"-e", "LDAP_TLS_KEY_FILE=/inputs/key.pem",
"-e", "LDAP_TLS_CA_FILE=/inputs/ca.pem",
"-e", "LDAP_CUSTOM_LDIF_DIR=/inputs",
"-e", "LDAP_ROOT=dc=pinniped,dc=dev",
"-v", tempDir + ":/inputs",
"-p", hostPort + ":1636",
"-m", "64m",
"--rm", // automatically delete the container when finished
"docker.io/bitnami/openldap",
}
t.Log("Starting:", "docker", strings.Join(dockerArgs, " "))
cmd := exec.CommandContext(ctx, "docker", dockerArgs...)
var stdoutBuf, stderrBuf syncBuffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
err = cmd.Start()
require.NoError(t, err)
t.Cleanup(func() {
// docker requires an interrupt signal to end the container.
// This t.Cleanup is registered after the one that cancels the context, so this one will happen first.
err := cmd.Process.Signal(os.Interrupt)
require.NoError(t, err)
time.Sleep(time.Second) // give a moment before we move on, because we'll send SIGKILL in a later t.Cleanup
})
earlyTerminationCh := make(chan bool, 1)
go func() {
err = cmd.Wait()
earlyTerminationCh <- true
}()
terminatedEarly := false
require.Eventually(t, func() bool {
t.Log("Waiting for slapd to start...")
// This substring is contained in the last line of output before the server starts.
if strings.Contains(stderrBuf.String(), " slapd starting\n") {
return true
}
select {
case <-earlyTerminationCh:
terminatedEarly = true
return true
default: // ignore when this non-blocking read found no message
}
return false
}, 2*time.Minute, time.Second)
require.Falsef(t, terminatedEarly, "docker command ended sooner than expected")
t.Log("Detected LDAP server has started successfully")
return ca.Bundle()
}
func writeToNewTempFile(t *testing.T, dir string, filename string, contents []byte) {
t.Helper()
filePath := path.Join(dir, filename)
err := ioutil.WriteFile(filePath, contents, 0644)
require.NoError(t, err)
t.Cleanup(func() {
err := os.Remove(filePath)
require.NoError(t, err)
})
}
var testLDIF = `
# ** CAUTION: Blank lines separate entries in the LDIF format! Do not remove them! ***
# Here's a good explaination of LDIF:
# https://www.digitalocean.com/community/tutorials/how-to-use-ldif-files-to-make-changes-to-an-openldap-system
# pinniped.dev (organization, root)
dn: dc=pinniped,dc=dev
objectClass: dcObject
objectClass: organization
dc: pinniped
o: example
# users, pinniped.dev (organization unit)
dn: ou=users,dc=pinniped,dc=dev
objectClass: organizationalUnit
ou: users
# groups, pinniped.dev (organization unit)
dn: ou=groups,dc=pinniped,dc=dev
objectClass: organizationalUnit
ou: groups
# beach-groups, groups, pinniped.dev (organization unit)
dn: ou=beach-groups,ou=groups,dc=pinniped,dc=dev
objectClass: organizationalUnit
ou: beach-groups
# pinny, users, pinniped.dev (user)
dn: cn=pinny,ou=users,dc=pinniped,dc=dev
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: pinny
sn: Seal
givenName: Pinny
mail: pinny.ldap@example.com
userPassword: password123
uid: pinny
uidNumber: 1000
gidNumber: 1000
homeDirectory: /home/pinny
loginShell: /bin/bash
gecos: pinny-the-seal
# wally, users, pinniped.dev
dn: cn=wally,ou=users,dc=pinniped,dc=dev
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: wally
sn: Walrus
givenName: Wally
mail: wally.ldap@example.com
mail: wally.alternate@example.com
userPassword: password456
uid: wally
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/wally
loginShell: /bin/bash
gecos: wally-the-walrus
# olive, users, pinniped.dev (user without password)
dn: cn=olive,ou=users,dc=pinniped,dc=dev
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: olive
sn: Boston Terrier
givenName: Olive
mail: olive.ldap@example.com
uid: olive
uidNumber: 1002
gidNumber: 1002
homeDirectory: /home/olive
loginShell: /bin/bash
gecos: olive-the-dog
# ball-game-players, beach-groups, groups, pinniped.dev (group of users)
dn: cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev
cn: ball-game-players
objectClass: groupOfNames
member: cn=pinny,ou=users,dc=pinniped,dc=dev
member: cn=olive,ou=users,dc=pinniped,dc=dev
# seals, groups, pinniped.dev (group of users)
dn: cn=seals,ou=groups,dc=pinniped,dc=dev
cn: seals
objectClass: groupOfNames
member: cn=pinny,ou=users,dc=pinniped,dc=dev
# walruses, groups, pinniped.dev (group of users)
dn: cn=walruses,ou=groups,dc=pinniped,dc=dev
cn: walruses
objectClass: groupOfNames
member: cn=wally,ou=users,dc=pinniped,dc=dev
# pinnipeds, users, pinniped.dev (group of groups)
dn: cn=pinnipeds,ou=groups,dc=pinniped,dc=dev
cn: pinnipeds
objectClass: groupOfNames
member: cn=seals,ou=groups,dc=pinniped,dc=dev
member: cn=walruses,ou=groups,dc=pinniped,dc=dev
# mammals, groups, pinniped.dev (group of both groups and users)
dn: cn=mammals,ou=groups,dc=pinniped,dc=dev
cn: mammals
objectClass: groupOfNames
member: cn=pinninpeds,ou=groups,dc=pinniped,dc=dev
member: cn=olive,ou=users,dc=pinniped,dc=dev
`