| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package vault |
| |
| import ( |
| "context" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/stretchr/testify/require" |
| |
| "github.com/armon/go-metrics" |
| "github.com/go-test/deep" |
| "github.com/golang/protobuf/ptypes" |
| uuid "github.com/hashicorp/go-uuid" |
| credGithub "github.com/hashicorp/vault/builtin/credential/github" |
| credUserpass "github.com/hashicorp/vault/builtin/credential/userpass" |
| "github.com/hashicorp/vault/helper/identity" |
| "github.com/hashicorp/vault/helper/namespace" |
| "github.com/hashicorp/vault/helper/storagepacker" |
| "github.com/hashicorp/vault/sdk/logical" |
| ) |
| |
| func TestIdentityStore_DeleteEntityAlias(t *testing.T) { |
| c, _, _ := TestCoreUnsealed(t) |
| txn := c.identityStore.db.Txn(true) |
| defer txn.Abort() |
| |
| alias := &identity.Alias{ |
| ID: "testAliasID1", |
| CanonicalID: "testEntityID", |
| MountType: "testMountType", |
| MountAccessor: "testMountAccessor", |
| Name: "testAliasName", |
| LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("testEntityID"), |
| } |
| alias2 := &identity.Alias{ |
| ID: "testAliasID2", |
| CanonicalID: "testEntityID", |
| MountType: "testMountType", |
| MountAccessor: "testMountAccessor2", |
| Name: "testAliasName2", |
| LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("testEntityID"), |
| } |
| entity := &identity.Entity{ |
| ID: "testEntityID", |
| Name: "testEntityName", |
| Policies: []string{"foo", "bar"}, |
| Aliases: []*identity.Alias{ |
| alias, |
| alias2, |
| }, |
| NamespaceID: namespace.RootNamespaceID, |
| BucketKey: c.identityStore.entityPacker.BucketKey("testEntityID"), |
| } |
| |
| err := c.identityStore.upsertEntityInTxn(context.Background(), txn, entity, nil, false) |
| require.NoError(t, err) |
| |
| err = c.identityStore.deleteAliasesInEntityInTxn(txn, entity, []*identity.Alias{alias, alias2}) |
| require.NoError(t, err) |
| |
| txn.Commit() |
| |
| alias, err = c.identityStore.MemDBAliasByID("testAliasID1", false, false) |
| require.NoError(t, err) |
| require.Nil(t, alias) |
| |
| alias, err = c.identityStore.MemDBAliasByID("testAliasID2", false, false) |
| require.NoError(t, err) |
| require.Nil(t, alias) |
| |
| entity, err = c.identityStore.MemDBEntityByID("testEntityID", false) |
| require.NoError(t, err) |
| |
| require.Len(t, entity.Aliases, 0) |
| } |
| |
| func TestIdentityStore_UnsealingWhenConflictingAliasNames(t *testing.T) { |
| err := AddTestCredentialBackend("github", credGithub.Factory) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| c, unsealKey, root := TestCoreUnsealed(t) |
| |
| meGH := &MountEntry{ |
| Table: credentialTableType, |
| Path: "github/", |
| Type: "github", |
| Description: "github auth", |
| } |
| |
| err = c.enableCredential(namespace.RootContext(nil), meGH) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| alias := &identity.Alias{ |
| ID: "alias1", |
| CanonicalID: "entity1", |
| MountType: "github", |
| MountAccessor: meGH.Accessor, |
| Name: "githubuser", |
| LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity1"), |
| } |
| entity := &identity.Entity{ |
| ID: "entity1", |
| Name: "name1", |
| Policies: []string{"foo", "bar"}, |
| Aliases: []*identity.Alias{ |
| alias, |
| }, |
| NamespaceID: namespace.RootNamespaceID, |
| BucketKey: c.identityStore.entityPacker.BucketKey("entity1"), |
| } |
| |
| err = c.identityStore.upsertEntity(namespace.RootContext(nil), entity, nil, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| alias2 := &identity.Alias{ |
| ID: "alias2", |
| CanonicalID: "entity2", |
| MountType: "github", |
| MountAccessor: meGH.Accessor, |
| Name: "GITHUBUSER", |
| LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity2"), |
| } |
| entity2 := &identity.Entity{ |
| ID: "entity2", |
| Name: "name2", |
| Policies: []string{"foo", "bar"}, |
| Aliases: []*identity.Alias{ |
| alias2, |
| }, |
| NamespaceID: namespace.RootNamespaceID, |
| BucketKey: c.identityStore.entityPacker.BucketKey("entity2"), |
| } |
| |
| // Persist the second entity directly without the regular flow. This will skip |
| // merging of these enties. |
| entity2Any, err := ptypes.MarshalAny(entity2) |
| if err != nil { |
| t.Fatal(err) |
| } |
| item := &storagepacker.Item{ |
| ID: entity2.ID, |
| Message: entity2Any, |
| } |
| |
| ctx := namespace.RootContext(nil) |
| if err = c.identityStore.entityPacker.PutItem(ctx, item); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Seal and ensure that unseal works |
| if err = c.Seal(root); err != nil { |
| t.Fatal(err) |
| } |
| |
| var unsealed bool |
| for i := 0; i < 3; i++ { |
| unsealed, err = c.Unseal(unsealKey[i]) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| if !unsealed { |
| t.Fatal("still sealed") |
| } |
| } |
| |
| func TestIdentityStore_EntityIDPassthrough(t *testing.T) { |
| // Enable GitHub auth and initialize |
| ctx := namespace.RootContext(nil) |
| is, ghAccessor, core := testIdentityStoreWithGithubAuth(ctx, t) |
| alias := &logical.Alias{ |
| MountType: "github", |
| MountAccessor: ghAccessor, |
| Name: "githubuser", |
| } |
| |
| // Create an entity with GitHub alias |
| entity, _, err := is.CreateOrFetchEntity(ctx, alias) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if entity == nil { |
| t.Fatalf("expected a non-nil entity") |
| } |
| |
| // Create a token with the above created entity set on it |
| ent := &logical.TokenEntry{ |
| ID: "testtokenid", |
| Path: "test", |
| Policies: []string{"root"}, |
| CreationTime: time.Now().Unix(), |
| EntityID: entity.ID, |
| NamespaceID: namespace.RootNamespaceID, |
| } |
| if err := core.tokenStore.create(ctx, ent); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| // Set a request handler to the noop backend which responds with the entity |
| // ID received in the request object |
| requestHandler := func(ctx context.Context, req *logical.Request) (*logical.Response, error) { |
| return &logical.Response{ |
| Data: map[string]interface{}{ |
| "entity_id": req.EntityID, |
| }, |
| }, nil |
| } |
| |
| noop := &NoopBackend{ |
| RequestHandler: requestHandler, |
| } |
| |
| // Mount the noop backend |
| _, barrier, _ := mockBarrier(t) |
| view := NewBarrierView(barrier, "logical/") |
| meUUID, err := uuid.GenerateUUID() |
| if err != nil { |
| t.Fatal(err) |
| } |
| err = core.router.Mount(noop, "test/backend/", &MountEntry{Path: "test/backend/", Type: "noop", UUID: meUUID, Accessor: "noop-accessor", namespace: namespace.RootNamespace}, view) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Make the request with the above created token |
| resp, err := core.HandleRequest(ctx, &logical.Request{ |
| ClientToken: "testtokenid", |
| Operation: logical.ReadOperation, |
| Path: "test/backend/foo", |
| }) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("bad: resp: %#v\n err: %v", resp, err) |
| } |
| |
| // Expected entity ID to be in the response |
| if resp.Data["entity_id"] != entity.ID { |
| t.Fatalf("expected entity ID to be passed through to the backend") |
| } |
| } |
| |
| func TestIdentityStore_CreateOrFetchEntity(t *testing.T) { |
| ctx := namespace.RootContext(nil) |
| is, ghAccessor, upAccessor, _ := testIdentityStoreWithGithubUserpassAuth(ctx, t) |
| |
| alias := &logical.Alias{ |
| MountType: "github", |
| MountAccessor: ghAccessor, |
| Name: "githubuser", |
| Metadata: map[string]string{ |
| "foo": "a", |
| }, |
| } |
| |
| entity, _, err := is.CreateOrFetchEntity(ctx, alias) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if entity == nil { |
| t.Fatalf("expected a non-nil entity") |
| } |
| |
| if len(entity.Aliases) != 1 { |
| t.Fatalf("bad: length of aliases; expected: 1, actual: %d", len(entity.Aliases)) |
| } |
| |
| if entity.Aliases[0].Name != alias.Name { |
| t.Fatalf("bad: alias name; expected: %q, actual: %q", alias.Name, entity.Aliases[0].Name) |
| } |
| |
| entity, _, err = is.CreateOrFetchEntity(ctx, alias) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if entity == nil { |
| t.Fatalf("expected a non-nil entity") |
| } |
| |
| if len(entity.Aliases) != 1 { |
| t.Fatalf("bad: length of aliases; expected: 1, actual: %d", len(entity.Aliases)) |
| } |
| |
| if entity.Aliases[0].Name != alias.Name { |
| t.Fatalf("bad: alias name; expected: %q, actual: %q", alias.Name, entity.Aliases[0].Name) |
| } |
| if diff := deep.Equal(entity.Aliases[0].Metadata, map[string]string{"foo": "a"}); diff != nil { |
| t.Fatal(diff) |
| } |
| |
| // Add a new alias to the entity and verify its existence |
| registerReq := &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "entity-alias", |
| Data: map[string]interface{}{ |
| "name": "githubuser2", |
| "canonical_id": entity.ID, |
| "mount_accessor": upAccessor, |
| }, |
| } |
| |
| resp, err := is.HandleRequest(ctx, registerReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("err:%v resp:%#v", err, resp) |
| } |
| |
| entity, _, err = is.CreateOrFetchEntity(ctx, alias) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if entity == nil { |
| t.Fatalf("expected a non-nil entity") |
| } |
| |
| if len(entity.Aliases) != 2 { |
| t.Fatalf("bad: length of aliases; expected: 2, actual: %d", len(entity.Aliases)) |
| } |
| |
| if entity.Aliases[1].Name != "githubuser2" { |
| t.Fatalf("bad: alias name; expected: %q, actual: %q", alias.Name, "githubuser2") |
| } |
| |
| if diff := deep.Equal(entity.Aliases[1].Metadata, map[string]string(nil)); diff != nil { |
| t.Fatal(diff) |
| } |
| |
| // Change the metadata of an existing alias and verify that |
| // a the change takes effect only for the target alias. |
| alias.Metadata = map[string]string{ |
| "foo": "zzzz", |
| } |
| |
| entity, _, err = is.CreateOrFetchEntity(ctx, alias) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if entity == nil { |
| t.Fatalf("expected a non-nil entity") |
| } |
| |
| if len(entity.Aliases) != 2 { |
| t.Fatalf("bad: length of aliases; expected: 2, actual: %d", len(entity.Aliases)) |
| } |
| |
| if diff := deep.Equal(entity.Aliases[0].Metadata, map[string]string{"foo": "zzzz"}); diff != nil { |
| t.Fatal(diff) |
| } |
| |
| if diff := deep.Equal(entity.Aliases[1].Metadata, map[string]string(nil)); diff != nil { |
| t.Fatal(diff) |
| } |
| } |
| |
| func TestIdentityStore_EntityByAliasFactors(t *testing.T) { |
| var err error |
| var resp *logical.Response |
| |
| ctx := namespace.RootContext(nil) |
| is, ghAccessor, _ := testIdentityStoreWithGithubAuth(ctx, 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(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") |
| } |
| entityID := idRaw.(string) |
| if entityID == "" { |
| t.Fatalf("invalid entity id") |
| } |
| |
| aliasData := map[string]interface{}{ |
| "entity_id": entityID, |
| "name": "alias_name", |
| "mount_accessor": ghAccessor, |
| } |
| aliasReq := &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "alias", |
| Data: aliasData, |
| } |
| |
| resp, err = is.HandleRequest(ctx, aliasReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("err:%v resp:%#v", err, resp) |
| } |
| if resp == nil { |
| t.Fatalf("expected a non-nil response") |
| } |
| |
| entity, err := is.entityByAliasFactors(ghAccessor, "alias_name", false) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if entity == nil { |
| t.Fatalf("expected a non-nil entity") |
| } |
| if entity.ID != entityID { |
| t.Fatalf("bad: entity ID; expected: %q actual: %q", entityID, entity.ID) |
| } |
| } |
| |
| func TestIdentityStore_WrapInfoInheritance(t *testing.T) { |
| var err error |
| var resp *logical.Response |
| |
| ctx := namespace.RootContext(nil) |
| core, is, ts, _ := testCoreWithIdentityTokenGithub(ctx, 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(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") |
| } |
| entityID := idRaw.(string) |
| if entityID == "" { |
| t.Fatalf("invalid entity id") |
| } |
| |
| // Create a token which has EntityID set and has update permissions to |
| // sys/wrapping/wrap |
| te := &logical.TokenEntry{ |
| Path: "test", |
| Policies: []string{"default", responseWrappingPolicyName}, |
| EntityID: entityID, |
| TTL: time.Hour, |
| } |
| testMakeTokenDirectly(t, ts, te) |
| |
| wrapReq := &logical.Request{ |
| Path: "sys/wrapping/wrap", |
| ClientToken: te.ID, |
| Operation: logical.UpdateOperation, |
| Data: map[string]interface{}{ |
| "foo": "bar", |
| }, |
| WrapInfo: &logical.RequestWrapInfo{ |
| TTL: time.Duration(5 * time.Second), |
| }, |
| } |
| |
| resp, err = core.HandleRequest(ctx, wrapReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("bad: resp: %#v, err: %v", resp, err) |
| } |
| |
| if resp.WrapInfo == nil { |
| t.Fatalf("expected a non-nil WrapInfo") |
| } |
| |
| if resp.WrapInfo.WrappedEntityID != entityID { |
| t.Fatalf("bad: WrapInfo in response not having proper entity ID set; expected: %q, actual:%q", entityID, resp.WrapInfo.WrappedEntityID) |
| } |
| } |
| |
| func TestIdentityStore_TokenEntityInheritance(t *testing.T) { |
| c, _, _ := TestCoreUnsealed(t) |
| ts := c.tokenStore |
| |
| // Create a token which has EntityID set |
| te := &logical.TokenEntry{ |
| Path: "test", |
| Policies: []string{"dev", "prod"}, |
| EntityID: "testentityid", |
| TTL: time.Hour, |
| } |
| testMakeTokenDirectly(t, ts, te) |
| |
| // Create a child token; this should inherit the EntityID |
| tokenReq := &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "create", |
| ClientToken: te.ID, |
| } |
| |
| ctx := namespace.RootContext(nil) |
| resp, err := ts.HandleRequest(ctx, tokenReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("bad: resp: %#v err: %v", err, resp) |
| } |
| |
| if resp.Auth.EntityID != te.EntityID { |
| t.Fatalf("bad: entity ID; expected: %v, actual: %v", te.EntityID, resp.Auth.EntityID) |
| } |
| |
| // Create an orphan token; this should not inherit the EntityID |
| tokenReq.Path = "create-orphan" |
| resp, err = ts.HandleRequest(ctx, tokenReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("bad: resp: %#v err: %v", err, resp) |
| } |
| |
| if resp.Auth.EntityID != "" { |
| t.Fatalf("expected entity ID to be not set") |
| } |
| } |
| |
| func TestIdentityStore_MergeConflictingAliases(t *testing.T) { |
| err := AddTestCredentialBackend("github", credGithub.Factory) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| c, _, _ := TestCoreUnsealed(t) |
| |
| meGH := &MountEntry{ |
| Table: credentialTableType, |
| Path: "github/", |
| Type: "github", |
| Description: "github auth", |
| } |
| |
| err = c.enableCredential(namespace.RootContext(nil), meGH) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| alias := &identity.Alias{ |
| ID: "alias1", |
| CanonicalID: "entity1", |
| MountType: "github", |
| MountAccessor: meGH.Accessor, |
| Name: "githubuser", |
| LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity1"), |
| } |
| entity := &identity.Entity{ |
| ID: "entity1", |
| Name: "name1", |
| Policies: []string{"foo", "bar"}, |
| Aliases: []*identity.Alias{ |
| alias, |
| }, |
| NamespaceID: namespace.RootNamespaceID, |
| BucketKey: c.identityStore.entityPacker.BucketKey("entity1"), |
| } |
| err = c.identityStore.upsertEntity(namespace.RootContext(nil), entity, nil, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| alias2 := &identity.Alias{ |
| ID: "alias2", |
| CanonicalID: "entity2", |
| MountType: "github", |
| MountAccessor: meGH.Accessor, |
| Name: "githubuser", |
| LocalBucketKey: c.identityStore.localAliasPacker.BucketKey("entity2"), |
| } |
| entity2 := &identity.Entity{ |
| ID: "entity2", |
| Name: "name2", |
| Policies: []string{"bar", "baz"}, |
| Aliases: []*identity.Alias{ |
| alias2, |
| }, |
| NamespaceID: namespace.RootNamespaceID, |
| BucketKey: c.identityStore.entityPacker.BucketKey("entity2"), |
| } |
| |
| err = c.identityStore.upsertEntity(namespace.RootContext(nil), entity2, nil, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| newEntity, _, err := c.identityStore.CreateOrFetchEntity(namespace.RootContext(nil), &logical.Alias{ |
| MountAccessor: meGH.Accessor, |
| MountType: "github", |
| Name: "githubuser", |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if newEntity == nil { |
| t.Fatal("nil new entity") |
| } |
| |
| entityToUse := "entity1" |
| if newEntity.ID == "entity1" { |
| entityToUse = "entity2" |
| } |
| if len(newEntity.MergedEntityIDs) != 1 || newEntity.MergedEntityIDs[0] != entityToUse { |
| t.Fatalf("bad merged entity ids: %v", newEntity.MergedEntityIDs) |
| } |
| if diff := deep.Equal(newEntity.Policies, []string{"bar", "baz", "foo"}); diff != nil { |
| t.Fatal(diff) |
| } |
| |
| newEntity, err = c.identityStore.MemDBEntityByID(entityToUse, false) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if newEntity != nil { |
| t.Fatal("got a non-nil entity") |
| } |
| } |
| |
| func testCoreWithIdentityTokenGithub(ctx context.Context, t *testing.T) (*Core, *IdentityStore, *TokenStore, string) { |
| is, ghAccessor, core := testIdentityStoreWithGithubAuth(ctx, t) |
| return core, is, core.tokenStore, ghAccessor |
| } |
| |
| func testCoreWithIdentityTokenGithubRoot(ctx context.Context, t *testing.T) (*Core, *IdentityStore, *TokenStore, string, string) { |
| is, ghAccessor, core, root := testIdentityStoreWithGithubAuthRoot(ctx, t) |
| return core, is, core.tokenStore, ghAccessor, root |
| } |
| |
| func testIdentityStoreWithGithubAuth(ctx context.Context, t *testing.T) (*IdentityStore, string, *Core) { |
| is, ghA, c, _ := testIdentityStoreWithGithubAuthRoot(ctx, t) |
| return is, ghA, c |
| } |
| |
| // testIdentityStoreWithGithubAuthRoot returns an instance of identity store |
| // which is mounted by default. This function also enables the github auth |
| // backend to assist with testing aliases and entities that require an valid |
| // mount accessor of an auth backend. |
| func testIdentityStoreWithGithubAuthRoot(ctx context.Context, t *testing.T) (*IdentityStore, string, *Core, string) { |
| // Add github credential factory to core config |
| err := AddTestCredentialBackend("github", credGithub.Factory) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| c, _, root := TestCoreUnsealed(t) |
| |
| meGH := &MountEntry{ |
| Table: credentialTableType, |
| Path: "github/", |
| Type: "github", |
| Description: "github auth", |
| } |
| |
| err = c.enableCredential(ctx, meGH) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| return c.identityStore, meGH.Accessor, c, root |
| } |
| |
| func testIdentityStoreWithGithubUserpassAuth(ctx context.Context, t *testing.T) (*IdentityStore, string, string, *Core) { |
| // Setup 2 auth backends, github and userpass |
| err := AddTestCredentialBackend("github", credGithub.Factory) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| err = AddTestCredentialBackend("userpass", credUserpass.Factory) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| c, _, _ := TestCoreUnsealed(t) |
| |
| githubMe := &MountEntry{ |
| Table: credentialTableType, |
| Path: "github/", |
| Type: "github", |
| Description: "github auth", |
| } |
| |
| err = c.enableCredential(ctx, githubMe) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| userpassMe := &MountEntry{ |
| Table: credentialTableType, |
| Path: "userpass/", |
| Type: "userpass", |
| Description: "userpass", |
| } |
| |
| err = c.enableCredential(ctx, userpassMe) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| return c.identityStore, githubMe.Accessor, userpassMe.Accessor, c |
| } |
| |
| func TestIdentityStore_MetadataKeyRegex(t *testing.T) { |
| key := "validVALID012_-=+/" |
| |
| if !metaKeyFormatRegEx(key) { |
| t.Fatal("failed to accept valid metadata key") |
| } |
| |
| key = "a:b" |
| if metaKeyFormatRegEx(key) { |
| t.Fatal("accepted invalid metadata key") |
| } |
| } |
| |
| func expectSingleCount(t *testing.T, sink *metrics.InmemSink, keyPrefix string) { |
| t.Helper() |
| |
| intervals := sink.Data() |
| // Test crossed an interval boundary, don't try to deal with it. |
| if len(intervals) > 1 { |
| t.Skip("Detected interval crossing.") |
| } |
| |
| var counter *metrics.SampledValue = nil |
| |
| for _, c := range intervals[0].Counters { |
| if strings.HasPrefix(c.Name, keyPrefix) { |
| counter = &c |
| break |
| } |
| } |
| if counter == nil { |
| t.Fatalf("No %q counter found.", keyPrefix) |
| } |
| |
| if counter.Count != 1 { |
| t.Errorf("Counter number of samples %v is not 1.", counter.Count) |
| } |
| |
| if counter.Sum != 1.0 { |
| t.Errorf("Counter sum %v is not 1.", counter.Sum) |
| } |
| } |
| |
| func TestIdentityStore_NewEntityCounter(t *testing.T) { |
| // Add github credential factory to core config |
| err := AddTestCredentialBackend("github", credGithub.Factory) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| c, _, _, sink := TestCoreUnsealedWithMetrics(t) |
| |
| meGH := &MountEntry{ |
| Table: credentialTableType, |
| Path: "github/", |
| Type: "github", |
| Description: "github auth", |
| } |
| |
| ctx := namespace.RootContext(nil) |
| err = c.enableCredential(ctx, meGH) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| is := c.identityStore |
| ghAccessor := meGH.Accessor |
| |
| alias := &logical.Alias{ |
| MountType: "github", |
| MountAccessor: ghAccessor, |
| Name: "githubuser", |
| Metadata: map[string]string{ |
| "foo": "a", |
| }, |
| } |
| |
| _, _, err = is.CreateOrFetchEntity(ctx, alias) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| expectSingleCount(t, sink, "identity.entity.creation") |
| |
| _, _, err = is.CreateOrFetchEntity(ctx, alias) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| expectSingleCount(t, sink, "identity.entity.creation") |
| } |
| |
| func TestIdentityStore_UpdateAliasMetadataPerAccessor(t *testing.T) { |
| entity := &identity.Entity{ |
| ID: "testEntityID", |
| Name: "testEntityName", |
| Policies: []string{"foo", "bar"}, |
| Aliases: []*identity.Alias{ |
| { |
| ID: "testAliasID1", |
| CanonicalID: "testEntityID", |
| MountType: "testMountType", |
| MountAccessor: "testMountAccessor", |
| Name: "sameAliasName", |
| }, |
| { |
| ID: "testAliasID2", |
| CanonicalID: "testEntityID", |
| MountType: "testMountType", |
| MountAccessor: "testMountAccessor2", |
| Name: "sameAliasName", |
| }, |
| }, |
| NamespaceID: namespace.RootNamespaceID, |
| } |
| |
| login := &logical.Alias{ |
| MountType: "testMountType", |
| MountAccessor: "testMountAccessor", |
| Name: "sameAliasName", |
| ID: "testAliasID", |
| Metadata: map[string]string{"foo": "bar"}, |
| } |
| |
| if i := changedAliasIndex(entity, login); i != 0 { |
| t.Fatalf("wrong alias index changed. Expected 0, got %d", i) |
| } |
| |
| login2 := &logical.Alias{ |
| MountType: "testMountType", |
| MountAccessor: "testMountAccessor2", |
| Name: "sameAliasName", |
| ID: "testAliasID2", |
| Metadata: map[string]string{"bar": "foo"}, |
| } |
| |
| if i := changedAliasIndex(entity, login2); i != 1 { |
| t.Fatalf("wrong alias index changed. Expected 1, got %d", i) |
| } |
| } |
| |
| // TestIdentityStore_DeleteCaseSensitivityKey tests that |
| // casesensitivity key gets removed from storage if it exists upon |
| // initializing identity store. |
| func TestIdentityStore_DeleteCaseSensitivityKey(t *testing.T) { |
| c, unsealKey, root := TestCoreUnsealed(t) |
| ctx := context.Background() |
| |
| // add caseSensitivityKey to storage |
| entry, err := logical.StorageEntryJSON(caseSensitivityKey, &casesensitivity{ |
| DisableLowerCasedNames: true, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| err = c.identityStore.view.Put(ctx, entry) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // check if the value is stored in storage |
| storageEntry, err := c.identityStore.view.Get(ctx, caseSensitivityKey) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if storageEntry == nil { |
| t.Fatalf("bad: expected a non-nil entry for casesensitivity key") |
| } |
| |
| // Seal and unseal to trigger identityStore initialize |
| if err = c.Seal(root); err != nil { |
| t.Fatal(err) |
| } |
| |
| var unsealed bool |
| for i := 0; i < len(unsealKey); i++ { |
| unsealed, err = c.Unseal(unsealKey[i]) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| if !unsealed { |
| t.Fatal("still sealed") |
| } |
| |
| // check if caseSensitivityKey exists after initialize |
| storageEntry, err = c.identityStore.view.Get(ctx, caseSensitivityKey) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if storageEntry != nil { |
| t.Fatalf("bad: expected no entry for casesensitivity key") |
| } |
| } |