Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix entity memberships in external identity groups across mounts #4365

Merged
merged 3 commits into from
Apr 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions vault/identity_store_groups_ext_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package vault_test

import (
"testing"

"github.com/hashicorp/vault/api"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"

credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
)

// Testing the fix for GH-4351
func TestIdentityStore_ExternalGroupMembershipsAcrossMounts(t *testing.T) {
coreConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{
"ldap": credLdap.Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()

core := cluster.Cores[0].Core
vault.TestWaitActive(t, core)
client := cluster.Cores[0].Client

// Enable the first LDAP auth
err := client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
Type: "ldap",
})
if err != nil {
t.Fatal(err)
}

// Extract out the mount accessor for LDAP auth
auths, err := client.Sys().ListAuth()
if err != nil {
t.Fatal(err)
}
ldapMountAccessor1 := auths["ldap/"].Accessor

// Configure LDAP auth
_, err = client.Logical().Write("auth/ldap/config", 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",
})
if err != nil {
t.Fatal(err)
}

// Create a group in LDAP auth
_, err = client.Logical().Write("auth/ldap/groups/testgroup1", map[string]interface{}{
"policies": "testgroup1-policy",
})
if err != nil {
t.Fatal(err)
}

// Tie the group to a user
_, err = client.Logical().Write("auth/ldap/users/tesla", map[string]interface{}{
"policies": "default",
"groups": "testgroup1",
})
if err != nil {
t.Fatal(err)
}

// Create an external group
secret, err := client.Logical().Write("identity/group", map[string]interface{}{
"type": "external",
})
if err != nil {
t.Fatal(err)
}
ldapExtGroupID1 := secret.Data["id"].(string)

// Associate a group from LDAP auth as a group-alias in the external group
_, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
"name": "testgroup1",
"mount_accessor": ldapMountAccessor1,
"canonical_id": ldapExtGroupID1,
})
if err != nil {
t.Fatal(err)
}

// Login using LDAP
secret, err = client.Logical().Write("auth/ldap/login/tesla", map[string]interface{}{
"password": "password",
})
if err != nil {
t.Fatal(err)
}
ldapClientToken := secret.Auth.ClientToken

//
// By now, the entity ID of the token should be automatically added as a
// member in the external group.
//

// Extract the entity ID of the token
secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken)
if err != nil {
t.Fatal(err)
}
entityID := secret.Data["entity_id"].(string)

// Enable another LDAP auth mount
err = client.Sys().EnableAuthWithOptions("ldap2", &api.EnableAuthOptions{
Type: "ldap",
})
if err != nil {
t.Fatal(err)
}

// Extract the mount accessor
auths, err = client.Sys().ListAuth()
if err != nil {
t.Fatal(err)
}
ldapMountAccessor2 := auths["ldap2/"].Accessor

// Create an entity-alias asserting that the user "tesla" from the first
// and second LDAP mounts as the same.
_, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
"name": "tesla",
"mount_accessor": ldapMountAccessor2,
"canonical_id": entityID,
})
if err != nil {
t.Fatal(err)
}

// Configure second LDAP auth
_, err = client.Logical().Write("auth/ldap2/config", 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",
})
if err != nil {
t.Fatal(err)
}

// Create a group in second LDAP auth
_, err = client.Logical().Write("auth/ldap2/groups/testgroup2", map[string]interface{}{
"policies": "testgroup2-policy",
})
if err != nil {
t.Fatal(err)
}

// Create a user in second LDAP auth
_, err = client.Logical().Write("auth/ldap2/users/tesla", map[string]interface{}{
"policies": "default",
"groups": "testgroup2",
})
if err != nil {
t.Fatal(err)
}

// Create another external group
secret, err = client.Logical().Write("identity/group", map[string]interface{}{
"type": "external",
})
if err != nil {
t.Fatal(err)
}
ldapExtGroupID2 := secret.Data["id"].(string)

// Create a group-alias tying the external group to "testgroup2" group in second LDAP
_, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
"name": "testgroup2",
"mount_accessor": ldapMountAccessor2,
"canonical_id": ldapExtGroupID2,
})
if err != nil {
t.Fatal(err)
}

// Login using second LDAP
_, err = client.Logical().Write("auth/ldap2/login/tesla", map[string]interface{}{
"password": "password",
})
if err != nil {
t.Fatal(err)
}

//
// By now the same entity ID of the token from first LDAP should have been
// added as a member of the second external group.
//

// Check that entityID is present in both the external groups
secret, err = client.Logical().Read("identity/group/id/" + ldapExtGroupID1)
if err != nil {
t.Fatal(err)
}
extGroup1Entities := secret.Data["member_entity_ids"].([]interface{})

found := false
for _, item := range extGroup1Entities {
if item.(string) == entityID {
found = true
break
}
}
if !found {
t.Fatalf("missing entity ID %q first external group with ID %q", entityID, ldapExtGroupID1)
}

secret, err = client.Logical().Read("identity/group/id/" + ldapExtGroupID2)
if err != nil {
t.Fatal(err)
}
extGroup2Entities := secret.Data["member_entity_ids"].([]interface{})
found = false
for _, item := range extGroup2Entities {
if item.(string) == entityID {
found = true
break
}
}
if !found {
t.Fatalf("missing entity ID %q first external group with ID %q", entityID, ldapExtGroupID2)
}
}
11 changes: 11 additions & 0 deletions vault/identity_store_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2309,6 +2309,11 @@ func (i *IdentityStore) refreshExternalGroupMembershipsByEntityID(entityID strin
return err
}

mountAccessor := ""
if len(groupAliases) != 0 {
mountAccessor = groupAliases[0].MountAccessor
}

var newGroups []*identity.Group
for _, alias := range groupAliases {
aliasByFactors, err := i.MemDBAliasByFactors(alias.MountAccessor, alias.Name, true, true)
Expand Down Expand Up @@ -2352,6 +2357,12 @@ func (i *IdentityStore) refreshExternalGroupMembershipsByEntityID(entityID strin
continue
}

// If the external group is from a different mount, don't remove the
// entity ID from it.
if mountAccessor != "" && group.Alias.MountAccessor != mountAccessor {
continue
}

i.logger.Debug("removing member entity ID from external group", "member_entity_id", entityID, "group_id", group.ID)

group.MemberEntityIDs = strutil.StrListDelete(group.MemberEntityIDs, entityID)
Expand Down