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