mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 03:27:54 +00:00
Tokenutilize ldap (#7036)
This commit is contained in:
@@ -77,7 +77,7 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri
|
||||
LDAP: ldaputil.NewLDAP(),
|
||||
}
|
||||
|
||||
c, err := ldapClient.DialLDAP(cfg)
|
||||
c, err := ldapClient.DialLDAP(cfg.ConfigEntry)
|
||||
if err != nil {
|
||||
return nil, logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri
|
||||
// Clean connection
|
||||
defer c.Close()
|
||||
|
||||
userBindDN, err := ldapClient.GetUserBindDN(cfg, c, username)
|
||||
userBindDN, err := ldapClient.GetUserBindDN(cfg.ConfigEntry, c, username)
|
||||
if err != nil {
|
||||
if b.Logger().IsDebug() {
|
||||
b.Logger().Debug("error getting user bind DN", "error", err)
|
||||
@@ -127,12 +127,12 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri
|
||||
}
|
||||
}
|
||||
|
||||
userDN, err := ldapClient.GetUserDN(cfg, c, userBindDN)
|
||||
userDN, err := ldapClient.GetUserDN(cfg.ConfigEntry, c, userBindDN)
|
||||
if err != nil {
|
||||
return nil, logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
|
||||
ldapGroups, err := ldapClient.GetLdapGroups(cfg, c, userDN, username)
|
||||
ldapGroups, err := ldapClient.GetLdapGroups(cfg.ConfigEntry, c, userDN, username)
|
||||
if err != nil {
|
||||
return nil, logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
|
||||
@@ -10,9 +10,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
|
||||
"github.com/hashicorp/vault/sdk/helper/ldaputil"
|
||||
"github.com/hashicorp/vault/sdk/helper/policyutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/tokenutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
@@ -581,6 +584,7 @@ func testAccStepConfigUrl(t *testing.T) logicaltest.TestStep {
|
||||
"userdn": "dc=example,dc=com",
|
||||
"groupdn": "dc=example,dc=com",
|
||||
"case_sensitive_names": true,
|
||||
"token_policies": "abc,xyz",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -600,6 +604,7 @@ func testAccStepConfigUrlWithAuthBind(t *testing.T) logicaltest.TestStep {
|
||||
"binddn": "cn=read-only-admin,dc=example,dc=com",
|
||||
"bindpass": "password",
|
||||
"case_sensitive_names": true,
|
||||
"token_policies": "abc,xyz",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -617,6 +622,7 @@ func testAccStepConfigUrlWithDiscover(t *testing.T) logicaltest.TestStep {
|
||||
"groupdn": "dc=example,dc=com",
|
||||
"discoverdn": true,
|
||||
"case_sensitive_names": true,
|
||||
"token_policies": "abc,xyz",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -752,7 +758,7 @@ func testAccStepLogin(t *testing.T, user string, pass string) logicaltest.TestSt
|
||||
Unauthenticated: true,
|
||||
|
||||
// Verifies user tesla maps to groups via local group (engineers) as well as remote group (Scientists)
|
||||
Check: logicaltest.TestCheckAuth([]string{"bar", "default", "foo"}),
|
||||
Check: logicaltest.TestCheckAuth([]string{"abc", "bar", "default", "foo", "xyz"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,7 +772,7 @@ func testAccStepLoginNoAttachedPolicies(t *testing.T, user string, pass string)
|
||||
Unauthenticated: true,
|
||||
|
||||
// Verifies user tesla maps to groups via local group (engineers) as well as remote group (Scientists)
|
||||
Check: logicaltest.TestCheckAuth([]string{"default"}),
|
||||
Check: logicaltest.TestCheckAuth([]string{"abc", "default", "xyz"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,3 +845,91 @@ func testAccStepUserList(t *testing.T, users []string) logicaltest.TestStep {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLdapAuthBackend_ConfigUpgrade(t *testing.T) {
|
||||
var resp *logical.Response
|
||||
var err error
|
||||
b, storage := createBackendWithStorage(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Write in some initial config
|
||||
configReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config",
|
||||
Data: map[string]interface{}{
|
||||
"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",
|
||||
"token_period": "5m",
|
||||
"token_explicit_max_ttl": "24h",
|
||||
},
|
||||
Storage: storage,
|
||||
}
|
||||
resp, err = b.HandleRequest(ctx, configReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
fd, err := b.getConfigFieldData()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defParams, err := ldaputil.NewConfigEntry(nil, fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
falseBool := new(bool)
|
||||
*falseBool = false
|
||||
|
||||
exp := &ldapConfigEntry{
|
||||
TokenParams: tokenutil.TokenParams{
|
||||
TokenPeriod: 5 * time.Minute,
|
||||
TokenExplicitMaxTTL: 24 * time.Hour,
|
||||
},
|
||||
ConfigEntry: &ldaputil.ConfigEntry{
|
||||
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",
|
||||
GroupFilter: defParams.GroupFilter,
|
||||
DenyNullBind: defParams.DenyNullBind,
|
||||
GroupAttr: defParams.GroupAttr,
|
||||
TLSMinVersion: defParams.TLSMinVersion,
|
||||
TLSMaxVersion: defParams.TLSMaxVersion,
|
||||
CaseSensitiveNames: falseBool,
|
||||
},
|
||||
}
|
||||
|
||||
configEntry, err := b.Config(ctx, configReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := deep.Equal(exp, configEntry); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
// Store just the config entry portion, for upgrade testing
|
||||
entry, err := logical.StorageEntryJSON("config", configEntry.ConfigEntry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = configReq.Storage.Put(ctx, entry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configEntry, err = b.Config(ctx, configReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// We won't have token params anymore so nil those out
|
||||
exp.TokenParams = tokenutil.TokenParams{}
|
||||
if diff := deep.Equal(exp, configEntry); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/sdk/helper/ldaputil"
|
||||
"github.com/hashicorp/vault/sdk/helper/tokenutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
func pathConfig(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
p := &framework.Path{
|
||||
Pattern: `config`,
|
||||
Fields: ldaputil.ConfigFields(),
|
||||
|
||||
@@ -22,40 +23,45 @@ func pathConfig(b *backend) *framework.Path {
|
||||
HelpSynopsis: pathConfigHelpSyn,
|
||||
HelpDescription: pathConfigHelpDesc,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct ConfigEntry struct using stored configuration.
|
||||
*/
|
||||
func (b *backend) Config(ctx context.Context, req *logical.Request) (*ldaputil.ConfigEntry, error) {
|
||||
// Schema for ConfigEntry
|
||||
fd, err := b.getConfigFieldData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new ConfigEntry, filling in defaults where appropriate
|
||||
result, err := ldaputil.NewConfigEntry(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (b *backend) Config(ctx context.Context, req *logical.Request) (*ldapConfigEntry, error) {
|
||||
storedConfig, err := req.Storage.Get(ctx, "config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if storedConfig == nil {
|
||||
// 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
|
||||
}
|
||||
|
||||
// No user overrides, return default configuration
|
||||
result.CaseSensitiveNames = new(bool)
|
||||
*result.CaseSensitiveNames = false
|
||||
|
||||
return result, nil
|
||||
return &ldapConfigEntry{ConfigEntry: result}, nil
|
||||
}
|
||||
|
||||
// Deserialize stored configuration.
|
||||
// Fields not specified in storedConfig will retain their defaults.
|
||||
if err := storedConfig.DecodeJSON(&result); err != nil {
|
||||
result := new(ldapConfigEntry)
|
||||
result.ConfigEntry = new(ldaputil.ConfigEntry)
|
||||
if err := storedConfig.DecodeJSON(result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -89,15 +95,25 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: cfg.PasswordlessMap(),
|
||||
}
|
||||
return resp, nil
|
||||
data := cfg.PasswordlessMap()
|
||||
cfg.PopulateTokenData(data)
|
||||
|
||||
return &logical.Response{
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
cfg, err := b.Config(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Build a ConfigEntry struct out of the supplied FieldData
|
||||
cfg, err := ldaputil.NewConfigEntry(d)
|
||||
cfg.ConfigEntry, err = ldaputil.NewConfigEntry(cfg.ConfigEntry, d)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
@@ -109,6 +125,10 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *
|
||||
*cfg.CaseSensitiveNames = false
|
||||
}
|
||||
|
||||
if err := cfg.ParseTokenFields(req, d); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON("config", cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -140,6 +160,11 @@ func (b *backend) getConfigFieldData() (*framework.FieldData, error) {
|
||||
return &fd, nil
|
||||
}
|
||||
|
||||
type ldapConfigEntry struct {
|
||||
tokenutil.TokenParams
|
||||
*ldaputil.ConfigEntry
|
||||
}
|
||||
|
||||
const pathConfigHelpSyn = `
|
||||
Configure the LDAP server to connect to, along with its options.
|
||||
`
|
||||
|
||||
@@ -3,7 +3,6 @@ package ldap
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/policyutil"
|
||||
@@ -51,6 +50,14 @@ func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Requ
|
||||
}
|
||||
|
||||
func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
cfg, err := b.Config(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
return logical.ErrorResponse("auth method not configured"), nil
|
||||
}
|
||||
|
||||
username := d.Get("username").(string)
|
||||
password := d.Get("password").(string)
|
||||
|
||||
@@ -68,10 +75,7 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framew
|
||||
resp = &logical.Response{}
|
||||
}
|
||||
|
||||
sort.Strings(policies)
|
||||
|
||||
resp.Auth = &logical.Auth{
|
||||
Policies: policies,
|
||||
auth := &logical.Auth{
|
||||
Metadata: map[string]string{
|
||||
"username": username,
|
||||
},
|
||||
@@ -79,14 +83,20 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framew
|
||||
"password": password,
|
||||
},
|
||||
DisplayName: username,
|
||||
LeaseOptions: logical.LeaseOptions{
|
||||
Renewable: true,
|
||||
},
|
||||
Alias: &logical.Alias{
|
||||
Name: username,
|
||||
},
|
||||
}
|
||||
|
||||
cfg.PopulateTokenAuth(auth)
|
||||
|
||||
// Add in configured policies from mappings
|
||||
if len(policies) > 0 {
|
||||
auth.Policies = append(auth.Policies, policies...)
|
||||
}
|
||||
|
||||
resp.Auth = auth
|
||||
|
||||
for _, groupName := range groupNames {
|
||||
if groupName == "" {
|
||||
continue
|
||||
@@ -99,6 +109,14 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framew
|
||||
}
|
||||
|
||||
func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
cfg, err := b.Config(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
return logical.ErrorResponse("auth method not configured"), nil
|
||||
}
|
||||
|
||||
username := req.Auth.Metadata["username"]
|
||||
password := req.Auth.InternalData["password"].(string)
|
||||
|
||||
@@ -106,12 +124,18 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f
|
||||
if len(loginPolicies) == 0 {
|
||||
return resp, err
|
||||
}
|
||||
finalPolicies := cfg.TokenPolicies
|
||||
if len(loginPolicies) > 0 {
|
||||
finalPolicies = append(finalPolicies, loginPolicies...)
|
||||
}
|
||||
|
||||
if !policyutil.EquivalentPolicies(loginPolicies, req.Auth.TokenPolicies) {
|
||||
if !policyutil.EquivalentPolicies(finalPolicies, req.Auth.TokenPolicies) {
|
||||
return nil, fmt.Errorf("policies have changed, not renewing")
|
||||
}
|
||||
|
||||
resp.Auth = req.Auth
|
||||
resp.Auth.TTL = cfg.TokenTTL
|
||||
resp.Auth.MaxTTL = cfg.TokenMaxTTL
|
||||
|
||||
// Remove old aliases
|
||||
resp.Auth.GroupAliases = nil
|
||||
|
||||
Reference in New Issue
Block a user