Tokenutilize ldap (#7036)

This commit is contained in:
Jeff Mitchell
2019-07-01 16:16:23 -04:00
committed by GitHub
parent 8e636cd8cd
commit 04c0bd6b94
4 changed files with 179 additions and 36 deletions

View File

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

View File

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

View File

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

View File

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