2015-05-06 01:54:27 +00:00
package ldap
import (
2018-01-08 18:31:38 +00:00
"context"
2022-02-18 01:19:44 +00:00
"strings"
2015-05-06 01:54:27 +00:00
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/sdk/framework"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/helper/consts"
2019-04-12 22:26:54 +00:00
"github.com/hashicorp/vault/sdk/helper/ldaputil"
2019-07-01 20:16:23 +00:00
"github.com/hashicorp/vault/sdk/helper/tokenutil"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/logical"
2015-05-06 01:54:27 +00:00
)
2022-02-18 01:19:44 +00:00
const userFilterWarning = "userfilter configured does not consider userattr and may result in colliding entity aliases on logins"
2015-05-06 01:54:27 +00:00
func pathConfig ( b * backend ) * framework . Path {
2019-07-01 20:16:23 +00:00
p := & framework . Path {
2015-05-06 01:54:27 +00:00
Pattern : ` config ` ,
2019-02-01 16:23:40 +00:00
Fields : ldaputil . ConfigFields ( ) ,
2015-05-06 01:54:27 +00:00
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-26 14:56:41 +00:00
logical . ReadOperation : b . pathConfigRead ,
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : b . pathConfigWrite ,
2015-05-06 01:54:27 +00:00
} ,
HelpSynopsis : pathConfigHelpSyn ,
HelpDescription : pathConfigHelpDesc ,
2019-07-01 20:35:18 +00:00
DisplayAttrs : & framework . DisplayAttributes {
Action : "Configure" ,
} ,
2015-05-06 01:54:27 +00:00
}
2019-07-01 20:16:23 +00:00
tokenutil . AddTokenFields ( p . Fields )
p . Fields [ "token_policies" ] . Description += ". This will apply to all tokens generated by this auth method, in addition to any configured for specific users/groups."
return p
2015-05-06 01:54:27 +00:00
}
2016-05-09 00:21:44 +00:00
/ *
* Construct ConfigEntry struct using stored configuration .
* /
2019-07-01 20:16:23 +00:00
func ( b * backend ) Config ( ctx context . Context , req * logical . Request ) ( * ldapConfigEntry , error ) {
2018-01-19 06:44:44 +00:00
storedConfig , err := req . Storage . Get ( ctx , "config" )
2016-05-09 00:21:44 +00:00
if err != nil {
2015-05-06 01:54:27 +00:00
return nil , err
}
2016-05-09 00:21:44 +00:00
if storedConfig == nil {
2019-07-01 20:16:23 +00:00
// Create a new ConfigEntry, filling in defaults where appropriate
fd , err := b . getConfigFieldData ( )
if err != nil {
return nil , err
}
result , err := ldaputil . NewConfigEntry ( nil , fd )
if err != nil {
return nil , err
}
2016-05-09 00:21:44 +00:00
// No user overrides, return default configuration
2018-04-03 13:52:43 +00:00
result . CaseSensitiveNames = new ( bool )
* result . CaseSensitiveNames = false
2019-07-29 19:43:34 +00:00
result . UsePre111GroupCNBehavior = new ( bool )
* result . UsePre111GroupCNBehavior = false
2019-07-01 20:16:23 +00:00
return & ldapConfigEntry { ConfigEntry : result } , nil
2016-05-09 00:21:44 +00:00
}
// Deserialize stored configuration.
// Fields not specified in storedConfig will retain their defaults.
2019-07-01 20:16:23 +00:00
result := new ( ldapConfigEntry )
result . ConfigEntry = new ( ldaputil . ConfigEntry )
if err := storedConfig . DecodeJSON ( result ) ; err != nil {
2016-05-09 00:21:44 +00:00
return nil , err
}
2018-04-03 13:52:43 +00:00
var persistNeeded bool
if result . CaseSensitiveNames == nil {
// Upgrade from before switching to case-insensitive
result . CaseSensitiveNames = new ( bool )
* result . CaseSensitiveNames = true
persistNeeded = true
}
2019-07-29 19:43:34 +00:00
if result . UsePre111GroupCNBehavior == nil {
result . UsePre111GroupCNBehavior = new ( bool )
* result . UsePre111GroupCNBehavior = true
persistNeeded = true
}
2019-02-01 21:56:57 +00:00
if persistNeeded && ( b . System ( ) . LocalMount ( ) || ! b . System ( ) . ReplicationState ( ) . HasState ( consts . ReplicationPerformanceSecondary | consts . ReplicationPerformanceStandby ) ) {
2018-04-03 13:52:43 +00:00
entry , err := logical . StorageEntryJSON ( "config" , result )
if err != nil {
return nil , err
}
if err := req . Storage . Put ( ctx , entry ) ; err != nil {
return nil , err
}
}
2016-05-09 00:21:44 +00:00
return result , nil
2015-05-06 01:54:27 +00:00
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigRead ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2018-01-19 06:44:44 +00:00
cfg , err := b . Config ( ctx , req )
2015-05-06 01:54:27 +00:00
if err != nil {
return nil , err
}
if cfg == nil {
return nil , nil
}
2019-07-01 20:16:23 +00:00
data := cfg . PasswordlessMap ( )
cfg . PopulateTokenData ( data )
2022-02-18 01:19:44 +00:00
resp := & logical . Response {
2019-07-01 20:16:23 +00:00
Data : data ,
2022-02-18 01:19:44 +00:00
}
if warnings := b . checkConfigUserFilter ( cfg ) ; len ( warnings ) > 0 {
resp . Warnings = warnings
}
return resp , nil
}
// checkConfigUserFilter performs a best-effort check the config's userfilter.
// It will checked whether the templated or literal userattr value is present,
// and if not return a warning.
func ( b * backend ) checkConfigUserFilter ( cfg * ldapConfigEntry ) [ ] string {
if cfg == nil || cfg . UserFilter == "" {
return nil
}
var warnings [ ] string
switch {
case strings . Contains ( cfg . UserFilter , "{{.UserAttr}}" ) :
// Case where the templated userattr value is provided
case strings . Contains ( cfg . UserFilter , cfg . UserAttr ) :
// Case where the literal userattr value is provided
default :
b . Logger ( ) . Debug ( userFilterWarning , "userfilter" , cfg . UserFilter , "userattr" , cfg . UserAttr )
warnings = append ( warnings , userFilterWarning )
}
return warnings
2015-05-06 01:54:27 +00:00
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigWrite ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-07-01 20:16:23 +00:00
cfg , err := b . Config ( ctx , req )
if err != nil {
return nil , err
}
if cfg == nil {
return nil , nil
}
2016-05-09 00:21:44 +00:00
// Build a ConfigEntry struct out of the supplied FieldData
2019-07-01 20:16:23 +00:00
cfg . ConfigEntry , err = ldaputil . NewConfigEntry ( cfg . ConfigEntry , d )
2016-05-09 00:21:44 +00:00
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , nil
2016-03-29 13:59:28 +00:00
}
2015-05-06 01:54:27 +00:00
2018-04-03 13:52:43 +00:00
// On write, if not specified, use false. We do this here so upgrade logic
// works since it calls the same newConfigEntry function
if cfg . CaseSensitiveNames == nil {
cfg . CaseSensitiveNames = new ( bool )
* cfg . CaseSensitiveNames = false
}
2019-07-29 19:43:34 +00:00
if cfg . UsePre111GroupCNBehavior == nil {
cfg . UsePre111GroupCNBehavior = new ( bool )
* cfg . UsePre111GroupCNBehavior = false
}
2019-07-01 20:16:23 +00:00
if err := cfg . ParseTokenFields ( req , d ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
2015-05-06 01:54:27 +00:00
entry , err := logical . StorageEntryJSON ( "config" , cfg )
if err != nil {
return nil , err
}
2018-01-19 06:44:44 +00:00
if err := req . Storage . Put ( ctx , entry ) ; err != nil {
2015-05-06 01:54:27 +00:00
return nil , err
}
2022-02-18 01:19:44 +00:00
if warnings := b . checkConfigUserFilter ( cfg ) ; len ( warnings ) > 0 {
return & logical . Response {
Warnings : warnings ,
} , nil
}
2015-05-06 01:54:27 +00:00
return nil , nil
}
2016-05-09 00:21:44 +00:00
/ *
* Returns FieldData describing our ConfigEntry struct schema
* /
func ( b * backend ) getConfigFieldData ( ) ( * framework . FieldData , error ) {
configPath := b . Route ( "config" )
if configPath == nil {
return nil , logical . ErrUnsupportedPath
}
raw := make ( map [ string ] interface { } , len ( configPath . Fields ) )
fd := framework . FieldData {
Raw : raw ,
Schema : configPath . Fields ,
}
return & fd , nil
2015-05-06 01:54:27 +00:00
}
2019-07-01 20:16:23 +00:00
type ldapConfigEntry struct {
tokenutil . TokenParams
* ldaputil . ConfigEntry
}
2015-05-06 01:54:27 +00:00
const pathConfigHelpSyn = `
2016-02-19 18:16:18 +00:00
Configure the LDAP server to connect to , along with its options .
2015-05-06 01:54:27 +00:00
`
const pathConfigHelpDesc = `
2016-02-19 18:16:18 +00:00
This endpoint allows you to configure the LDAP server to connect to and its
configuration options .
2015-05-06 01:54:27 +00:00
The LDAP URL can use either the "ldap://" or "ldaps://" schema . In the former
2016-02-19 18:16:18 +00:00
case , an unencrypted connection will be made with a default port of 389 , unless
the "starttls" parameter is set to true , in which case TLS will be used . In the
latter case , a SSL connection will be established with a default port of 636.
# # A NOTE ON ESCAPING
2016-02-19 18:47:26 +00:00
It is up to the administrator to provide properly escaped DNs . This includes
the user DN , bind DN for search , and so on .
2016-02-19 18:16:18 +00:00
The only DN escaping performed by this backend is on usernames given at login
time when they are inserted into the final bind DN , and uses escaping rules
2016-02-19 18:47:26 +00:00
defined in RFC 4514.
2016-02-19 18:16:18 +00:00
Additionally , Active Directory has escaping rules that differ slightly from the
RFC ; in particular it requires escaping of '#' regardless of position in the DN
( the RFC only requires it to be escaped when it is the first character ) , and
'=' , which the RFC indicates can be escaped with a backslash , but does not
contain in its set of required escapes . If you are using Active Directory and
these appear in your usernames , please ensure that they are escaped , in
addition to being properly escaped in your configured DNs .
For reference , see https : //www.ietf.org/rfc/rfc4514.txt and
http : //social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
2015-05-06 01:54:27 +00:00
`