diff --git a/builtin/credential/ldap/backend_test.go b/builtin/credential/ldap/backend_test.go index 74b4e18a1..3ad906612 100644 --- a/builtin/credential/ldap/backend_test.go +++ b/builtin/credential/ldap/backend_test.go @@ -1231,6 +1231,7 @@ func TestLdapAuthBackend_ConfigUpgrade(t *testing.T) { UsePre111GroupCNBehavior: new(bool), RequestTimeout: cfg.RequestTimeout, UsernameAsAlias: false, + DerefAliases: "never", }, } diff --git a/changelog/18230.txt b/changelog/18230.txt new file mode 100644 index 000000000..335f9670d --- /dev/null +++ b/changelog/18230.txt @@ -0,0 +1,3 @@ +```release-note:improvement +auth/ldap: allow configuration of alias dereferencing in LDAP search +``` diff --git a/sdk/helper/ldaputil/client.go b/sdk/helper/ldaputil/client.go index 8a7ac4822..fab55488f 100644 --- a/sdk/helper/ldaputil/client.go +++ b/sdk/helper/ldaputil/client.go @@ -119,10 +119,11 @@ func (c *Client) makeLdapSearchRequest(cfg *ConfigEntry, conn Connection, userna c.Logger.Debug("discovering user", "userdn", cfg.UserDN, "filter", renderedFilter) } ldapRequest := &ldap.SearchRequest{ - BaseDN: cfg.UserDN, - Scope: ldap.ScopeWholeSubtree, - Filter: renderedFilter, - SizeLimit: 2, // Should be only 1 result. Any number larger (2 or more) means access denied. + BaseDN: cfg.UserDN, + DerefAliases: ldapDerefAliasMap[cfg.DerefAliases], + Scope: ldap.ScopeWholeSubtree, + Filter: renderedFilter, + SizeLimit: 2, // Should be only 1 result. Any number larger (2 or more) means access denied. Attributes: []string{ cfg.UserAttr, // Return only needed attributes }, @@ -274,10 +275,11 @@ func (c *Client) GetUserDN(cfg *ConfigEntry, conn Connection, bindDN, username s c.Logger.Debug("searching upn", "userdn", cfg.UserDN, "filter", filter) } result, err := conn.Search(&ldap.SearchRequest{ - BaseDN: cfg.UserDN, - Scope: ldap.ScopeWholeSubtree, - Filter: filter, - SizeLimit: math.MaxInt32, + BaseDN: cfg.UserDN, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldapDerefAliasMap[cfg.DerefAliases], + Filter: filter, + SizeLimit: math.MaxInt32, }) if err != nil { return userDN, fmt.Errorf("LDAP search failed for detecting user: %w", err) @@ -335,9 +337,10 @@ func (c *Client) performLdapFilterGroupsSearch(cfg *ConfigEntry, conn Connection } result, err := conn.Search(&ldap.SearchRequest{ - BaseDN: cfg.GroupDN, - Scope: ldap.ScopeWholeSubtree, - Filter: renderedQuery.String(), + BaseDN: cfg.GroupDN, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldapDerefAliasMap[cfg.DerefAliases], + Filter: renderedQuery.String(), Attributes: []string{ cfg.GroupAttr, }, @@ -393,9 +396,10 @@ func (c *Client) performLdapFilterGroupsSearchPaging(cfg *ConfigEntry, conn Pagi } result, err := conn.SearchWithPaging(&ldap.SearchRequest{ - BaseDN: cfg.GroupDN, - Scope: ldap.ScopeWholeSubtree, - Filter: renderedQuery.String(), + BaseDN: cfg.GroupDN, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldapDerefAliasMap[cfg.DerefAliases], + Filter: renderedQuery.String(), Attributes: []string{ cfg.GroupAttr, }, @@ -442,9 +446,10 @@ func sidBytesToString(b []byte) (string, error) { func (c *Client) performLdapTokenGroupsSearch(cfg *ConfigEntry, conn Connection, userDN string) ([]*ldap.Entry, error) { result, err := conn.Search(&ldap.SearchRequest{ - BaseDN: userDN, - Scope: ldap.ScopeBaseObject, - Filter: "(objectClass=*)", + BaseDN: userDN, + Scope: ldap.ScopeBaseObject, + DerefAliases: ldapDerefAliasMap[cfg.DerefAliases], + Filter: "(objectClass=*)", Attributes: []string{ "tokenGroups", }, @@ -470,9 +475,10 @@ func (c *Client) performLdapTokenGroupsSearch(cfg *ConfigEntry, conn Connection, } groupResult, err := conn.Search(&ldap.SearchRequest{ - BaseDN: fmt.Sprintf("", sidString), - Scope: ldap.ScopeBaseObject, - Filter: "(objectClass=*)", + BaseDN: fmt.Sprintf("", sidString), + Scope: ldap.ScopeBaseObject, + DerefAliases: ldapDerefAliasMap[cfg.DerefAliases], + Filter: "(objectClass=*)", Attributes: []string{ "1.1", // RFC no attributes }, diff --git a/sdk/helper/ldaputil/config.go b/sdk/helper/ldaputil/config.go index 43844da22..8da5f2225 100644 --- a/sdk/helper/ldaputil/config.go +++ b/sdk/helper/ldaputil/config.go @@ -13,8 +13,17 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/errwrap" + + "github.com/go-ldap/ldap/v3" ) +var ldapDerefAliasMap = map[string]int{ + "never": ldap.NeverDerefAliases, + "finding": ldap.DerefFindingBaseObj, + "searching": ldap.DerefInSearching, + "always": ldap.DerefAlways, +} + // ConfigFields returns all the config fields that can potentially be used by the LDAP client. // Not all fields will be used by every integration. func ConfigFields() map[string]*framework.FieldSchema { @@ -226,6 +235,13 @@ Default: ({{.UserAttr}}={{.Username}})`, Description: "Timeout, in seconds, for the connection when making requests against the server before returning back an error.", Default: "90s", }, + + "dereference_aliases": { + Type: framework.TypeString, + Description: "When aliases should be dereferenced on search operations. Accepted values are 'never', 'finding', 'searching', 'always'. Defaults to 'never'.", + Default: "never", + AllowedValues: []interface{}{"never", "finding", "searching", "always"}, + }, } } @@ -392,6 +408,10 @@ func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry cfg.RequestTimeout = d.Get("request_timeout").(int) } + if _, ok := d.Raw["dereference_aliases"]; ok || !hadExisting { + cfg.DerefAliases = d.Get("dereference_aliases").(string) + } + return cfg, nil } @@ -418,6 +438,7 @@ type ConfigEntry struct { UseTokenGroups bool `json:"use_token_groups"` UsePre111GroupCNBehavior *bool `json:"use_pre111_group_cn_behavior"` RequestTimeout int `json:"request_timeout"` + DerefAliases string `json:"dereference_aliases"` // These json tags deviate from snake case because there was a past issue // where the tag was being ignored, causing it to be jsonified as "CaseSensitiveNames", etc. @@ -456,6 +477,7 @@ func (c *ConfigEntry) PasswordlessMap() map[string]interface{} { "anonymous_group_search": c.AnonymousGroupSearch, "request_timeout": c.RequestTimeout, "username_as_alias": c.UsernameAsAlias, + "dereference_aliases": c.DerefAliases, } if c.CaseSensitiveNames != nil { m["case_sensitive_names"] = *c.CaseSensitiveNames diff --git a/sdk/helper/ldaputil/config_test.go b/sdk/helper/ldaputil/config_test.go index 32edb5dff..fb6513450 100644 --- a/sdk/helper/ldaputil/config_test.go +++ b/sdk/helper/ldaputil/config_test.go @@ -168,6 +168,7 @@ var jsonConfigDefault = []byte(` "use_pre111_group_cn_behavior": null, "username_as_alias": false, "request_timeout": 90, + "dereference_aliases": "never", "CaseSensitiveNames": false, "ClientTLSCert": "", "ClientTLSKey": "" diff --git a/website/content/api-docs/auth/ldap.mdx b/website/content/api-docs/auth/ldap.mdx index 16aa4ebce..9b0007900 100644 --- a/website/content/api-docs/auth/ldap.mdx +++ b/website/content/api-docs/auth/ldap.mdx @@ -91,6 +91,9 @@ This endpoint configures the LDAP auth method. returning _user_ objects, use: `memberOf`. The default is `cn`. - `username_as_alias` `(bool: false)` - If set to true, forces the auth method to use the username passed by the user as the alias name. +- `dereference_aliases` `(string: never)` - When aliases should be dereferenced + on search operations. Accepted values are 'never', 'finding', 'searching', + 'always'. Defaults to 'never'. @include 'tokenfields.mdx' diff --git a/website/content/docs/auth/ldap.mdx b/website/content/docs/auth/ldap.mdx index 82641bf3d..bcb1084a2 100644 --- a/website/content/docs/auth/ldap.mdx +++ b/website/content/docs/auth/ldap.mdx @@ -133,6 +133,10 @@ There are two alternate methods of resolving the user object used to authenticat @include 'ldap-auth-userfilter-warning.mdx' +#### Alias Dereferencing + +- `dereference_aliases` (string, optional) - Control how aliases are dereferenced when performing the search. Possible values are: `never`, `finding`, `searching`, and `always`. `finding` will only dereference aliases during name resolution of the base. `searching` will dereference aliases after name resolution. + #### Binding - User Principal Name (AD) - `upndomain` (string, optional) - userPrincipalDomain used to construct the UPN string for the authenticating user. The constructed UPN will appear as `[username]@UPNDomain`. Example: `example.com`, which will cause vault to bind as `username@example.com`.