open-vault/vault/identity_store_entities_test.go
Christopher Swenson 2c8e88ab67
Check if plugin version matches running version (#17182)
Check if plugin version matches running version

When registering a plugin, we check if the request version matches the
self-reported version from the plugin. If these do not match, we log a
warning.

This uncovered a few missing pieces for getting the database version
code fully working.

We added an environment variable that helps us unit test the running
version behavior as well, but only for approle, postgresql, and consul
plugins.

Return 400 on plugin not found or version mismatch

Populate the running SHA256 of plugins in the mount and auth tables (#17217)
2022-09-21 12:25:04 -07:00

1311 lines
35 KiB
Go

package vault
import (
"context"
"fmt"
"reflect"
"sort"
"strings"
"testing"
"github.com/hashicorp/go-uuid"
credGithub "github.com/hashicorp/vault/builtin/credential/github"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/logical"
)
func TestIdentityStore_EntityDeleteGroupMembershipUpdate(t *testing.T) {
i, _, _ := testIdentityStoreWithGithubAuth(namespace.RootContext(nil), t)
// Create an entity
resp, err := i.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "entity",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "testentity",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
entityID := resp.Data["id"].(string)
// Create a group
resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "testgroup",
"member_entity_ids": []string{entityID},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
// Ensure that the group has entity ID as its member
resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "group/name/testgroup",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
expected := []string{entityID}
actual := resp.Data["member_entity_ids"].([]string)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: member entity ids; expected: %#v\nactual: %#v", expected, actual)
}
// Delete the entity
resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "entity/name/testentity",
Operation: logical.DeleteOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
// Ensure that the group does not have entity ID as it's member anymore
resp, err = i.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "group/name/testgroup",
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
expected = []string{}
actual = resp.Data["member_entity_ids"].([]string)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: member entity ids; expected: %#v\nactual: %#v", expected, actual)
}
}
func TestIdentityStore_CaseInsensitiveEntityName(t *testing.T) {
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
testEntityName := "testEntityName"
// Create an entity with case sensitive name
resp, err := i.HandleRequest(ctx, &logical.Request{
Path: "entity",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": testEntityName,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
entityID := resp.Data["id"].(string)
// Lookup the entity by ID and check that name returned is case sensitive
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "entity/id/" + entityID,
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
}
entityName := resp.Data["name"].(string)
if entityName != testEntityName {
t.Fatalf("bad entity name; expected: %q, actual: %q", testEntityName, entityName)
}
// Lookup the entity by case sensitive name
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "entity/name/" + testEntityName,
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
entityName = resp.Data["name"].(string)
if entityName != testEntityName {
t.Fatalf("bad entity name; expected: %q, actual: %q", testEntityName, entityName)
}
// Lookup the entity by case insensitive name
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "entity/name/" + strings.ToLower(testEntityName),
Operation: logical.ReadOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
entityName = resp.Data["name"].(string)
if entityName != testEntityName {
t.Fatalf("bad entity name; expected: %q, actual: %q", testEntityName, entityName)
}
// Ensure that there is only one entity
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "entity/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 entities; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
}
}
func TestIdentityStore_EntityByName(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: "entity/name/testentityname",
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: "entity/name/testentityname",
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) != "testentityname" {
t.Fatalf("bad entity response: %#v", resp)
}
// Update entity metadata using the name endpoint
entityMetadata := map[string]string{
"foo": "bar",
}
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "entity/name/testentityname",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"metadata": entityMetadata,
},
})
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: "entity/name/testentityname",
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), entityMetadata) {
t.Fatalf("bad entity response: %#v", resp)
}
// Delete the entity using the name endpoint
resp, err = i.HandleRequest(ctx, &logical.Request{
Path: "entity/name/testentityname",
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: "entity/name/testentityname",
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: "entity/name/testentityname",
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: "entity/name/testentityname2",
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: "entity/name",
Operation: logical.ListOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
expected := []string{"testentityname2", "testentityname"}
sort.Strings(expected)
actual := resp.Data["keys"].([]string)
sort.Strings(actual)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: entity list response; expected: %#v\nactual: %#v", expected, actual)
}
}
func TestIdentityStore_EntityReadGroupIDs(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
entityReq := &logical.Request{
Path: "entity",
Operation: logical.UpdateOperation,
}
resp, err = i.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
entityID := resp.Data["id"].(string)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"member_entity_ids": []string{
entityID,
},
},
}
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
groupID := resp.Data["id"].(string)
// Create another group with the above created group as its subgroup
groupReq.Data = map[string]interface{}{
"member_group_ids": []string{groupID},
}
resp, err = i.HandleRequest(ctx, groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
inheritedGroupID := resp.Data["id"].(string)
lookupReq := &logical.Request{
Path: "lookup/entity",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "id",
"id": entityID,
},
}
resp, err = i.HandleRequest(ctx, lookupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
expected := []string{groupID, inheritedGroupID}
actual := resp.Data["group_ids"].([]string)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: group_ids; expected: %#v\nactual: %#v\n", expected, actual)
}
expected = []string{groupID}
actual = resp.Data["direct_group_ids"].([]string)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: direct_group_ids; expected: %#v\nactual: %#v\n", expected, actual)
}
expected = []string{inheritedGroupID}
actual = resp.Data["inherited_group_ids"].([]string)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: inherited_group_ids; expected: %#v\nactual: %#v\n", expected, actual)
}
}
func TestIdentityStore_EntityCreateUpdate(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
entityData := map[string]interface{}{
"name": "testentityname",
"metadata": []string{"someusefulkey=someusefulvalue"},
"policies": []string{"testpolicy1", "testpolicy2"},
}
entityReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: entityData,
}
// Create the entity
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityID := resp.Data["id"].(string)
updateData := map[string]interface{}{
// Set the entity ID here
"id": entityID,
"name": "updatedentityname",
"metadata": []string{"updatedkey=updatedvalue"},
"policies": []string{"updatedpolicy1", "updatedpolicy2"},
}
entityReq.Data = updateData
// Update the entity
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityReq.Path = "entity/id/" + entityID
entityReq.Operation = logical.ReadOperation
// Read the entity
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["id"] != entityID ||
resp.Data["name"] != updateData["name"] ||
!reflect.DeepEqual(resp.Data["policies"], updateData["policies"]) {
t.Fatalf("bad: entity response after update; resp: %#v\n updateData: %#v\n", resp.Data, updateData)
}
}
func TestIdentityStore_BatchDelete(t *testing.T) {
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
ids := make([]string, 10000)
for i := 0; i < 10000; i++ {
entityData := map[string]interface{}{
"name": fmt.Sprintf("entity-%d", i),
}
entityReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: entityData,
}
// Create the entity
resp, err := is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
ids[i] = resp.Data["id"].(string)
}
deleteReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity/batch-delete",
Data: map[string]interface{}{
"entity_ids": ids,
},
}
resp, err := is.HandleRequest(ctx, deleteReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
for _, entityID := range ids {
// Read the entity
resp, err := is.HandleRequest(ctx, &logical.Request{
Operation: logical.ReadOperation,
Path: "entity/id/" + entityID,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp != nil {
t.Fatal(resp)
}
}
}
func TestIdentityStore_CloneImmutability(t *testing.T) {
alias := &identity.Alias{
ID: "testaliasid",
Name: "testaliasname",
MergedFromCanonicalIDs: []string{"entityid1"},
}
entity := &identity.Entity{
ID: "testentityid",
Name: "testentityname",
Aliases: []*identity.Alias{
alias,
},
}
clonedEntity, err := entity.Clone()
if err != nil {
t.Fatal(err)
}
// Modify entity
entity.Aliases[0].ID = "invalidid"
if clonedEntity.Aliases[0].ID == "invalidid" {
t.Fatalf("cloned entity is mutated")
}
clonedAlias, err := alias.Clone()
if err != nil {
t.Fatal(err)
}
alias.MergedFromCanonicalIDs[0] = "invalidid"
if clonedAlias.MergedFromCanonicalIDs[0] == "invalidid" {
t.Fatalf("cloned alias is mutated")
}
}
func TestIdentityStore_MemDBImmutability(t *testing.T) {
var err error
ctx := namespace.RootContext(nil)
is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
validateMountResp := is.router.ValidateMountByAccessor(githubAccessor)
if validateMountResp == nil {
t.Fatal("failed to validate github auth mount")
}
alias1 := &identity.Alias{
CanonicalID: "testentityid",
ID: "testaliasid",
MountAccessor: githubAccessor,
MountType: validateMountResp.MountType,
Name: "testaliasname",
Metadata: map[string]string{
"testkey1": "testmetadatavalue1",
"testkey2": "testmetadatavalue2",
},
}
entity := &identity.Entity{
ID: "testentityid",
Name: "testentityname",
Metadata: map[string]string{
"someusefulkey": "someusefulvalue",
},
Aliases: []*identity.Alias{
alias1,
},
}
entity.BucketKey = is.entityPacker.BucketKey(entity.ID)
txn := is.db.Txn(true)
defer txn.Abort()
err = is.MemDBUpsertEntityInTxn(txn, entity)
if err != nil {
t.Fatal(err)
}
txn.Commit()
entityFetched, err := is.MemDBEntityByID(entity.ID, true)
if err != nil {
t.Fatal(err)
}
// Modify the fetched entity outside of a transaction
entityFetched.Aliases[0].ID = "invalidaliasid"
entityFetched, err = is.MemDBEntityByID(entity.ID, false)
if err != nil {
t.Fatal(err)
}
if entityFetched.Aliases[0].ID == "invalidaliasid" {
t.Fatal("memdb item is mutable outside of transaction")
}
}
func TestIdentityStore_ContextCancel(t *testing.T) {
var err error
var resp *logical.Response
ctx, cancelFunc := context.WithCancel(namespace.RootContext(nil))
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
entityReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
expected := []string{}
for i := 0; i < 10; i++ {
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
expected = append(expected, resp.Data["id"].(string))
}
listReq := &logical.Request{
Operation: logical.ListOperation,
Path: "entity/id",
}
cancelFunc()
resp, err = is.HandleRequest(ctx, listReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Warnings == nil || len(resp.Warnings) == 0 {
t.Fatalf("expected warning for cancelled context. resp:%#v", resp)
}
}
func TestIdentityStore_ListEntities(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
entityReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
expected := []string{}
for i := 0; i < 10; i++ {
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
expected = append(expected, resp.Data["id"].(string))
}
listReq := &logical.Request{
Operation: logical.ListOperation,
Path: "entity/id",
}
resp, err = is.HandleRequest(ctx, listReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
actual := resp.Data["keys"].([]string)
// Sort the operands for DeepEqual to work
sort.Strings(actual)
sort.Strings(expected)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("bad: listed entity IDs; expected: %#v\n actual: %#v\n", expected, actual)
}
}
func TestIdentityStore_LoadingEntities(t *testing.T) {
var resp *logical.Response
// Add github credential factory to core config
err := AddTestCredentialBackend("github", credGithub.Factory)
if err != nil {
t.Fatalf("err: %s", err)
}
c := TestCore(t)
unsealKeys, token := TestCoreInit(t, c)
for _, key := range unsealKeys {
if _, err := TestCoreUnseal(c, TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
}
if c.Sealed() {
t.Fatal("should not be sealed")
}
meGH := &MountEntry{
Table: credentialTableType,
Path: "github/",
Type: "github",
Description: "github auth",
namespace: namespace.RootNamespace,
}
// Mount UUID for github auth
meGHUUID, err := uuid.GenerateUUID()
if err != nil {
t.Fatal(err)
}
meGH.UUID = meGHUUID
// Mount accessor for github auth
githubAccessor, err := c.generateMountAccessor("github")
if err != nil {
panic(fmt.Sprintf("could not generate github accessor: %v", err))
}
meGH.Accessor = githubAccessor
// Storage view for github auth
ghView := NewBarrierView(c.barrier, credentialBarrierPrefix+meGH.UUID+"/")
// Sysview for github auth
ghSysview := c.mountEntrySysView(meGH)
// Create new github auth credential backend
ghAuth, _, err := c.newCredentialBackend(context.Background(), meGH, ghSysview, ghView)
if err != nil {
t.Fatal(err)
}
// Mount github auth
err = c.router.Mount(ghAuth, "auth/github", meGH, ghView)
if err != nil {
t.Fatal(err)
}
// Identity store will be mounted by now, just fetch it from router
identitystore := c.router.MatchingBackend(namespace.RootContext(nil), "identity/")
if identitystore == nil {
t.Fatalf("failed to fetch identity store from router")
}
is := identitystore.(*IdentityStore)
registerData := map[string]interface{}{
"name": "testentityname",
"metadata": []string{"someusefulkey=someusefulvalue"},
"policies": []string{"testpolicy1", "testpolicy2"},
}
registerReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: registerData,
}
ctx := namespace.RootContext(nil)
// Register the entity
resp, err = is.HandleRequest(ctx, registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityID := resp.Data["id"].(string)
readReq := &logical.Request{
Path: "entity/id/" + entityID,
Operation: logical.ReadOperation,
}
// Ensure that entity is created
resp, err = is.HandleRequest(ctx, readReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["id"] != entityID {
t.Fatalf("failed to read the created entity")
}
// Perform a seal/unseal cycle
err = c.Seal(token)
if err != nil {
t.Fatalf("failed to seal core: %v", err)
}
if !c.Sealed() {
t.Fatal("should be sealed")
}
for _, key := range unsealKeys {
if _, err := TestCoreUnseal(c, TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
}
if c.Sealed() {
t.Fatal("should not be sealed")
}
// Check if the entity is restored
resp, err = is.HandleRequest(ctx, readReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["id"] != entityID {
t.Fatalf("failed to read the created entity after a seal/unseal cycle")
}
}
func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
var err error
ctx := namespace.RootContext(nil)
is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
validateMountResp := is.router.ValidateMountByAccessor(githubAccessor)
if validateMountResp == nil {
t.Fatal("failed to validate github auth mount")
}
alias1 := &identity.Alias{
CanonicalID: "testentityid",
ID: "testaliasid",
MountAccessor: githubAccessor,
MountType: validateMountResp.MountType,
Name: "testaliasname",
Metadata: map[string]string{
"testkey1": "testmetadatavalue1",
"testkey2": "testmetadatavalue2",
},
}
alias2 := &identity.Alias{
CanonicalID: "testentityid",
ID: "testaliasid2",
MountAccessor: validateMountResp.MountAccessor,
MountType: validateMountResp.MountType,
Name: "testaliasname2",
Metadata: map[string]string{
"testkey2": "testmetadatavalue2",
"testkey3": "testmetadatavalue3",
},
}
entity := &identity.Entity{
ID: "testentityid",
Name: "testentityname",
Metadata: map[string]string{
"someusefulkey": "someusefulvalue",
},
Aliases: []*identity.Alias{
alias1,
alias2,
},
}
entity.BucketKey = is.entityPacker.BucketKey(entity.ID)
txn := is.db.Txn(true)
defer txn.Abort()
err = is.MemDBUpsertEntityInTxn(txn, entity)
if err != nil {
t.Fatal(err)
}
txn.Commit()
// Fetch the entity using its ID
entityFetched, err := is.MemDBEntityByID(entity.ID, false)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(entity, entityFetched) {
t.Fatalf("bad: mismatched entities; expected: %#v\n actual: %#v\n", entity, entityFetched)
}
// Fetch the entity using its name
entityFetched, err = is.MemDBEntityByName(namespace.RootContext(nil), entity.Name, false)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(entity, entityFetched) {
t.Fatalf("entity mismatched entities; expected: %#v\n actual: %#v\n", entity, entityFetched)
}
txn = is.db.Txn(false)
entitiesFetched, err := is.MemDBEntitiesByBucketKeyInTxn(txn, entity.BucketKey)
if err != nil {
t.Fatal(err)
}
if len(entitiesFetched) != 1 {
t.Fatalf("bad: length of entities; expected: 1, actual: %d", len(entitiesFetched))
}
err = is.MemDBDeleteEntityByID(entity.ID)
if err != nil {
t.Fatal(err)
}
entityFetched, err = is.MemDBEntityByID(entity.ID, false)
if err != nil {
t.Fatal(err)
}
if entityFetched != nil {
t.Fatalf("bad: entity; expected: nil, actual: %#v\n", entityFetched)
}
entityFetched, err = is.MemDBEntityByName(namespace.RootContext(nil), entity.Name, false)
if err != nil {
t.Fatal(err)
}
if entityFetched != nil {
t.Fatalf("bad: entity; expected: nil, actual: %#v\n", entityFetched)
}
}
func TestIdentityStore_EntityCRUD(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
registerData := map[string]interface{}{
"name": "testentityname",
"metadata": []string{"someusefulkey=someusefulvalue"},
"policies": []string{"testpolicy1", "testpolicy1", "testpolicy2", "testpolicy2"},
}
registerReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: registerData,
}
// Register the entity
resp, err = is.HandleRequest(ctx, registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
idRaw, ok := resp.Data["id"]
if !ok {
t.Fatalf("entity id not present in response")
}
id := idRaw.(string)
if id == "" {
t.Fatalf("invalid entity id")
}
readReq := &logical.Request{
Path: "entity/id/" + id,
Operation: logical.ReadOperation,
}
resp, err = is.HandleRequest(ctx, readReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["id"] != id ||
resp.Data["name"] != registerData["name"] ||
!reflect.DeepEqual(resp.Data["policies"], strutil.RemoveDuplicates(registerData["policies"].([]string), false)) {
t.Fatalf("bad: entity response")
}
updateData := map[string]interface{}{
"name": "updatedentityname",
"metadata": []string{"updatedkey=updatedvalue"},
"policies": []string{"updatedpolicy1", "updatedpolicy2"},
}
updateReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity/id/" + id,
Data: updateData,
}
resp, err = is.HandleRequest(ctx, updateReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
resp, err = is.HandleRequest(ctx, readReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["id"] != id ||
resp.Data["name"] != updateData["name"] ||
!reflect.DeepEqual(resp.Data["policies"], updateData["policies"]) {
t.Fatalf("bad: entity response after update; resp: %#v\n updateData: %#v\n", resp.Data, updateData)
}
deleteReq := &logical.Request{
Path: "entity/id/" + id,
Operation: logical.DeleteOperation,
}
resp, err = is.HandleRequest(ctx, deleteReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
resp, err = is.HandleRequest(ctx, readReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response; actual: %#v\n", resp)
}
}
func TestIdentityStore_MergeEntitiesByID(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
is, githubAccessor, upAccessor, _ := testIdentityStoreWithGithubUserpassAuth(ctx, t)
registerData := map[string]interface{}{
"name": "testentityname2",
"metadata": []string{"someusefulkey=someusefulvalue"},
}
registerData2 := map[string]interface{}{
"name": "testentityname",
"metadata": []string{"someusefulkey=someusefulvalue"},
}
aliasRegisterData1 := map[string]interface{}{
"name": "testaliasname1",
"mount_accessor": githubAccessor,
"metadata": []string{"organization=hashicorp", "team=vault"},
}
aliasRegisterData2 := map[string]interface{}{
"name": "testaliasname2",
"mount_accessor": upAccessor,
"metadata": []string{"organization=hashicorp", "team=vault"},
}
registerReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: registerData,
}
// Register the entity
resp, err = is.HandleRequest(ctx, registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityID1 := resp.Data["id"].(string)
// Set entity ID in alias registration data and register alias
aliasRegisterData1["entity_id"] = entityID1
aliasReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "alias",
Data: aliasRegisterData1,
}
// Register the alias
resp, err = is.HandleRequest(ctx, aliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity1, err := is.MemDBEntityByID(entityID1, false)
if err != nil {
t.Fatal(err)
}
if entity1 == nil {
t.Fatalf("failed to create entity: %v", err)
}
if len(entity1.Aliases) != 1 {
t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity1.Aliases))
}
entity1GroupReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group",
Data: map[string]interface{}{
"member_entity_ids": entityID1,
},
}
resp, err = is.HandleRequest(ctx, entity1GroupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity1GroupID := resp.Data["id"].(string)
registerReq.Data = registerData2
// Register another entity
resp, err = is.HandleRequest(ctx, registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityID2 := resp.Data["id"].(string)
aliasRegisterData2["entity_id"] = entityID2
aliasReq.Data = aliasRegisterData2
// Register the alias
resp, err = is.HandleRequest(ctx, aliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity2, err := is.MemDBEntityByID(entityID2, false)
if err != nil {
t.Fatal(err)
}
if entity2 == nil {
t.Fatalf("failed to create entity: %v", err)
}
if len(entity2.Aliases) != 1 {
t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity2.Aliases))
}
entity2GroupReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "group",
Data: map[string]interface{}{
"member_entity_ids": entityID2,
},
}
resp, err = is.HandleRequest(ctx, entity2GroupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity2GroupID := resp.Data["id"].(string)
mergeData := map[string]interface{}{
"to_entity_id": entityID1,
"from_entity_ids": []string{entityID2},
}
mergeReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity/merge",
Data: mergeData,
}
resp, err = is.HandleRequest(ctx, mergeReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "entity/id/" + entityID2,
}
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp != nil {
t.Fatalf("entity should have been deleted")
}
entityReq.Path = "entity/id/" + entityID1
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity1Aliases := resp.Data["aliases"].([]interface{})
if len(entity1Aliases) != 2 {
t.Fatalf("bad: number of aliases in entity; expected: 2, actual: %d", len(entity1Aliases))
}
githubAliases := 0
for _, aliasRaw := range entity1Aliases {
alias := aliasRaw.(map[string]interface{})
aliasLookedUp, err := is.MemDBAliasByID(alias["id"].(string), false, false)
if err != nil {
t.Fatal(err)
}
if aliasLookedUp == nil {
t.Fatalf("index for alias id %q is not updated", alias["id"].(string))
}
if aliasLookedUp.MountAccessor == githubAccessor {
githubAliases += 1
}
}
// Test that only 1 alias for the githubAccessor is present in the merged entity,
// as the github alias on entity2 should've been skipped in the merge
if githubAliases != 1 {
t.Fatalf("Unexcepted number of github aliases in merged entity; expected: 1, actual: %d", githubAliases)
}
entity1Groups := resp.Data["direct_group_ids"].([]string)
if len(entity1Groups) != 2 {
t.Fatalf("bad: number of groups in entity; expected: 2, actual: %d", len(entity1Groups))
}
for _, group := range []string{entity1GroupID, entity2GroupID} {
if !strutil.StrListContains(entity1Groups, group) {
t.Fatalf("group id %q not found in merged entity direct groups %q", group, entity1Groups)
}
groupLookedUp, err := is.MemDBGroupByID(group, false)
if err != nil {
t.Fatal(err)
}
expectedEntityIDs := []string{entity1.ID}
if !strutil.EquivalentSlices(groupLookedUp.MemberEntityIDs, expectedEntityIDs) {
t.Fatalf("group id %q should contain %q but contains %q", group, expectedEntityIDs, groupLookedUp.MemberEntityIDs)
}
}
}
func TestIdentityStore_MergeEntitiesByID_DuplicateFromEntityIDs(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil)
is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
// Register the entity
registerReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: map[string]interface{}{
"name": "testentityname2",
"metadata": []string{"someusefulkey=someusefulvalue"},
"policies": []string{"testPolicy1", "testPolicy1", "testPolicy2"},
},
}
resp, err = is.HandleRequest(ctx, registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityID1 := resp.Data["id"].(string)
entity1, err := is.MemDBEntityByID(entityID1, false)
if err != nil {
t.Fatal(err)
}
if entity1 == nil {
t.Fatalf("failed to create entity: %v", err)
}
// Register another entity
registerReq.Data = map[string]interface{}{
"name": "testentityname",
"metadata": []string{"someusefulkey=someusefulvalue"},
}
resp, err = is.HandleRequest(ctx, registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityID2 := resp.Data["id"].(string)
aliasReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "alias",
Data: map[string]interface{}{
"name": "testaliasname1",
"mount_accessor": githubAccessor,
"metadata": []string{"organization=hashicorp", "team=vault"},
"entity_id": entityID2,
"policies": []string{"testPolicy1", "testPolicy1", "testPolicy2"},
},
}
// Register the alias
resp, err = is.HandleRequest(ctx, aliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity2, err := is.MemDBEntityByID(entityID2, false)
if err != nil {
t.Fatal(err)
}
if entity2 == nil {
t.Fatalf("failed to create entity: %v", err)
}
if len(entity2.Aliases) != 1 {
t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity2.Aliases))
}
mergeReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity/merge",
Data: map[string]interface{}{
"to_entity_id": entityID1,
"from_entity_ids": []string{entityID2, entityID2},
},
}
resp, err = is.HandleRequest(ctx, mergeReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "entity/id/" + entityID2,
}
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp != nil {
t.Fatalf("entity should have been deleted")
}
entityReq.Path = "entity/id/" + entityID1
resp, err = is.HandleRequest(ctx, entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity1Lookup, err := is.MemDBEntityByID(entityID1, false)
if err != nil {
t.Fatal(err)
}
if entity1Lookup == nil {
t.Fatalf("failed to create entity: %v", err)
}
if len(entity1Lookup.Aliases) != 1 {
t.Fatalf("bad: number of aliases in entity; expected: 1, actual: %d", len(entity1Lookup.Aliases))
}
if len(entity1Lookup.Policies) != 2 {
t.Fatalf("invalid number of entity policies; expected: 2, actualL: %d", len(entity1Lookup.Policies))
}
}