ldap: add ability to set policies based on username as well as groups
This commit is contained in:
parent
0e2edc2378
commit
301a22295d
|
@ -21,6 +21,7 @@ func Backend() *framework.Backend {
|
|||
Root: []string{
|
||||
"config",
|
||||
"groups/*",
|
||||
"users/*",
|
||||
},
|
||||
|
||||
Unauthenticated: []string{
|
||||
|
@ -32,6 +33,7 @@ func Backend() *framework.Backend {
|
|||
pathLogin(&b),
|
||||
pathConfig(&b),
|
||||
pathGroups(&b),
|
||||
pathUsers(&b),
|
||||
}),
|
||||
|
||||
AuthRenew: b.pathLoginRenew,
|
||||
|
@ -135,6 +137,12 @@ func (b *backend) Login(req *logical.Request, username string, password string)
|
|||
|
||||
var allgroups []string
|
||||
var policies []string
|
||||
|
||||
user, err := b.User(req.Storage, username)
|
||||
if err == nil && user != nil {
|
||||
policies = append(policies, user.Policies...)
|
||||
}
|
||||
|
||||
for _, e := range sresult.Entries {
|
||||
dn, err := ldap.ParseDN(e.DN)
|
||||
if err != nil || len(dn.RDNs) == 0 || len(dn.RDNs[0].Attributes) == 0 {
|
||||
|
|
|
@ -17,6 +17,7 @@ func TestBackend_basic(t *testing.T) {
|
|||
Steps: []logicaltest.TestStep{
|
||||
testAccStepConfigUrl(t),
|
||||
testAccStepGroup(t, "scientists", "foo"),
|
||||
testAccStepUser(t, "tesla", "bar"),
|
||||
testAccStepLogin(t, "tesla", "password"),
|
||||
},
|
||||
})
|
||||
|
@ -96,6 +97,65 @@ func testAccStepDeleteGroup(t *testing.T, group string) logicaltest.TestStep {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBackend_userCrud(t *testing.T) {
|
||||
b := Backend()
|
||||
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepUser(t, "g1", "bar"),
|
||||
testAccStepReadUser(t, "g1", "bar"),
|
||||
testAccStepDeleteUser(t, "g1"),
|
||||
testAccStepReadUser(t, "g1", ""),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccStepUser(t *testing.T, user string, policies string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "users/" + user,
|
||||
Data: map[string]interface{}{
|
||||
"policies": policies,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepReadUser(t *testing.T, user string, policies string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "users/" + user,
|
||||
Check: func(resp *logical.Response) error {
|
||||
if resp == nil {
|
||||
if policies == "" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("bad: %#v", resp)
|
||||
}
|
||||
|
||||
var d struct {
|
||||
Policies string `mapstructure:"policies"`
|
||||
}
|
||||
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Policies != policies {
|
||||
return fmt.Errorf("bad: %#v", resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepDeleteUser(t *testing.T, user string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.DeleteOperation,
|
||||
Path: "users/" + user,
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepLogin(t *testing.T, user string, pass string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
|
@ -105,7 +165,7 @@ func testAccStepLogin(t *testing.T, user string, pass string) logicaltest.TestSt
|
|||
},
|
||||
Unauthenticated: true,
|
||||
|
||||
Check: logicaltest.TestCheckAuth([]string{"foo"}),
|
||||
Check: logicaltest.TestCheckAuth([]string{"foo", "bar"}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
118
builtin/credential/ldap/path_users.go
Normal file
118
builtin/credential/ldap/path_users.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func pathUsers(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: `users/(?P<name>.+)`,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the LDAP user.",
|
||||
},
|
||||
|
||||
"policies": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Comma-separated list of policies associated to the user.",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.DeleteOperation: b.pathUserDelete,
|
||||
logical.ReadOperation: b.pathUserRead,
|
||||
logical.WriteOperation: b.pathUserWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) User(s logical.Storage, n string) (*UserEntry, error) {
|
||||
entry, err := s.Get("user/" + n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var result UserEntry
|
||||
if err := entry.DecodeJSON(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathUserDelete(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
err := req.Storage.Delete("user/" + d.Get("name").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathUserRead(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
user, err := b.User(req.Storage, d.Get("name").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"policies": strings.Join(user.Policies, ","),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathUserWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
name := d.Get("name").(string)
|
||||
policies := strings.Split(d.Get("policies").(string), ",")
|
||||
for i, p := range policies {
|
||||
policies[i] = strings.TrimSpace(p)
|
||||
}
|
||||
|
||||
// Store it
|
||||
entry, err := logical.StorageEntryJSON("user/"+name, &UserEntry{
|
||||
Policies: policies,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Storage.Put(entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type UserEntry struct {
|
||||
Policies []string
|
||||
}
|
||||
|
||||
const pathUserHelpSyn = `
|
||||
Manage users allowed to authenticate.
|
||||
`
|
||||
|
||||
const pathUserHelpDesc = `
|
||||
This endpoint allows you to create, read, update, and delete configuration
|
||||
for LDAP users that are allowed to authenticate, and associate policies to
|
||||
them.
|
||||
|
||||
Deleting a user will not revoke auth for prior authenticated users in that
|
||||
user. To do this, do a revoke on "login/<username>" for
|
||||
the usernames you want revoked.
|
||||
`
|
Loading…
Reference in a new issue