open-vault/vault/identity_store_entities_test.go
Jeff Mitchell 9687ccc8fa Tackle #4929 a different way (#4932)
* Tackle #4929 a different way

This turns c.sealed into an atomic, which allows us to call sealInternal
without a lock. By doing so we can better control lock grabbing when a
condition causing the standby loop to get out of active happens. This
encapsulates that logic into two distinct pieces (although they could
be combined into one), and makes lock guarding more understandable.

* Re-add context canceling to the non-HA version of sealInternal

* Return explicitly after stopCh triggered
2018-07-24 13:57:25 -07:00

846 lines
22 KiB
Go

package vault
import (
"context"
"fmt"
"reflect"
"sort"
"testing"
uuid "github.com/hashicorp/go-uuid"
credGithub "github.com/hashicorp/vault/builtin/credential/github"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/logical"
)
func TestIdentityStore_EntityReadGroupIDs(t *testing.T) {
var err error
var resp *logical.Response
i, _, _ := testIdentityStoreWithGithubAuth(t)
entityReq := &logical.Request{
Path: "entity",
Operation: logical.UpdateOperation,
}
resp, err = i.HandleRequest(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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
is, _, _ := testIdentityStoreWithGithubAuth(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(context.Background(), 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(context.Background(), 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(context.Background(), 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_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
is, githubAccessor, _ := testIdentityStoreWithGithubAuth(t)
validateMountResp := is.core.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.BucketKeyHash = is.entityPacker.BucketKeyHashByItemID(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_ListEntities(t *testing.T) {
var err error
var resp *logical.Response
is, _, _ := testIdentityStoreWithGithubAuth(t)
entityReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
}
expected := []string{}
for i := 0; i < 10; i++ {
resp, err = is.HandleRequest(context.Background(), 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(context.Background(), 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",
}
// 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("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,
}
// Register the entity
resp, err = is.HandleRequest(context.Background(), 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(context.Background(), 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(context.Background(), 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
is, githubAccessor, _ := testIdentityStoreWithGithubAuth(t)
validateMountResp := is.core.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.BucketKeyHash = is.entityPacker.BucketKeyHashByItemID(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(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.MemDBEntitiesByBucketEntryKeyHashInTxn(txn, entity.BucketKeyHash)
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(entity.Name, false)
if err != nil {
t.Fatal(err)
}
if entityFetched != nil {
t.Fatalf("bad: entity; expected: nil, actual: %#v\n", entityFetched)
}
}
// This test is required because MemDB does not take care of ensuring
// uniqueness of indexes that are marked unique. It is the job of the higher
// level abstraction, the identity store in this case.
func TestIdentityStore_EntitySameEntityNames(t *testing.T) {
var err error
var resp *logical.Response
is, _, _ := testIdentityStoreWithGithubAuth(t)
registerData := map[string]interface{}{
"name": "testentityname",
}
registerReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: registerData,
}
// Register an entity
resp, err = is.HandleRequest(context.Background(), registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Register another entity with same name
resp, err = is.HandleRequest(context.Background(), registerReq)
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected an error due to entity name not being unique")
}
}
func TestIdentityStore_EntityCRUD(t *testing.T) {
var err error
var resp *logical.Response
is, _, _ := testIdentityStoreWithGithubAuth(t)
registerData := map[string]interface{}{
"name": "testentityname",
"metadata": []string{"someusefulkey=someusefulvalue"},
"policies": []string{"testpolicy1", "testpolicy2"},
}
registerReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: registerData,
}
// Register the entity
resp, err = is.HandleRequest(context.Background(), 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(context.Background(), 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"], registerData["policies"]) {
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(context.Background(), updateReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
resp, err = is.HandleRequest(context.Background(), 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(context.Background(), deleteReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
resp, err = is.HandleRequest(context.Background(), 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
is, githubAccessor, _ := testIdentityStoreWithGithubAuth(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": githubAccessor,
"metadata": []string{"organization=hashicorp", "team=vault"},
}
aliasRegisterData3 := map[string]interface{}{
"name": "testaliasname3",
"mount_accessor": githubAccessor,
"metadata": []string{"organization=hashicorp", "team=vault"},
}
aliasRegisterData4 := map[string]interface{}{
"name": "testaliasname4",
"mount_accessor": githubAccessor,
"metadata": []string{"organization=hashicorp", "team=vault"},
}
registerReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "entity",
Data: registerData,
}
// Register the entity
resp, err = is.HandleRequest(context.Background(), 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
aliasRegisterData2["entity_id"] = entityID1
aliasReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "alias",
Data: aliasRegisterData1,
}
// Register the alias
resp, err = is.HandleRequest(context.Background(), aliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Register the alias
aliasReq.Data = aliasRegisterData2
resp, err = is.HandleRequest(context.Background(), 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) != 2 {
t.Fatalf("bad: number of aliases in entity; expected: 2, actual: %d", len(entity1.Aliases))
}
registerReq.Data = registerData2
// Register another entity
resp, err = is.HandleRequest(context.Background(), registerReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entityID2 := resp.Data["id"].(string)
// Set entity ID in alias registration data and register alias
aliasRegisterData3["entity_id"] = entityID2
aliasRegisterData4["entity_id"] = entityID2
aliasReq = &logical.Request{
Operation: logical.UpdateOperation,
Path: "alias",
Data: aliasRegisterData3,
}
// Register the alias
resp, err = is.HandleRequest(context.Background(), aliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Register the alias
aliasReq.Data = aliasRegisterData4
resp, err = is.HandleRequest(context.Background(), 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) != 2 {
t.Fatalf("bad: number of aliases in entity; expected: 2, actual: %d", len(entity2.Aliases))
}
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(context.Background(), 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(context.Background(), 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(context.Background(), entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity2Aliases := resp.Data["aliases"].([]interface{})
if len(entity2Aliases) != 4 {
t.Fatalf("bad: number of aliases in entity; expected: 4, actual: %d", len(entity2Aliases))
}
for _, aliasRaw := range entity2Aliases {
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))
}
}
}