mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-21 10:41:11 +01:00
A change in copystructure has caused some panics due to the custom copy function. I'm more nervous about production panics than I am about keeping some bad code wiping out some existing warnings, so remove the custom copy function and just allow direct setting of Warnings.
541 lines
14 KiB
Go
541 lines
14 KiB
Go
package ldap
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
logicaltest "github.com/hashicorp/vault/logical/testing"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
|
|
b := Backend()
|
|
if b == nil {
|
|
t.Fatalf("failed to create backend")
|
|
}
|
|
|
|
_, err := b.Backend.Setup(config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return b, config.StorageView
|
|
}
|
|
|
|
func TestLdapAuthBackend_UserPolicies(t *testing.T) {
|
|
var resp *logical.Response
|
|
var err error
|
|
b, storage := createBackendWithStorage(t)
|
|
|
|
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(configReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
|
}
|
|
|
|
groupReq := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"policies": "grouppolicy",
|
|
},
|
|
Path: "groups/engineers",
|
|
Storage: storage,
|
|
}
|
|
resp, err = b.HandleRequest(groupReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
|
}
|
|
|
|
userReq := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"groups": "engineers",
|
|
"policies": "userpolicy",
|
|
},
|
|
Path: "users/tesla",
|
|
Storage: storage,
|
|
}
|
|
|
|
resp, err = b.HandleRequest(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(loginReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%v resp:%#v", err, resp)
|
|
}
|
|
expected := []string{"default", "grouppolicy", "userpolicy"}
|
|
if !reflect.DeepEqual(expected, resp.Auth.Policies) {
|
|
t.Fatalf("bad: policies: expected: %q, actual: %q", expected, resp.Auth.Policies)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Acceptance test for LDAP Auth Backend
|
|
*
|
|
* The tests here rely on a public LDAP server:
|
|
* [http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/]
|
|
*
|
|
* ...as well as existence of a person object, `uid=tesla,dc=example,dc=com`,
|
|
* which is a member of a group, `ou=scientists,dc=example,dc=com`
|
|
*
|
|
* Querying the server from the command line:
|
|
* $ ldapsearch -x -H ldap://ldap.forumsys.com -b dc=example,dc=com -s sub \
|
|
* '(&(objectClass=groupOfUniqueNames)(uniqueMember=uid=tesla,dc=example,dc=com))'
|
|
*
|
|
* $ ldapsearch -x -H ldap://ldap.forumsys.com -b dc=example,dc=com -s sub uid=tesla
|
|
*/
|
|
func factory(t *testing.T) logical.Backend {
|
|
defaultLeaseTTLVal := time.Hour * 24
|
|
maxLeaseTTLVal := time.Hour * 24 * 32
|
|
b, err := Factory(&logical.BackendConfig{
|
|
Logger: nil,
|
|
System: &logical.StaticSystemView{
|
|
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
|
MaxLeaseTTLVal: maxLeaseTTLVal,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unable to create backend: %s", err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func TestBackend_basic(t *testing.T) {
|
|
b := factory(t)
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
Backend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepConfigUrl(t),
|
|
// Map Scientists group (from LDAP server) with foo policy
|
|
testAccStepGroup(t, "Scientists", "foo"),
|
|
|
|
// Map engineers group (local) with bar policy
|
|
testAccStepGroup(t, "engineers", "bar"),
|
|
|
|
// Map tesla user with local engineers group
|
|
testAccStepUser(t, "tesla", "engineers"),
|
|
|
|
// Authenticate
|
|
testAccStepLogin(t, "tesla", "password"),
|
|
|
|
// Verify both groups mappings can be listed back
|
|
testAccStepGroupList(t, []string{"engineers", "Scientists"}),
|
|
|
|
// Verify user mapping can be listed back
|
|
testAccStepUserList(t, []string{"tesla"}),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_basic_authbind(t *testing.T) {
|
|
b := factory(t)
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
Backend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepConfigUrlWithAuthBind(t),
|
|
testAccStepGroup(t, "Scientists", "foo"),
|
|
testAccStepGroup(t, "engineers", "bar"),
|
|
testAccStepUser(t, "tesla", "engineers"),
|
|
testAccStepLogin(t, "tesla", "password"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_basic_discover(t *testing.T) {
|
|
b := factory(t)
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
Backend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepConfigUrlWithDiscover(t),
|
|
testAccStepGroup(t, "Scientists", "foo"),
|
|
testAccStepGroup(t, "engineers", "bar"),
|
|
testAccStepUser(t, "tesla", "engineers"),
|
|
testAccStepLogin(t, "tesla", "password"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_basic_nogroupdn(t *testing.T) {
|
|
b := factory(t)
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
Backend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepConfigUrlNoGroupDN(t),
|
|
testAccStepGroup(t, "Scientists", "foo"),
|
|
testAccStepGroup(t, "engineers", "bar"),
|
|
testAccStepUser(t, "tesla", "engineers"),
|
|
testAccStepLoginNoGroupDN(t, "tesla", "password"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_groupCrud(t *testing.T) {
|
|
b := factory(t)
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
Backend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepGroup(t, "g1", "foo"),
|
|
testAccStepReadGroup(t, "g1", "default,foo"),
|
|
testAccStepDeleteGroup(t, "g1"),
|
|
testAccStepReadGroup(t, "g1", ""),
|
|
},
|
|
})
|
|
}
|
|
|
|
/*
|
|
* Test backend configuration defaults are successfully read.
|
|
*/
|
|
func TestBackend_configDefaultsAfterUpdate(t *testing.T) {
|
|
b := factory(t)
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
Backend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
logicaltest.TestStep{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config",
|
|
Data: map[string]interface{}{},
|
|
},
|
|
logicaltest.TestStep{
|
|
Operation: logical.ReadOperation,
|
|
Path: "config",
|
|
Check: func(resp *logical.Response) error {
|
|
if resp == nil {
|
|
return fmt.Errorf("bad: %#v", resp)
|
|
}
|
|
|
|
// Test well-known defaults
|
|
cfg := resp.Data
|
|
defaultGroupFilter := "(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))"
|
|
if cfg["groupfilter"] != defaultGroupFilter {
|
|
t.Errorf("Default mismatch: groupfilter. Expected: '%s', received :'%s'", defaultGroupFilter, cfg["groupfilter"])
|
|
}
|
|
|
|
defaultGroupAttr := "cn"
|
|
if cfg["groupattr"] != defaultGroupAttr {
|
|
t.Errorf("Default mismatch: groupattr. Expected: '%s', received :'%s'", defaultGroupAttr, cfg["groupattr"])
|
|
}
|
|
|
|
defaultUserAttr := "cn"
|
|
if cfg["userattr"] != defaultUserAttr {
|
|
t.Errorf("Default mismatch: userattr. Expected: '%s', received :'%s'", defaultUserAttr, cfg["userattr"])
|
|
}
|
|
|
|
defaultDenyNullBind := true
|
|
if cfg["deny_null_bind"] != defaultDenyNullBind {
|
|
t.Errorf("Default mismatch: deny_null_bind. Expected: '%s', received :'%s'", defaultDenyNullBind, cfg["deny_null_bind"])
|
|
}
|
|
|
|
return nil
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func testAccStepConfigUrl(t *testing.T) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
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",
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepConfigUrlWithAuthBind(t *testing.T) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
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/
|
|
// In this test we also exercise multiple URL support
|
|
"url": "foobar://ldap.example.com,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",
|
|
"bindpass": "password",
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepConfigUrlWithDiscover(t *testing.T) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
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",
|
|
"discoverdn": true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepConfigUrlNoGroupDN(t *testing.T) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
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",
|
|
"discoverdn": true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepGroup(t *testing.T, group string, policies string) logicaltest.TestStep {
|
|
t.Logf("[testAccStepGroup] - Registering group %s, policy %s", group, policies)
|
|
return logicaltest.TestStep{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "groups/" + group,
|
|
Data: map[string]interface{}{
|
|
"policies": policies,
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepReadGroup(t *testing.T, group string, policies string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.ReadOperation,
|
|
Path: "groups/" + group,
|
|
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 testAccStepDeleteGroup(t *testing.T, group string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.DeleteOperation,
|
|
Path: "groups/" + group,
|
|
}
|
|
}
|
|
|
|
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, groups string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "users/" + user,
|
|
Data: map[string]interface{}{
|
|
"groups": groups,
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepReadUser(t *testing.T, user string, groups string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.ReadOperation,
|
|
Path: "users/" + user,
|
|
Check: func(resp *logical.Response) error {
|
|
if resp == nil {
|
|
if groups == "" {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("bad: %#v", resp)
|
|
}
|
|
|
|
var d struct {
|
|
Groups string `mapstructure:"groups"`
|
|
}
|
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.Groups != groups {
|
|
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.UpdateOperation,
|
|
Path: "login/" + user,
|
|
Data: map[string]interface{}{
|
|
"password": pass,
|
|
},
|
|
Unauthenticated: true,
|
|
|
|
// Verifies user tesla maps to groups via local group (engineers) as well as remote group (Scientiests)
|
|
Check: logicaltest.TestCheckAuth([]string{"bar", "default", "foo"}),
|
|
}
|
|
}
|
|
|
|
func testAccStepLoginNoGroupDN(t *testing.T, user string, pass string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "login/" + user,
|
|
Data: map[string]interface{}{
|
|
"password": pass,
|
|
},
|
|
Unauthenticated: true,
|
|
|
|
// Verifies a search without defined GroupDN returns a warnting rather than failing
|
|
Check: func(resp *logical.Response) error {
|
|
if len(resp.Warnings) != 1 {
|
|
return fmt.Errorf("expected a warning due to no group dn, got: %#v", resp.Warnings)
|
|
}
|
|
|
|
return logicaltest.TestCheckAuth([]string{"bar", "default"})(resp)
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestLDAPEscape(t *testing.T) {
|
|
testcases := map[string]string{
|
|
"#test": "\\#test",
|
|
"test,hello": "test\\,hello",
|
|
"test,hel+lo": "test\\,hel\\+lo",
|
|
"test\\hello": "test\\\\hello",
|
|
" test ": "\\ test \\ ",
|
|
}
|
|
|
|
for test, answer := range testcases {
|
|
res := EscapeLDAPValue(test)
|
|
if res != answer {
|
|
t.Errorf("Failed to escape %s: %s != %s\n", test, res, answer)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testAccStepGroupList(t *testing.T, groups []string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.ListOperation,
|
|
Path: "groups",
|
|
Check: func(resp *logical.Response) error {
|
|
if resp.IsError() {
|
|
return fmt.Errorf("Got error response: %#v", *resp)
|
|
}
|
|
|
|
expected := make([]string, len(groups))
|
|
copy(expected, groups)
|
|
sort.Strings(expected)
|
|
|
|
sortedResponse := make([]string, len(resp.Data["keys"].([]string)))
|
|
copy(sortedResponse, resp.Data["keys"].([]string))
|
|
sort.Strings(sortedResponse)
|
|
|
|
if !reflect.DeepEqual(expected, sortedResponse) {
|
|
return fmt.Errorf("expected:\n%#v\ngot:\n%#v\n", expected, sortedResponse)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepUserList(t *testing.T, users []string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.ListOperation,
|
|
Path: "users",
|
|
Check: func(resp *logical.Response) error {
|
|
if resp.IsError() {
|
|
return fmt.Errorf("Got error response: %#v", *resp)
|
|
}
|
|
|
|
expected := make([]string, len(users))
|
|
copy(expected, users)
|
|
sort.Strings(expected)
|
|
|
|
sortedResponse := make([]string, len(resp.Data["keys"].([]string)))
|
|
copy(sortedResponse, resp.Data["keys"].([]string))
|
|
sort.Strings(sortedResponse)
|
|
|
|
if !reflect.DeepEqual(expected, sortedResponse) {
|
|
return fmt.Errorf("expected:\n%#v\ngot:\n%#v\n", expected, sortedResponse)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|