Allow alias dereferencing in LDAP searches (#18230)

* impr(auth/ldap): allow to dereference aliases in searches

* docs: add documentation for LDAP alias dereferencing

* chore(auth/ldap): add changelog entry for PR 18230

* chore: run formatter

* fix: update default LDAP configuration with new default

* Update website/content/docs/auth/ldap.mdx

Co-authored-by: tjperry07 <tjperry07@users.noreply.github.com>

* docs(ldap): add alias dereferencing to API docs for LDAP

---------

Co-authored-by: tjperry07 <tjperry07@users.noreply.github.com>
This commit is contained in:
Jakob Beckmann 2023-02-24 19:49:17 +01:00 committed by GitHub
parent 809957aac0
commit 078a245939
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 20 deletions

View File

@ -1231,6 +1231,7 @@ func TestLdapAuthBackend_ConfigUpgrade(t *testing.T) {
UsePre111GroupCNBehavior: new(bool),
RequestTimeout: cfg.RequestTimeout,
UsernameAsAlias: false,
DerefAliases: "never",
},
}

3
changelog/18230.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
auth/ldap: allow configuration of alias dereferencing in LDAP search
```

View File

@ -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("<SID=%s>", sidString),
Scope: ldap.ScopeBaseObject,
Filter: "(objectClass=*)",
BaseDN: fmt.Sprintf("<SID=%s>", sidString),
Scope: ldap.ScopeBaseObject,
DerefAliases: ldapDerefAliasMap[cfg.DerefAliases],
Filter: "(objectClass=*)",
Attributes: []string{
"1.1", // RFC no attributes
},

View File

@ -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

View File

@ -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": ""

View File

@ -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'

View File

@ -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`.