916 lines
23 KiB
Go
916 lines
23 KiB
Go
// 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")
|
|
}
|
|
}
|