deb.open-vault/vault/identity_store_groups_test.go

1487 lines
45 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package vault
import (
"fmt"
"reflect"
"sort"
"strings"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical"
)
func TestIdentityStore_Groups_AddByNameEntityUpdate(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)
// Create an entity and get its ID
entityRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
resp, err := c.identityStore.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID := resp.Data["id"].(string)
// Create a group containing the entity
groupName := "group-name"
expectedMemberEntityIDs := []string{entityID}
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": groupName,
"member_entity_ids": expectedMemberEntityIDs,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
// Remove the entity from the group
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": groupName,
"member_entity_ids": []string{},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
// Make sure the member no longer thinks it's in the group
entityIDReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "entity/id/" + entityID,
}
resp, err = c.identityStore.HandleRequest(ctx, entityIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
expectedGroupIDs := []string{}
actualGroupIDs := resp.Data["direct_group_ids"]
if !reflect.DeepEqual(expectedGroupIDs, actualGroupIDs) {
t.Fatalf("bad: direct_group_ids:\nexpected: %#v\nactual: %#v", expectedGroupIDs, actualGroupIDs)
}
}
func TestIdentityStore_FixOverwrittenMemberGroupIDs(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)
// Create a group
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "group1",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
groupID := resp.Data["id"].(string)
expectedMemberGroupIDs := []string{groupID}
// Create another group and add the above created group as its member
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "group2",
"policies": "default",
"member_group_ids": expectedMemberGroupIDs,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
// Create another group and add the above created group as its member
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group/name/group2",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"policies": "default,another-policy",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
// Read the group and check if the member_group_ids is intact
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group/name/group2",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
if !reflect.DeepEqual(resp.Data["member_group_ids"], expectedMemberGroupIDs) {
t.Fatalf("bad: member_group_ids; expected: %#v\n, actual: %#v", expectedMemberGroupIDs, resp.Data["member_group_ids"])
}
}
func TestIdentityStore_GroupEntityMembershipUpgrade(t *testing.T) {
c, keys, rootToken := TestCoreUnsealed(t)
// Create a group
resp, err := c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "testgroup",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
// Create a memdb transaction
txn := c.identityStore.db.Txn(true)
defer txn.Abort()
// Fetch the above created group
group, err := c.identityStore.MemDBGroupByNameInTxn(namespace.RootContext(nil), txn, "testgroup", true)
if err != nil {
t.Fatal(err)
}
// Manually add an invalid entity as the group's member
group.MemberEntityIDs = []string{"invalidentityid"}
ctx := namespace.RootContext(nil)
// Persist the group
err = c.identityStore.UpsertGroupInTxn(ctx, txn, group, true)
if err != nil {
t.Fatal(err)
}
txn.Commit()
// Perform seal and unseal forcing an upgrade
err = c.Seal(rootToken)
if err != nil {
t.Fatal(err)
}
for i, key := range keys {
unseal, err := TestCoreUnseal(c, key)
if err != nil {
t.Fatal(err)
}
if i+1 == len(keys) && !unseal {
t.Fatalf("failed to unseal")
}
}
// Read the group and ensure that invalid entity id is cleaned up
group, err = c.identityStore.MemDBGroupByName(namespace.RootContext(nil), "testgroup", false)
if err != nil {
t.Fatal(err)
}
if len(group.MemberEntityIDs) != 0 {
t.Fatalf("bad: member entity IDs; expected: none, actual: %#v", group.MemberEntityIDs)
}
}
func TestIdentityStore_MemberGroupIDDelete(t *testing.T) {
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
// Create a child group
resp, err := i.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "child",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
childGroupID := resp.Data["id"].(string)
// Create a parent group with the above group ID as its child
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "parent",
"member_group_ids": []string{childGroupID},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Ensure that member group ID is properly updated
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/parent",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
memberGroupIDs := resp.Data["member_group_ids"].([]string)
if len(memberGroupIDs) != 1 && memberGroupIDs[0] != childGroupID {
t.Fatalf("bad: member group ids; expected: %#v, actual: %#v", []string{childGroupID}, memberGroupIDs)
}
// Clear the member group IDs from the parent group
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/parent",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"member_group_ids": []string{},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Ensure that member group ID is properly deleted
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/parent",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
memberGroupIDs = resp.Data["member_group_ids"].([]string)
if len(memberGroupIDs) != 0 {
t.Fatalf("bad: length of member group ids; expected: %d, actual: %d", 0, len(memberGroupIDs))
}
}
func TestIdentityStore_CaseInsensitiveGroupName(t *testing.T) {
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
testGroupName := "testGroupName"
// Create an group with case sensitive name
resp, err := i.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": testGroupName,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
groupID := resp.Data["id"].(string)
// Lookup the group by ID and check that name returned is case sensitive
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/id/" + groupID,
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
groupName := resp.Data["name"].(string)
if groupName != testGroupName {
t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
}
// Lookup the group by case sensitive name
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/" + testGroupName,
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
groupName = resp.Data["name"].(string)
if groupName != testGroupName {
t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
}
// Lookup the group by case insensitive name
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/" + strings.ToLower(testGroupName),
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
groupName = resp.Data["name"].(string)
if groupName != testGroupName {
t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
}
// Ensure that there is only one group
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name",
Operation: logical.ListOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
if len(resp.Data["keys"].([]string)) != 1 {
t.Fatalf("bad length of groups; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
}
}
func TestIdentityStore_GroupByName(t *testing.T) {
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
// Create an entity using the "name" endpoint
resp, err := i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname",
Operation: logical.UpdateOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp == nil {
t.Fatalf("expected a non-nil response")
}
// Test the read by name endpoint
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp == nil || resp.Data["name"].(string) != "testgroupname" {
t.Fatalf("bad entity response: %#v", resp)
}
// Update group metadata using the name endpoint
groupMetadata := map[string]string{
"foo": "bar",
}
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"metadata": groupMetadata,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Check the updated result
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp == nil || !reflect.DeepEqual(resp.Data["metadata"].(map[string]string), groupMetadata) {
t.Fatalf("bad group response: %#v", resp)
}
// Delete the group using the name endpoint
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname",
Operation: logical.DeleteOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Check if deletion was successful
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
// Create 2 entities
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname",
Operation: logical.UpdateOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp == nil {
t.Fatalf("expected a non-nil response")
}
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name/testgroupname2",
Operation: logical.UpdateOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp == nil {
t.Fatalf("expected a non-nil response")
}
// List the entities by name
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "group/name",
Operation: logical.ListOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
expected := []string{"testgroupname2", "testgroupname"}
sort.Strings(expected)
actual := resp.Data["keys"].([]string)
sort.Strings(actual)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: group list response; expected: %#v\nactual: %#v", expected, actual)
}
}
func TestIdentityStore_Groups_TypeMembershipAdditions(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "external",
"member_entity_ids": "sampleentityid",
},
}
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
groupReq.Data = map[string]interface{}{
"type": "external",
"member_group_ids": "samplegroupid",
}
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
}
func TestIdentityStore_Groups_TypeImmutability(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
}
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
internalGroupID := resp.Data["id"].(string)
groupReq.Data = map[string]interface{}{
"type": "external",
}
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
externalGroupID := resp.Data["id"].(string)
// Try to mark internal group as external
groupReq.Data = map[string]interface{}{
"type": "external",
}
groupReq.Path = "group/id/" + internalGroupID
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
// Try to mark internal group as external
groupReq.Data = map[string]interface{}{
"type": "internal",
}
groupReq.Path = "group/id/" + externalGroupID
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
}
func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
var err error
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
// Create a dummy group
group := &identity.Group{
ID: "testgroupid",
Name: "testgroupname",
Metadata: map[string]string{
"testmetadatakey1": "testmetadatavalue1",
"testmetadatakey2": "testmetadatavalue2",
},
ParentGroupIDs: []string{"testparentgroupid1", "testparentgroupid2"},
MemberEntityIDs: []string{"testentityid1", "testentityid2"},
Policies: []string{"testpolicy1", "testpolicy2"},
BucketKey: i.groupPacker.BucketKey("testgroupid"),
}
// Insert it into memdb
txn := i.db.Txn(true)
defer txn.Abort()
err = i.MemDBUpsertGroupInTxn(txn, group)
if err != nil {
t.Fatal(err)
}
txn.Commit()
// Insert another dummy group
group = &identity.Group{
ID: "testgroupid2",
Name: "testgroupname2",
Metadata: map[string]string{
"testmetadatakey2": "testmetadatavalue2",
"testmetadatakey3": "testmetadatavalue3",
},
ParentGroupIDs: []string{"testparentgroupid2", "testparentgroupid3"},
MemberEntityIDs: []string{"testentityid2", "testentityid3"},
Policies: []string{"testpolicy2", "testpolicy3"},
BucketKey: i.groupPacker.BucketKey("testgroupid2"),
}
// Insert it into memdb
txn = i.db.Txn(true)
defer txn.Abort()
err = i.MemDBUpsertGroupInTxn(txn, group)
if err != nil {
t.Fatal(err)
}
txn.Commit()
var fetchedGroup *identity.Group
// Fetch group given the name
fetchedGroup, err = i.MemDBGroupByName(namespace.RootContext(nil), "testgroupname", false)
if err != nil {
t.Fatal(err)
}
if fetchedGroup == nil || fetchedGroup.Name != "testgroupname" {
t.Fatalf("failed to fetch an indexed group")
}
// Fetch group given the ID
fetchedGroup, err = i.MemDBGroupByID("testgroupid", false)
if err != nil {
t.Fatal(err)
}
if fetchedGroup == nil || fetchedGroup.Name != "testgroupname" {
t.Fatalf("failed to fetch an indexed group")
}
var fetchedGroups []*identity.Group
// Fetch the subgroups of a given group ID
fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid1", false)
if err != nil {
t.Fatal(err)
}
if len(fetchedGroups) != 1 || fetchedGroups[0].Name != "testgroupname" {
t.Fatalf("failed to fetch an indexed group")
}
fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid2", false)
if err != nil {
t.Fatal(err)
}
if len(fetchedGroups) != 2 {
t.Fatalf("failed to fetch a indexed groups")
}
// Fetch groups based on member entity ID
fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid1", false, false)
if err != nil {
t.Fatal(err)
}
if len(fetchedGroups) != 1 || fetchedGroups[0].Name != "testgroupname" {
t.Fatalf("failed to fetch an indexed group")
}
fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid2", false, false)
if err != nil {
t.Fatal(err)
}
if len(fetchedGroups) != 2 {
t.Fatalf("failed to fetch groups by entity ID")
}
}
func TestIdentityStore_GroupsCreateUpdate(t *testing.T) {
var resp *logical.Response
var err error
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
// Create an entity and get its ID
entityRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID1 := resp.Data["id"].(string)
// Create another entity and get its ID
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID2 := resp.Data["id"].(string)
// Create a group with the above created 2 entities as its members
groupData := map[string]interface{}{
"policies": "testpolicy1,testPolicy1 , testpolicy2",
"metadata": []string{"testkey1=testvalue1", "testkey2=testvalue2"},
"member_entity_ids": []string{entityID1, entityID2},
}
// Create a group and get its ID
groupReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group",
Data: groupData,
}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
memberGroupID1 := resp.Data["id"].(string)
// Create another group and get its ID
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
memberGroupID2 := resp.Data["id"].(string)
// Create a group with the above 2 groups as its members
groupData["member_group_ids"] = []string{memberGroupID1, memberGroupID2}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
groupID := resp.Data["id"].(string)
// Read the group using its iD and check if all the fields are properly
// set
groupReq = &logical.Request{
Operation: logical.ReadOperation,
Path: "group/id/" + groupID,
}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
expectedData := map[string]interface{}{
"policies": []string{"testpolicy1", "testpolicy2"},
"metadata": map[string]string{
"testkey1": "testvalue1",
"testkey2": "testvalue2",
},
"parent_group_ids": []string(nil),
}
expectedData["id"] = resp.Data["id"]
expectedData["type"] = resp.Data["type"]
expectedData["name"] = resp.Data["name"]
expectedData["member_group_ids"] = resp.Data["member_group_ids"]
expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
expectedData["creation_time"] = resp.Data["creation_time"]
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
expectedData["alias"] = resp.Data["alias"]
expectedData["namespace_id"] = "root"
if diff := deep.Equal(expectedData, resp.Data); diff != nil {
t.Fatal(diff)
}
// Update the policies and metadata in the group
groupReq.Operation = logical.UpdateOperation
groupReq.Data = groupData
// Update by setting ID in the param
groupData["id"] = groupID
groupData["policies"] = "updatedpolicy1,updatedpolicy2"
groupData["metadata"] = []string{"updatedkey=updatedvalue"}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
// Check if updates are reflected
groupReq.Operation = logical.ReadOperation
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
expectedData["metadata"] = map[string]string{
"updatedkey": "updatedvalue",
}
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
if !reflect.DeepEqual(expectedData, resp.Data) {
t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
}
}
func TestIdentityStore_GroupsCreateUpdateDuplicatePolicy(t *testing.T) {
var resp *logical.Response
var err error
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
// Create a group with the above created 2 entities as its members
groupData := map[string]interface{}{
"policies": []string{"testpolicy1", "testpolicy2"},
"metadata": []string{"testkey1=testvalue1", "testkey2=testvalue2"},
}
// Create a group and get its ID
groupReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group",
Data: groupData,
}
// Create a group with the above 2 groups as its members
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
groupID := resp.Data["id"].(string)
// Read the group using its iD and check if all the fields are properly
// set
groupReq = &logical.Request{
Operation: logical.ReadOperation,
Path: "group/id/" + groupID,
}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
expectedData := map[string]interface{}{
"policies": []string{"testpolicy1", "testpolicy2"},
"metadata": map[string]string{
"testkey1": "testvalue1",
"testkey2": "testvalue2",
},
"parent_group_ids": []string(nil),
}
expectedData["id"] = resp.Data["id"]
expectedData["type"] = resp.Data["type"]
expectedData["name"] = resp.Data["name"]
expectedData["creation_time"] = resp.Data["creation_time"]
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
expectedData["alias"] = resp.Data["alias"]
expectedData["namespace_id"] = "root"
expectedData["member_group_ids"] = resp.Data["member_group_ids"]
expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
if diff := deep.Equal(expectedData, resp.Data); diff != nil {
t.Fatal(diff)
}
// Update the policies and metadata in the group
groupReq.Operation = logical.UpdateOperation
groupReq.Data = groupData
// Update by setting ID in the param
groupData["id"] = groupID
groupData["policies"] = []string{"updatedpolicy1", "updatedpolicy2", "updatedpolicy2"}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
// Check if updates are reflected
groupReq.Operation = logical.ReadOperation
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
if !reflect.DeepEqual(expectedData, resp.Data) {
t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
}
}
func TestIdentityStore_GroupsCRUD_ByID(t *testing.T) {
var resp *logical.Response
var err error
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
// Create an entity and get its ID
entityRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID1 := resp.Data["id"].(string)
// Create another entity and get its ID
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID2 := resp.Data["id"].(string)
// Create a group with the above created 2 entities as its members
groupData := map[string]interface{}{
"policies": "testpolicy1,testpolicy2",
"metadata": []string{"testkey1=testvalue1", "testkey2=testvalue2"},
"member_entity_ids": []string{entityID1, entityID2},
}
// Create a group and get its ID
groupRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group",
Data: groupData,
}
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
memberGroupID1 := resp.Data["id"].(string)
// Create another group and get its ID
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
memberGroupID2 := resp.Data["id"].(string)
// Create a group with the above 2 groups as its members
groupData["member_group_ids"] = []string{memberGroupID1, memberGroupID2}
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
groupID := resp.Data["id"].(string)
// Read the group using its name and check if all the fields are properly
// set
groupReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "group/id/" + groupID,
}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
expectedData := map[string]interface{}{
"policies": []string{"testpolicy1", "testpolicy2"},
"metadata": map[string]string{
"testkey1": "testvalue1",
"testkey2": "testvalue2",
},
"parent_group_ids": []string(nil),
}
expectedData["id"] = resp.Data["id"]
expectedData["type"] = resp.Data["type"]
expectedData["name"] = resp.Data["name"]
expectedData["member_group_ids"] = resp.Data["member_group_ids"]
expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
expectedData["creation_time"] = resp.Data["creation_time"]
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
expectedData["alias"] = resp.Data["alias"]
expectedData["namespace_id"] = "root"
if diff := deep.Equal(expectedData, resp.Data); diff != nil {
t.Fatal(diff)
}
// Update the policies and metadata in the group
groupReq.Operation = logical.UpdateOperation
groupReq.Data = groupData
groupData["policies"] = "updatedpolicy1,updatedpolicy2"
groupData["metadata"] = []string{"updatedkey=updatedvalue"}
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
// Check if updates are reflected
groupReq.Operation = logical.ReadOperation
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
expectedData["metadata"] = map[string]string{
"updatedkey": "updatedvalue",
}
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
if !reflect.DeepEqual(expectedData, resp.Data) {
t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
}
// Check if delete is working properly
groupReq.Operation = logical.DeleteOperation
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
groupReq.Operation = logical.ReadOperation
resp, err = is.HandleRequest(ctx, groupReq)
if err != nil {
t.Fatal(err)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
}
func TestIdentityStore_GroupMultiCase(t *testing.T) {
var resp *logical.Response
var err error
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
groupRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group",
}
// Create 'build' group
buildGroupData := map[string]interface{}{
"name": "build",
"policies": "buildpolicy",
}
groupRegisterReq.Data = buildGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
buildGroupID := resp.Data["id"].(string)
// Create 'deploy' group
deployGroupData := map[string]interface{}{
"name": "deploy",
"policies": "deploypolicy",
}
groupRegisterReq.Data = deployGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
deployGroupID := resp.Data["id"].(string)
// Create an entity ID
entityRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID1 := resp.Data["id"].(string)
// Add the entity as a member of 'build' group
entityIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group/id/" + buildGroupID,
Data: map[string]interface{}{
"member_entity_ids": []string{entityID1},
},
}
resp, err = is.HandleRequest(ctx, entityIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
// Add the entity as a member of the 'deploy` group
entityIDReq.Path = "group/id/" + deployGroupID
resp, err = is.HandleRequest(ctx, entityIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
policiesResult, err := is.groupPoliciesByEntityID(entityID1)
if err != nil {
t.Fatal(err)
}
policies := []string{}
for _, nsPolicies := range policiesResult {
policies = append(policies, nsPolicies...)
}
sort.Strings(policies)
expected := []string{"deploypolicy", "buildpolicy"}
sort.Strings(expected)
if !reflect.DeepEqual(expected, policies) {
t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
}
}
/*
Test groups hierarchy:
------- eng(entityID3) -------
| |
----- vault ----- -- ops(entityID2) --
| | | |
kube(entityID1) identity build deploy
*/
func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
var resp *logical.Response
var err error
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
groupRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group",
}
// Create 'kube' group
kubeGroupData := map[string]interface{}{
"name": "kube",
"policies": "kubepolicy",
}
groupRegisterReq.Data = kubeGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
kubeGroupID := resp.Data["id"].(string)
// Create 'identity' group
identityGroupData := map[string]interface{}{
"name": "identity",
"policies": "identitypolicy",
}
groupRegisterReq.Data = identityGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
identityGroupID := resp.Data["id"].(string)
// Create 'build' group
buildGroupData := map[string]interface{}{
"name": "build",
"policies": "buildpolicy",
}
groupRegisterReq.Data = buildGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
buildGroupID := resp.Data["id"].(string)
// Create 'deploy' group
deployGroupData := map[string]interface{}{
"name": "deploy",
"policies": "deploypolicy",
}
groupRegisterReq.Data = deployGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
deployGroupID := resp.Data["id"].(string)
// Create 'vault' with 'kube' and 'identity' as member groups
vaultMemberGroupIDs := []string{kubeGroupID, identityGroupID}
vaultGroupData := map[string]interface{}{
"name": "vault",
"policies": "vaultpolicy",
"member_group_ids": vaultMemberGroupIDs,
}
groupRegisterReq.Data = vaultGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
vaultGroupID := resp.Data["id"].(string)
// Create 'ops' group with 'build' and 'deploy' as member groups
opsMemberGroupIDs := []string{buildGroupID, deployGroupID}
opsGroupData := map[string]interface{}{
"name": "ops",
"policies": "opspolicy",
"member_group_ids": opsMemberGroupIDs,
}
groupRegisterReq.Data = opsGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
opsGroupID := resp.Data["id"].(string)
// Create 'eng' group with 'vault' and 'ops' as member groups
engMemberGroupIDs := []string{vaultGroupID, opsGroupID}
engGroupData := map[string]interface{}{
"name": "eng",
"policies": "engpolicy",
"member_group_ids": engMemberGroupIDs,
}
groupRegisterReq.Data = engGroupData
resp, err = is.HandleRequest(ctx, groupRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
engGroupID := resp.Data["id"].(string)
/*
fmt.Printf("engGroupID: %#v\n", engGroupID)
fmt.Printf("vaultGroupID: %#v\n", vaultGroupID)
fmt.Printf("opsGroupID: %#v\n", opsGroupID)
fmt.Printf("kubeGroupID: %#v\n", kubeGroupID)
fmt.Printf("identityGroupID: %#v\n", identityGroupID)
fmt.Printf("buildGroupID: %#v\n", buildGroupID)
fmt.Printf("deployGroupID: %#v\n", deployGroupID)
*/
var memberGroupIDs []string
// Fetch 'eng' group
engGroup, err := is.MemDBGroupByID(engGroupID, false)
if err != nil {
t.Fatal(err)
}
memberGroupIDs, err = is.memberGroupIDsByID(engGroup.ID)
if err != nil {
t.Fatal(err)
}
sort.Strings(memberGroupIDs)
sort.Strings(engMemberGroupIDs)
if !reflect.DeepEqual(engMemberGroupIDs, memberGroupIDs) {
t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", engMemberGroupIDs, memberGroupIDs)
}
vaultGroup, err := is.MemDBGroupByID(vaultGroupID, false)
if err != nil {
t.Fatal(err)
}
memberGroupIDs, err = is.memberGroupIDsByID(vaultGroup.ID)
if err != nil {
t.Fatal(err)
}
sort.Strings(memberGroupIDs)
sort.Strings(vaultMemberGroupIDs)
if !reflect.DeepEqual(vaultMemberGroupIDs, memberGroupIDs) {
t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", vaultMemberGroupIDs, memberGroupIDs)
}
opsGroup, err := is.MemDBGroupByID(opsGroupID, false)
if err != nil {
t.Fatal(err)
}
memberGroupIDs, err = is.memberGroupIDsByID(opsGroup.ID)
if err != nil {
t.Fatal(err)
}
sort.Strings(memberGroupIDs)
sort.Strings(opsMemberGroupIDs)
if !reflect.DeepEqual(opsMemberGroupIDs, memberGroupIDs) {
t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", opsMemberGroupIDs, memberGroupIDs)
}
groupUpdateReq := &logical.Request{
Operation: logical.UpdateOperation,
}
// Adding 'engGroupID' under 'kubeGroupID' should fail
groupUpdateReq.Path = "group/name/kube"
groupUpdateReq.Data = kubeGroupData
kubeGroupData["member_group_ids"] = []string{engGroupID}
resp, err = is.HandleRequest(ctx, groupUpdateReq)
if err != nil || resp == nil || !resp.IsError() {
t.Fatalf("expected an error response")
}
// Create an entity ID
entityRegisterReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID1 := resp.Data["id"].(string)
// Add the entity as a member of 'kube' group
entityIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group/id/" + kubeGroupID,
Data: map[string]interface{}{
"member_entity_ids": []string{entityID1},
},
}
resp, err = is.HandleRequest(ctx, entityIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
// Create a second entity ID
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID2 := resp.Data["id"].(string)
// Add the entity as a member of 'ops' group
entityIDReq.Path = "group/id/" + opsGroupID
entityIDReq.Data = map[string]interface{}{
"member_entity_ids": []string{entityID2},
}
resp, err = is.HandleRequest(ctx, entityIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
// Create a third entity ID
resp, err = is.HandleRequest(ctx, entityRegisterReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
entityID3 := resp.Data["id"].(string)
// Add the entity as a member of 'eng' group
entityIDReq.Path = "group/id/" + engGroupID
entityIDReq.Data = map[string]interface{}{
"member_entity_ids": []string{entityID3},
"member_group_ids": engMemberGroupIDs,
}
resp, err = is.HandleRequest(ctx, entityIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
policiesResult, err := is.groupPoliciesByEntityID(entityID1)
if err != nil {
t.Fatal(err)
}
var policies []string
for _, nsPolicies := range policiesResult {
policies = append(policies, nsPolicies...)
}
sort.Strings(policies)
expected := []string{"kubepolicy", "vaultpolicy", "engpolicy"}
sort.Strings(expected)
if !reflect.DeepEqual(expected, policies) {
t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
}
policiesResult, err = is.groupPoliciesByEntityID(entityID2)
if err != nil {
t.Fatal(err)
}
policies = nil
for _, nsPolicies := range policiesResult {
policies = append(policies, nsPolicies...)
}
sort.Strings(policies)
expected = []string{"opspolicy", "engpolicy"}
sort.Strings(expected)
if !reflect.DeepEqual(expected, policies) {
t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
}
policiesResult, err = is.groupPoliciesByEntityID(entityID3)
if err != nil {
t.Fatal(err)
}
policies = nil
for _, nsPolicies := range policiesResult {
policies = append(policies, nsPolicies...)
}
if len(policies) != 1 && policies[0] != "engpolicy" {
t.Fatalf("bad: policies; expected: 'engpolicy'\nactual:%#v", policies)
}
groups, inheritedGroups, err := is.groupsByEntityID(entityID1)
if err != nil {
t.Fatal(err)
}
if len(groups) != 1 {
t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
}
if len(inheritedGroups) != 2 {
t.Fatalf("bad: length of inheritedGroups; expected: 2, actual: %d", len(inheritedGroups))
}
groups, inheritedGroups, err = is.groupsByEntityID(entityID2)
if err != nil {
t.Fatal(err)
}
if len(groups) != 1 {
t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
}
if len(inheritedGroups) != 1 {
t.Fatalf("bad: length of inheritedGroups; expected: 1, actual: %d", len(inheritedGroups))
}
groups, inheritedGroups, err = is.groupsByEntityID(entityID3)
if err != nil {
t.Fatal(err)
}
if len(groups) != 1 {
t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
}
if len(inheritedGroups) != 0 {
t.Fatalf("bad: length of inheritedGroups; expected: 0, actual: %d", len(inheritedGroups))
}
}
func TestIdentityStore_GroupCycleDetection(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)
group1Name := "group1"
group2Name := "group2"
group3Name := "group3"
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group1Name,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to create group %q, err: %v, resp: %#v", group1Name, err, resp)
}
group1Id := resp.Data["id"].(string)
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group2Name,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to create group %q, err: %v, resp: %#v", group2Name, err, resp)
}
group2Id := resp.Data["id"].(string)
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group3Name,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to create group %q, err: %v, resp: %#v", group3Name, err, resp)
}
group3Id := resp.Data["id"].(string)
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group1Name,
"member_group_ids": []string{},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to update group %q, err: %v, resp: %#v", group1Name, err, resp)
}
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group2Name,
"member_group_ids": []string{group3Id},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to update group %q, err: %v, resp: %#v", group2Name, err, resp)
}
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group3Name,
"member_group_ids": []string{group1Id, group2Id},
},
})
if err != nil || resp == nil {
t.Fatalf("unexpected group update error for group %q, err: %v, resp: %#v", group3Name, err, resp)
}
if !resp.IsError() || resp.Error().Error() != fmt.Sprintf("%s %q", errCycleDetectedPrefix, group2Id) {
t.Fatalf("expected update to group %q to fail due to cycle, resp: %#v", group3Id, resp)
}
}