mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 02:57:59 +00:00
Case insensitive behavior for LDAP (#4238)
This commit is contained in:
@@ -15,6 +15,11 @@ DEPRECATIONS/CHANGES:
|
|||||||
accommodate this as best as possible, and users of other tools may have to
|
accommodate this as best as possible, and users of other tools may have to
|
||||||
make adjustments, but in the end we felt that the ends did not justify the
|
make adjustments, but in the end we felt that the ends did not justify the
|
||||||
means and we needed to prioritize security over operational convenience.
|
means and we needed to prioritize security over operational convenience.
|
||||||
|
* LDAP auth method case sensitivity: We now treat usernames and groups
|
||||||
|
configured locally for policy assignment in a case insensitive fashion by
|
||||||
|
default. Existing configurations will continue to work as they do now;
|
||||||
|
however, the next time a configuration is written `case_sensitive_names`
|
||||||
|
will need to be explicitly set to `true`.
|
||||||
|
|
||||||
FEATURES:
|
FEATURES:
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap"
|
"github.com/go-ldap/ldap"
|
||||||
@@ -173,8 +174,13 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
var allGroups []string
|
var allGroups []string
|
||||||
|
canonicalUsername := username
|
||||||
|
cs := *cfg.CaseSensitiveNames
|
||||||
|
if !cs {
|
||||||
|
canonicalUsername = strings.ToLower(username)
|
||||||
|
}
|
||||||
// Import the custom added groups from ldap backend
|
// Import the custom added groups from ldap backend
|
||||||
user, err := b.User(ctx, req.Storage, username)
|
user, err := b.User(ctx, req.Storage, canonicalUsername)
|
||||||
if err == nil && user != nil && user.Groups != nil {
|
if err == nil && user != nil && user.Groups != nil {
|
||||||
if b.Logger().IsDebug() {
|
if b.Logger().IsDebug() {
|
||||||
b.Logger().Debug("adding local groups", "num_local_groups", len(user.Groups), "local_groups", user.Groups)
|
b.Logger().Debug("adding local groups", "num_local_groups", len(user.Groups), "local_groups", user.Groups)
|
||||||
@@ -184,9 +190,18 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri
|
|||||||
// Merge local and LDAP groups
|
// Merge local and LDAP groups
|
||||||
allGroups = append(allGroups, ldapGroups...)
|
allGroups = append(allGroups, ldapGroups...)
|
||||||
|
|
||||||
|
canonicalGroups := allGroups
|
||||||
|
// If not case sensitive, lowercase all
|
||||||
|
if !cs {
|
||||||
|
canonicalGroups = make([]string, len(allGroups))
|
||||||
|
for i, v := range allGroups {
|
||||||
|
canonicalGroups[i] = strings.ToLower(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve policies
|
// Retrieve policies
|
||||||
var policies []string
|
var policies []string
|
||||||
for _, groupName := range allGroups {
|
for _, groupName := range canonicalGroups {
|
||||||
group, err := b.Group(ctx, req.Storage, groupName)
|
group, err := b.Group(ctx, req.Storage, groupName)
|
||||||
if err == nil && group != nil {
|
if err == nil && group != nil {
|
||||||
policies = append(policies, group.Policies...)
|
policies = append(policies, group.Policies...)
|
||||||
|
|||||||
@@ -31,6 +31,182 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
|||||||
return b, config.StorageView
|
return b, config.StorageView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLdapAuthBackend_CaseSensitivity(t *testing.T) {
|
||||||
|
var resp *logical.Response
|
||||||
|
var err error
|
||||||
|
b, storage := createBackendWithStorage(t)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
testVals := func(caseSensitive bool) {
|
||||||
|
// Clear storage
|
||||||
|
userList, err := storage.List(ctx, "user/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, user := range userList {
|
||||||
|
err = storage.Delete(ctx, "user/"+user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupList, err := storage.List(ctx, "group/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, group := range groupList {
|
||||||
|
err = storage.Delete(ctx, "group/"+group)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configReq := &logical.Request{
|
||||||
|
Path: "config",
|
||||||
|
Operation: logical.ReadOperation,
|
||||||
|
Storage: storage,
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(ctx, configReq)
|
||||||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||||||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("nil response")
|
||||||
|
}
|
||||||
|
if resp.Data["case_sensitive_names"].(bool) != caseSensitive {
|
||||||
|
t.Fatalf("expected case sensitivity %t, got %t", caseSensitive, resp.Data["case_sensitive_names"].(bool))
|
||||||
|
}
|
||||||
|
|
||||||
|
groupReq := &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"policies": "grouppolicy",
|
||||||
|
},
|
||||||
|
Path: "groups/EngineerS",
|
||||||
|
Storage: storage,
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(ctx, groupReq)
|
||||||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||||||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||||
|
}
|
||||||
|
keys, err := storage.List(ctx, "group/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
switch caseSensitive {
|
||||||
|
case true:
|
||||||
|
if keys[0] != "EngineerS" {
|
||||||
|
t.Fatalf("bad: %s", keys[0])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if keys[0] != "engineers" {
|
||||||
|
t.Fatalf("bad: %s", keys[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userReq := &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"groups": "EngineerS",
|
||||||
|
"policies": "userpolicy",
|
||||||
|
},
|
||||||
|
Path: "users/teSlA",
|
||||||
|
Storage: storage,
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(ctx, userReq)
|
||||||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||||||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||||
|
}
|
||||||
|
keys, err = storage.List(ctx, "user/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
switch caseSensitive {
|
||||||
|
case true:
|
||||||
|
if keys[0] != "teSlA" {
|
||||||
|
t.Fatalf("bad: %s", keys[0])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if keys[0] != "tesla" {
|
||||||
|
t.Fatalf("bad: %s", keys[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if caseSensitive {
|
||||||
|
// The online test server is actually case sensitive so we need to
|
||||||
|
// write again so it works
|
||||||
|
userReq = &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"groups": "EngineerS",
|
||||||
|
"policies": "userpolicy",
|
||||||
|
},
|
||||||
|
Path: "users/tesla",
|
||||||
|
Storage: storage,
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(ctx, userReq)
|
||||||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||||||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loginReq := &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "login/tesla",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"password": "password",
|
||||||
|
},
|
||||||
|
Storage: storage,
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(ctx, loginReq)
|
||||||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||||||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||||
|
}
|
||||||
|
expected := []string{"grouppolicy", "userpolicy"}
|
||||||
|
if !reflect.DeepEqual(expected, resp.Auth.Policies) {
|
||||||
|
t.Fatalf("bad: policies: expected: %q, actual: %q", expected, resp.Auth.Policies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configReq := &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "config",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
// Online LDAP test server
|
||||||
|
// http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
|
||||||
|
"url": "ldap://ldap.forumsys.com",
|
||||||
|
"userattr": "uid",
|
||||||
|
"userdn": "dc=example,dc=com",
|
||||||
|
"groupdn": "dc=example,dc=com",
|
||||||
|
"binddn": "cn=read-only-admin,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
Storage: storage,
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(ctx, configReq)
|
||||||
|
if err != nil || (resp != nil && resp.IsError()) {
|
||||||
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
testVals(false)
|
||||||
|
|
||||||
|
// Check that if the value is nil, on read it is case sensitive
|
||||||
|
configEntry, err := b.Config(ctx, configReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
configEntry.CaseSensitiveNames = nil
|
||||||
|
entry, err := logical.StorageEntryJSON("config", configEntry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = configReq.Storage.Put(ctx, entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testVals(true)
|
||||||
|
}
|
||||||
|
|
||||||
func TestLdapAuthBackend_UserPolicies(t *testing.T) {
|
func TestLdapAuthBackend_UserPolicies(t *testing.T) {
|
||||||
var resp *logical.Response
|
var resp *logical.Response
|
||||||
var err error
|
var err error
|
||||||
@@ -282,6 +458,7 @@ func testAccStepConfigUrl(t *testing.T) logicaltest.TestStep {
|
|||||||
"userattr": "uid",
|
"userattr": "uid",
|
||||||
"userdn": "dc=example,dc=com",
|
"userdn": "dc=example,dc=com",
|
||||||
"groupdn": "dc=example,dc=com",
|
"groupdn": "dc=example,dc=com",
|
||||||
|
"case_sensitive_names": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,6 +477,7 @@ func testAccStepConfigUrlWithAuthBind(t *testing.T) logicaltest.TestStep {
|
|||||||
"groupdn": "dc=example,dc=com",
|
"groupdn": "dc=example,dc=com",
|
||||||
"binddn": "cn=read-only-admin,dc=example,dc=com",
|
"binddn": "cn=read-only-admin,dc=example,dc=com",
|
||||||
"bindpass": "password",
|
"bindpass": "password",
|
||||||
|
"case_sensitive_names": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,6 +494,7 @@ func testAccStepConfigUrlWithDiscover(t *testing.T) logicaltest.TestStep {
|
|||||||
"userdn": "dc=example,dc=com",
|
"userdn": "dc=example,dc=com",
|
||||||
"groupdn": "dc=example,dc=com",
|
"groupdn": "dc=example,dc=com",
|
||||||
"discoverdn": true,
|
"discoverdn": true,
|
||||||
|
"case_sensitive_names": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,6 +510,7 @@ func testAccStepConfigUrlNoGroupDN(t *testing.T) logicaltest.TestStep {
|
|||||||
"userattr": "uid",
|
"userattr": "uid",
|
||||||
"userdn": "dc=example,dc=com",
|
"userdn": "dc=example,dc=com",
|
||||||
"discoverdn": true,
|
"discoverdn": true,
|
||||||
|
"case_sensitive_names": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/go-ldap/ldap"
|
"github.com/go-ldap/ldap"
|
||||||
log "github.com/hashicorp/go-hclog"
|
log "github.com/hashicorp/go-hclog"
|
||||||
multierror "github.com/hashicorp/go-multierror"
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/vault/helper/consts"
|
||||||
"github.com/hashicorp/vault/helper/tlsutil"
|
"github.com/hashicorp/vault/helper/tlsutil"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
"github.com/hashicorp/vault/logical/framework"
|
"github.com/hashicorp/vault/logical/framework"
|
||||||
@@ -109,11 +110,17 @@ Default: cn`,
|
|||||||
Default: "tls12",
|
Default: "tls12",
|
||||||
Description: "Maximum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'",
|
Description: "Maximum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'",
|
||||||
},
|
},
|
||||||
|
|
||||||
"deny_null_bind": &framework.FieldSchema{
|
"deny_null_bind": &framework.FieldSchema{
|
||||||
Type: framework.TypeBool,
|
Type: framework.TypeBool,
|
||||||
Default: true,
|
Default: true,
|
||||||
Description: "Denies an unauthenticated LDAP bind request if the user's password is empty; defaults to true",
|
Description: "Denies an unauthenticated LDAP bind request if the user's password is empty; defaults to true",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"case_sensitive_names": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeBool,
|
||||||
|
Description: "If true, case sensitivity will be used when comparing usernames and groups for matching policies.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
@@ -149,6 +156,9 @@ func (b *backend) Config(ctx context.Context, req *logical.Request) (*ConfigEntr
|
|||||||
|
|
||||||
if storedConfig == nil {
|
if storedConfig == nil {
|
||||||
// No user overrides, return default configuration
|
// No user overrides, return default configuration
|
||||||
|
result.CaseSensitiveNames = new(bool)
|
||||||
|
*result.CaseSensitiveNames = false
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +168,24 @@ func (b *backend) Config(ctx context.Context, req *logical.Request) (*ConfigEntr
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var persistNeeded bool
|
||||||
|
if result.CaseSensitiveNames == nil {
|
||||||
|
// Upgrade from before switching to case-insensitive
|
||||||
|
result.CaseSensitiveNames = new(bool)
|
||||||
|
*result.CaseSensitiveNames = true
|
||||||
|
persistNeeded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if persistNeeded && (b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary)) {
|
||||||
|
entry, err := logical.StorageEntryJSON("config", result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.logger = b.Logger()
|
result.logger = b.Logger()
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -189,6 +217,7 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f
|
|||||||
"discoverdn": cfg.DiscoverDN,
|
"discoverdn": cfg.DiscoverDN,
|
||||||
"tls_min_version": cfg.TLSMinVersion,
|
"tls_min_version": cfg.TLSMinVersion,
|
||||||
"tls_max_version": cfg.TLSMaxVersion,
|
"tls_max_version": cfg.TLSMaxVersion,
|
||||||
|
"case_sensitive_names": *cfg.CaseSensitiveNames,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
@@ -282,23 +311,33 @@ func (b *backend) newConfigEntry(d *framework.FieldData) (*ConfigEntry, error) {
|
|||||||
if startTLS {
|
if startTLS {
|
||||||
cfg.StartTLS = startTLS
|
cfg.StartTLS = startTLS
|
||||||
}
|
}
|
||||||
|
|
||||||
bindDN := d.Get("binddn").(string)
|
bindDN := d.Get("binddn").(string)
|
||||||
if bindDN != "" {
|
if bindDN != "" {
|
||||||
cfg.BindDN = bindDN
|
cfg.BindDN = bindDN
|
||||||
}
|
}
|
||||||
|
|
||||||
bindPass := d.Get("bindpass").(string)
|
bindPass := d.Get("bindpass").(string)
|
||||||
if bindPass != "" {
|
if bindPass != "" {
|
||||||
cfg.BindPassword = bindPass
|
cfg.BindPassword = bindPass
|
||||||
}
|
}
|
||||||
|
|
||||||
denyNullBind := d.Get("deny_null_bind").(bool)
|
denyNullBind := d.Get("deny_null_bind").(bool)
|
||||||
if denyNullBind {
|
if denyNullBind {
|
||||||
cfg.DenyNullBind = denyNullBind
|
cfg.DenyNullBind = denyNullBind
|
||||||
}
|
}
|
||||||
|
|
||||||
discoverDN := d.Get("discoverdn").(bool)
|
discoverDN := d.Get("discoverdn").(bool)
|
||||||
if discoverDN {
|
if discoverDN {
|
||||||
cfg.DiscoverDN = discoverDN
|
cfg.DiscoverDN = discoverDN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caseSensitiveNames, ok := d.GetOk("case_sensitive_names")
|
||||||
|
if ok {
|
||||||
|
cfg.CaseSensitiveNames = new(bool)
|
||||||
|
*cfg.CaseSensitiveNames = caseSensitiveNames.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +348,13 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *
|
|||||||
return logical.ErrorResponse(err.Error()), nil
|
return logical.ErrorResponse(err.Error()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
entry, err := logical.StorageEntryJSON("config", cfg)
|
entry, err := logical.StorageEntryJSON("config", cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -322,22 +368,23 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *
|
|||||||
|
|
||||||
type ConfigEntry struct {
|
type ConfigEntry struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
Url string `json:"url" structs:"url" mapstructure:"url"`
|
Url string `json:"url"`
|
||||||
UserDN string `json:"userdn" structs:"userdn" mapstructure:"userdn"`
|
UserDN string `json:"userdn"`
|
||||||
GroupDN string `json:"groupdn" structs:"groupdn" mapstructure:"groupdn"`
|
GroupDN string `json:"groupdn"`
|
||||||
GroupFilter string `json:"groupfilter" structs:"groupfilter" mapstructure:"groupfilter"`
|
GroupFilter string `json:"groupfilter"`
|
||||||
GroupAttr string `json:"groupattr" structs:"groupattr" mapstructure:"groupattr"`
|
GroupAttr string `json:"groupattr"`
|
||||||
UPNDomain string `json:"upndomain" structs:"upndomain" mapstructure:"upndomain"`
|
UPNDomain string `json:"upndomain"`
|
||||||
UserAttr string `json:"userattr" structs:"userattr" mapstructure:"userattr"`
|
UserAttr string `json:"userattr"`
|
||||||
Certificate string `json:"certificate" structs:"certificate" mapstructure:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
InsecureTLS bool `json:"insecure_tls" structs:"insecure_tls" mapstructure:"insecure_tls"`
|
InsecureTLS bool `json:"insecure_tls"`
|
||||||
StartTLS bool `json:"starttls" structs:"starttls" mapstructure:"starttls"`
|
StartTLS bool `json:"starttls"`
|
||||||
BindDN string `json:"binddn" structs:"binddn" mapstructure:"binddn"`
|
BindDN string `json:"binddn"`
|
||||||
BindPassword string `json:"bindpass" structs:"bindpass" mapstructure:"bindpass"`
|
BindPassword string `json:"bindpass"`
|
||||||
DenyNullBind bool `json:"deny_null_bind" structs:"deny_null_bind" mapstructure:"deny_null_bind"`
|
DenyNullBind bool `json:"deny_null_bind"`
|
||||||
DiscoverDN bool `json:"discoverdn" structs:"discoverdn" mapstructure:"discoverdn"`
|
DiscoverDN bool `json:"discoverdn"`
|
||||||
TLSMinVersion string `json:"tls_min_version" structs:"tls_min_version" mapstructure:"tls_min_version"`
|
TLSMinVersion string `json:"tls_min_version"`
|
||||||
TLSMaxVersion string `json:"tls_max_version" structs:"tls_max_version" mapstructure:"tls_max_version"`
|
TLSMaxVersion string `json:"tls_max_version"`
|
||||||
|
CaseSensitiveNames *bool `json:"case_sensitive_names,omitempty`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntry) GetTLSConfig(host string) (*tls.Config, error) {
|
func (c *ConfigEntry) GetTLSConfig(host string) (*tls.Config, error) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ldap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/helper/policyutil"
|
"github.com/hashicorp/vault/helper/policyutil"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
@@ -74,7 +75,20 @@ func (b *backend) pathGroupDelete(ctx context.Context, req *logical.Request, d *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) pathGroupRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
func (b *backend) pathGroupRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
group, err := b.Group(ctx, req.Storage, d.Get("name").(string))
|
groupname := d.Get("name").(string)
|
||||||
|
|
||||||
|
cfg, err := b.Config(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return logical.ErrorResponse("ldap backend not configured"), nil
|
||||||
|
}
|
||||||
|
if !*cfg.CaseSensitiveNames {
|
||||||
|
groupname = strings.ToLower(groupname)
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := b.Group(ctx, req.Storage, groupname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -90,8 +104,21 @@ func (b *backend) pathGroupRead(ctx context.Context, req *logical.Request, d *fr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) pathGroupWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
func (b *backend) pathGroupWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
groupname := d.Get("name").(string)
|
||||||
|
|
||||||
|
cfg, err := b.Config(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return logical.ErrorResponse("ldap backend not configured"), nil
|
||||||
|
}
|
||||||
|
if !*cfg.CaseSensitiveNames {
|
||||||
|
groupname = strings.ToLower(groupname)
|
||||||
|
}
|
||||||
|
|
||||||
// Store it
|
// Store it
|
||||||
entry, err := logical.StorageEntryJSON("group/"+d.Get("name").(string), &GroupEntry{
|
entry, err := logical.StorageEntryJSON("group/"+groupname, &GroupEntry{
|
||||||
Policies: policyutil.ParsePolicies(d.Get("policies")),
|
Policies: policyutil.ParsePolicies(d.Get("policies")),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -81,7 +81,20 @@ func (b *backend) pathUserDelete(ctx context.Context, req *logical.Request, d *f
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
user, err := b.User(ctx, req.Storage, d.Get("name").(string))
|
username := d.Get("name").(string)
|
||||||
|
|
||||||
|
cfg, err := b.Config(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return logical.ErrorResponse("ldap backend not configured"), nil
|
||||||
|
}
|
||||||
|
if !*cfg.CaseSensitiveNames {
|
||||||
|
username = strings.ToLower(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := b.User(ctx, req.Storage, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -98,15 +111,29 @@ func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *fra
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
name := d.Get("name").(string)
|
lowercaseGroups := false
|
||||||
groups := strutil.RemoveDuplicates(strutil.ParseStringSlice(d.Get("groups").(string), ","), false)
|
username := d.Get("name").(string)
|
||||||
|
|
||||||
|
cfg, err := b.Config(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return logical.ErrorResponse("ldap backend not configured"), nil
|
||||||
|
}
|
||||||
|
if !*cfg.CaseSensitiveNames {
|
||||||
|
username = strings.ToLower(username)
|
||||||
|
lowercaseGroups = true
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := strutil.RemoveDuplicates(strutil.ParseStringSlice(d.Get("groups").(string), ","), lowercaseGroups)
|
||||||
policies := policyutil.ParsePolicies(d.Get("policies"))
|
policies := policyutil.ParsePolicies(d.Get("policies"))
|
||||||
for i, g := range groups {
|
for i, g := range groups {
|
||||||
groups[i] = strings.TrimSpace(g)
|
groups[i] = strings.TrimSpace(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store it
|
// Store it
|
||||||
entry, err := logical.StorageEntryJSON("user/"+name, &UserEntry{
|
entry, err := logical.StorageEntryJSON("user/"+username, &UserEntry{
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
Policies: policies,
|
Policies: policies,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ This endpoint configures the LDAP auth method.
|
|||||||
|
|
||||||
- `url` `(string: <required>)` – The LDAP server to connect to. Examples:
|
- `url` `(string: <required>)` – The LDAP server to connect to. Examples:
|
||||||
`ldap://ldap.myorg.com`, `ldaps://ldap.myorg.com:636`
|
`ldap://ldap.myorg.com`, `ldaps://ldap.myorg.com:636`
|
||||||
|
- `case_sensitive_names` `(bool: false)` – If set, user and group names
|
||||||
|
assigned to policies within the backend will be case sensitive. Otherwise,
|
||||||
|
names will be normalized to lower case. Case will still be preserved when
|
||||||
|
sending the username to the LDAP server at login time; this is only for
|
||||||
|
matching local user/group definitions.
|
||||||
- `starttls` `(bool: false)` – If true, issues a `StartTLS` command after
|
- `starttls` `(bool: false)` – If true, issues a `StartTLS` command after
|
||||||
establishing an unencrypted connection.
|
establishing an unencrypted connection.
|
||||||
- `tls_min_version` `(string: tls12)` – Minimum TLS version to use. Accepted
|
- `tls_min_version` `(string: tls12)` – Minimum TLS version to use. Accepted
|
||||||
|
|||||||
Reference in New Issue
Block a user