Case insensitive identity names (#5404)
* case insensitive identity names * TestIdentityStore_GroupHierarchyCases * address review feedback * Use errwrap.Contains instead of errwrap.ContainsType * Warn about duplicate names all the time to help fix them * Address review feedback
This commit is contained in:
parent
b382517982
commit
c677cd0790
|
@ -31,23 +31,31 @@ func (c *Core) IdentityStore() *IdentityStore {
|
|||
return c.identityStore
|
||||
}
|
||||
|
||||
// NewIdentityStore creates a new identity store
|
||||
func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendConfig, logger log.Logger) (*IdentityStore, error) {
|
||||
func (i *IdentityStore) resetDB(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Create a new in-memory database for the identity store
|
||||
db, err := memdb.NewMemDB(identityStoreSchema())
|
||||
i.db, err = memdb.NewMemDB(identityStoreSchema(!i.disableLowerCasedNames))
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed to create memdb for identity store: {{err}}", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendConfig, logger log.Logger) (*IdentityStore, error) {
|
||||
iStore := &IdentityStore{
|
||||
view: config.StorageView,
|
||||
db: db,
|
||||
logger: logger,
|
||||
core: core,
|
||||
}
|
||||
|
||||
// Create a memdb instance, which by default, operates on lower cased
|
||||
// identity names
|
||||
err := iStore.resetDB(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entitiesPackerLogger := iStore.logger.Named("storagepacker").Named("entities")
|
||||
core.AddLogger(entitiesPackerLogger)
|
||||
groupsPackerLogger := iStore.logger.Named("storagepacker").Named("groups")
|
||||
|
|
|
@ -181,10 +181,6 @@ func (i *IdentityStore) handleAliasUpdateCommon() framework.OperationFunc {
|
|||
if entity == nil {
|
||||
return nil, fmt.Errorf("existing alias is not associated with an entity")
|
||||
}
|
||||
if canonicalID == "" || entity.ID == canonicalID {
|
||||
// Nothing to do
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
resp := &logical.Response{}
|
||||
|
@ -255,6 +251,12 @@ func (i *IdentityStore) handleAliasUpdateCommon() framework.OperationFunc {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
for index, item := range entity.Aliases {
|
||||
if item.ID == alias.ID {
|
||||
entity.Aliases[index] = alias
|
||||
}
|
||||
}
|
||||
|
||||
// Index entity and its aliases in MemDB and persist entity along with
|
||||
// aliases in storage. If the alias is being transferred over from
|
||||
// one entity to another, previous entity needs to get refreshed in MemDB
|
||||
|
|
|
@ -2,6 +2,7 @@ package vault
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/identity"
|
||||
|
@ -9,6 +10,89 @@ import (
|
|||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestIdentityStore_CaseInsensitiveEntityAliasName(t *testing.T) {
|
||||
ctx := namespace.RootContext(nil)
|
||||
i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
|
||||
|
||||
// Create an entity
|
||||
resp, err := i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "entity",
|
||||
Operation: logical.UpdateOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
entityID := resp.Data["id"].(string)
|
||||
|
||||
testAliasName := "testAliasName"
|
||||
// Create a case sensitive alias name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "entity-alias",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"mount_accessor": accessor,
|
||||
"canonical_id": entityID,
|
||||
"name": testAliasName,
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
aliasID := resp.Data["id"].(string)
|
||||
|
||||
// Ensure that reading the alias returns case sensitive alias name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "entity-alias/id/" + aliasID,
|
||||
Operation: logical.ReadOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
aliasName := resp.Data["name"].(string)
|
||||
if aliasName != testAliasName {
|
||||
t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
|
||||
}
|
||||
|
||||
// Overwrite the alias using lower cased alias name. This shouldn't error.
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "entity-alias/id/" + aliasID,
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"mount_accessor": accessor,
|
||||
"canonical_id": entityID,
|
||||
"name": strings.ToLower(testAliasName),
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
// Ensure that reading the alias returns lower cased alias name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "entity-alias/id/" + aliasID,
|
||||
Operation: logical.ReadOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
aliasName = resp.Data["name"].(string)
|
||||
if aliasName != strings.ToLower(testAliasName) {
|
||||
t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
|
||||
}
|
||||
|
||||
// Ensure that there is one entity alias
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "entity-alias/id",
|
||||
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 entity aliases; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
|
||||
}
|
||||
}
|
||||
|
||||
// This test is required because MemDB does not take care of ensuring
|
||||
// uniqueness of indexes that are marked unique.
|
||||
func TestIdentityStore_AliasSameAliasNames(t *testing.T) {
|
||||
|
|
|
@ -467,7 +467,7 @@ func (i *IdentityStore) pathEntityNameDelete() framework.OperationFunc {
|
|||
defer txn.Abort()
|
||||
|
||||
// Fetch the entity using its name
|
||||
entity, err := i.MemDBEntityByNameInTxn(txn, ctx, entityName, true)
|
||||
entity, err := i.MemDBEntityByNameInTxn(ctx, txn, entityName, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
|
@ -14,6 +15,77 @@ import (
|
|||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -270,8 +342,8 @@ func TestIdentityStore_EntityCreateUpdate(t *testing.T) {
|
|||
|
||||
func TestIdentityStore_CloneImmutability(t *testing.T) {
|
||||
alias := &identity.Alias{
|
||||
ID: "testaliasid",
|
||||
Name: "testaliasname",
|
||||
ID: "testaliasid",
|
||||
Name: "testaliasname",
|
||||
MergedFromCanonicalIDs: []string{"entityid1"},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
|
||||
|
@ -10,6 +11,81 @@ import (
|
|||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestIdentityStore_CaseInsensitiveGroupAliasName(t *testing.T) {
|
||||
ctx := namespace.RootContext(nil)
|
||||
i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
|
||||
|
||||
// Create a group
|
||||
resp, err := i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"type": "external",
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
groupID := resp.Data["id"].(string)
|
||||
|
||||
testAliasName := "testAliasName"
|
||||
|
||||
// Create a case sensitive alias name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group-alias",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"mount_accessor": accessor,
|
||||
"canonical_id": groupID,
|
||||
"name": testAliasName,
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
aliasID := resp.Data["id"].(string)
|
||||
|
||||
// Ensure that reading the alias returns case sensitive alias name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group-alias/id/" + aliasID,
|
||||
Operation: logical.ReadOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
aliasName := resp.Data["name"].(string)
|
||||
if aliasName != testAliasName {
|
||||
t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
|
||||
}
|
||||
|
||||
// Overwrite the alias using lower cased alias name. This shouldn't error.
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group-alias/id/" + aliasID,
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"mount_accessor": accessor,
|
||||
"canonical_id": groupID,
|
||||
"name": strings.ToLower(testAliasName),
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
// Ensure that reading the alias returns lower cased alias name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group-alias/id/" + aliasID,
|
||||
Operation: logical.ReadOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
aliasName = resp.Data["name"].(string)
|
||||
if aliasName != strings.ToLower(testAliasName) {
|
||||
t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentityStore_EnsureNoDanglingGroupAlias(t *testing.T) {
|
||||
err := AddTestCredentialBackend("userpass", credUserpass.Factory)
|
||||
if err != nil {
|
||||
|
|
|
@ -3,6 +3,7 @@ package vault
|
|||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
|
@ -80,6 +81,77 @@ func TestIdentityStore_MemberGroupIDDelete(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIdentityStore_CaseInsensitiveGroupName(t *testing.T) {
|
||||
ctx := namespace.RootContext(nil)
|
||||
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
|
||||
|
||||
testGroupName := "testGroupName"
|
||||
|
||||
// Create an group with case sensitive name
|
||||
resp, err := i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"name": testGroupName,
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
groupID := resp.Data["id"].(string)
|
||||
|
||||
// Lookup the group by ID and check that name returned is case sensitive
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group/id/" + groupID,
|
||||
Operation: logical.ReadOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
|
||||
}
|
||||
groupName := resp.Data["name"].(string)
|
||||
if groupName != testGroupName {
|
||||
t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
|
||||
}
|
||||
|
||||
// Lookup the group by case sensitive name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group/name/" + testGroupName,
|
||||
Operation: logical.ReadOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
groupName = resp.Data["name"].(string)
|
||||
if groupName != testGroupName {
|
||||
t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
|
||||
}
|
||||
|
||||
// Lookup the group by case insensitive name
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group/name/" + strings.ToLower(testGroupName),
|
||||
Operation: logical.ReadOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
groupName = resp.Data["name"].(string)
|
||||
if groupName != testGroupName {
|
||||
t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
|
||||
}
|
||||
|
||||
// Ensure that there is only one group
|
||||
resp, err = i.HandleRequest(ctx, &logical.Request{
|
||||
Path: "group/name",
|
||||
Operation: logical.ListOperation,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if len(resp.Data["keys"].([]string)) != 1 {
|
||||
t.Fatalf("bad length of groups; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentityStore_GroupByName(t *testing.T) {
|
||||
ctx := namespace.RootContext(nil)
|
||||
i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
|
||||
|
|
|
@ -13,12 +13,12 @@ const (
|
|||
groupAliasesTable = "group_aliases"
|
||||
)
|
||||
|
||||
func identityStoreSchema() *memdb.DBSchema {
|
||||
func identityStoreSchema(lowerCaseName bool) *memdb.DBSchema {
|
||||
iStoreSchema := &memdb.DBSchema{
|
||||
Tables: make(map[string]*memdb.TableSchema),
|
||||
}
|
||||
|
||||
schemas := []func() *memdb.TableSchema{
|
||||
schemas := []func(bool) *memdb.TableSchema{
|
||||
entitiesTableSchema,
|
||||
aliasesTableSchema,
|
||||
groupsTableSchema,
|
||||
|
@ -26,7 +26,7 @@ func identityStoreSchema() *memdb.DBSchema {
|
|||
}
|
||||
|
||||
for _, schemaFunc := range schemas {
|
||||
schema := schemaFunc()
|
||||
schema := schemaFunc(lowerCaseName)
|
||||
if _, ok := iStoreSchema.Tables[schema.Name]; ok {
|
||||
panic(fmt.Sprintf("duplicate table name: %s", schema.Name))
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func identityStoreSchema() *memdb.DBSchema {
|
|||
return iStoreSchema
|
||||
}
|
||||
|
||||
func aliasesTableSchema() *memdb.TableSchema {
|
||||
func aliasesTableSchema(lowerCaseName bool) *memdb.TableSchema {
|
||||
return &memdb.TableSchema{
|
||||
Name: entityAliasesTable,
|
||||
Indexes: map[string]*memdb.IndexSchema{
|
||||
|
@ -56,7 +56,8 @@ func aliasesTableSchema() *memdb.TableSchema {
|
|||
Field: "MountAccessor",
|
||||
},
|
||||
&memdb.StringFieldIndex{
|
||||
Field: "Name",
|
||||
Field: "Name",
|
||||
Lowercase: lowerCaseName,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -71,7 +72,7 @@ func aliasesTableSchema() *memdb.TableSchema {
|
|||
}
|
||||
}
|
||||
|
||||
func entitiesTableSchema() *memdb.TableSchema {
|
||||
func entitiesTableSchema(lowerCaseName bool) *memdb.TableSchema {
|
||||
return &memdb.TableSchema{
|
||||
Name: entitiesTable,
|
||||
Indexes: map[string]*memdb.IndexSchema{
|
||||
|
@ -91,7 +92,8 @@ func entitiesTableSchema() *memdb.TableSchema {
|
|||
Field: "NamespaceID",
|
||||
},
|
||||
&memdb.StringFieldIndex{
|
||||
Field: "Name",
|
||||
Field: "Name",
|
||||
Lowercase: lowerCaseName,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -120,7 +122,7 @@ func entitiesTableSchema() *memdb.TableSchema {
|
|||
}
|
||||
}
|
||||
|
||||
func groupsTableSchema() *memdb.TableSchema {
|
||||
func groupsTableSchema(lowerCaseName bool) *memdb.TableSchema {
|
||||
return &memdb.TableSchema{
|
||||
Name: groupsTable,
|
||||
Indexes: map[string]*memdb.IndexSchema{
|
||||
|
@ -140,7 +142,8 @@ func groupsTableSchema() *memdb.TableSchema {
|
|||
Field: "NamespaceID",
|
||||
},
|
||||
&memdb.StringFieldIndex{
|
||||
Field: "Name",
|
||||
Field: "Name",
|
||||
Lowercase: lowerCaseName,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -175,7 +178,7 @@ func groupsTableSchema() *memdb.TableSchema {
|
|||
}
|
||||
}
|
||||
|
||||
func groupAliasesTableSchema() *memdb.TableSchema {
|
||||
func groupAliasesTableSchema(lowerCaseName bool) *memdb.TableSchema {
|
||||
return &memdb.TableSchema{
|
||||
Name: groupAliasesTable,
|
||||
Indexes: map[string]*memdb.IndexSchema{
|
||||
|
@ -195,7 +198,8 @@ func groupAliasesTableSchema() *memdb.TableSchema {
|
|||
Field: "MountAccessor",
|
||||
},
|
||||
&memdb.StringFieldIndex{
|
||||
Field: "Name",
|
||||
Field: "Name",
|
||||
Lowercase: lowerCaseName,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -70,6 +70,10 @@ type IdentityStore struct {
|
|||
|
||||
// core is the pointer to Vault's core
|
||||
core *Core
|
||||
|
||||
// disableLowerCaseNames indicates whether or not identity artifacts are
|
||||
// operated case insensitively
|
||||
disableLowerCasedNames bool
|
||||
}
|
||||
|
||||
type groupDiff struct {
|
||||
|
|
|
@ -2,6 +2,7 @@ package vault
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -19,24 +20,57 @@ import (
|
|||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
var (
|
||||
errDuplicateIdentityName = errors.New("duplicate identity name")
|
||||
)
|
||||
|
||||
func (c *Core) loadIdentityStoreArtifacts(ctx context.Context) error {
|
||||
var err error
|
||||
if c.identityStore == nil {
|
||||
c.logger.Warn("identity store is not setup, skipping loading")
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.identityStore.loadEntities(ctx)
|
||||
loadFunc := func(context.Context) error {
|
||||
err := c.identityStore.loadEntities(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.identityStore.loadGroups(ctx)
|
||||
}
|
||||
|
||||
// Load everything when memdb is set to operate on lower cased names
|
||||
err := loadFunc(ctx)
|
||||
switch {
|
||||
case err == nil:
|
||||
// If it succeeds, all is well
|
||||
return nil
|
||||
case err != nil && !errwrap.Contains(err, errDuplicateIdentityName.Error()):
|
||||
return err
|
||||
}
|
||||
|
||||
c.identityStore.logger.Warn("enabling case sensitive identity names")
|
||||
|
||||
// Set identity store to operate on case sensitive identity names
|
||||
c.identityStore.disableLowerCasedNames = true
|
||||
|
||||
// Swap the memdb instance by the one which operates on case sensitive
|
||||
// names, hence obviating the need to unload anything that's already
|
||||
// loaded.
|
||||
err = c.identityStore.resetDB(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.identityStore.loadGroups(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Attempt to load identity artifacts once more after memdb is reset to
|
||||
// accept case sensitive names
|
||||
return loadFunc(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
func (i *IdentityStore) sanitizeName(name string) string {
|
||||
if i.disableLowerCasedNames {
|
||||
return name
|
||||
}
|
||||
return strings.ToLower(name)
|
||||
}
|
||||
|
||||
func (i *IdentityStore) loadGroups(ctx context.Context) error {
|
||||
|
@ -66,6 +100,18 @@ func (i *IdentityStore) loadGroups(ctx context.Context) error {
|
|||
continue
|
||||
}
|
||||
|
||||
// Ensure that there are no groups with duplicate names
|
||||
groupByName, err := i.MemDBGroupByName(ctx, group.Name, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if groupByName != nil {
|
||||
i.logger.Warn(errDuplicateIdentityName.Error(), "group_name", group.Name, "conflicting_group_name", groupByName.Name, "action", "merge the contents of duplicated groups into one and delete the other")
|
||||
if !i.disableLowerCasedNames {
|
||||
return errDuplicateIdentityName
|
||||
}
|
||||
}
|
||||
|
||||
if i.logger.IsDebug() {
|
||||
i.logger.Debug("loading group", "name", group.Name, "id", group.ID)
|
||||
}
|
||||
|
@ -187,6 +233,18 @@ func (i *IdentityStore) loadEntities(ctx context.Context) error {
|
|||
continue
|
||||
}
|
||||
|
||||
// Ensure that there are no entities with duplicate names
|
||||
entityByName, err := i.MemDBEntityByName(ctx, entity.Name, false)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if entityByName != nil {
|
||||
i.logger.Warn(errDuplicateIdentityName.Error(), "entity_name", entity.Name, "conflicting_entity_name", entityByName.Name, "action", "merge the duplicate entities into one")
|
||||
if !i.disableLowerCasedNames {
|
||||
return errDuplicateIdentityName
|
||||
}
|
||||
}
|
||||
|
||||
// Only update MemDB and don't hit the storage again
|
||||
err = i.upsertEntity(ctx, entity, nil, false)
|
||||
if err != nil {
|
||||
|
@ -223,7 +281,9 @@ func (i *IdentityStore) upsertEntityInTxn(ctx context.Context, txn *memdb.Txn, e
|
|||
return fmt.Errorf("entity is nil")
|
||||
}
|
||||
|
||||
for _, alias := range entity.Aliases {
|
||||
aliasFactors := make([]string, len(entity.Aliases))
|
||||
|
||||
for index, alias := range entity.Aliases {
|
||||
// Verify that alias is not associated to a different one already
|
||||
aliasByFactors, err := i.MemDBAliasByFactors(alias.MountAccessor, alias.Name, false, false)
|
||||
if err != nil {
|
||||
|
@ -244,11 +304,20 @@ func (i *IdentityStore) upsertEntityInTxn(ctx context.Context, txn *memdb.Txn, e
|
|||
return nil
|
||||
}
|
||||
|
||||
if strutil.StrListContains(aliasFactors, i.sanitizeName(alias.Name)+alias.MountAccessor) {
|
||||
i.logger.Warn(errDuplicateIdentityName.Error(), "alias_name", alias.Name, "mount_accessor", alias.MountAccessor, "entity_name", entity.Name, "action", "delete one of the duplicate aliases")
|
||||
if !i.disableLowerCasedNames {
|
||||
return errDuplicateIdentityName
|
||||
}
|
||||
}
|
||||
|
||||
// Insert or update alias in MemDB using the transaction created above
|
||||
err = i.MemDBUpsertAliasInTxn(txn, alias, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliasFactors[index] = i.sanitizeName(alias.Name) + alias.MountAccessor
|
||||
}
|
||||
|
||||
// If previous entity is set, update it in MemDB and persist it
|
||||
|
@ -583,10 +652,10 @@ func (i *IdentityStore) MemDBEntityByName(ctx context.Context, entityName string
|
|||
|
||||
txn := i.db.Txn(false)
|
||||
|
||||
return i.MemDBEntityByNameInTxn(txn, ctx, entityName, clone)
|
||||
return i.MemDBEntityByNameInTxn(ctx, txn, entityName, clone)
|
||||
}
|
||||
|
||||
func (i *IdentityStore) MemDBEntityByNameInTxn(txn *memdb.Txn, ctx context.Context, entityName string, clone bool) (*identity.Entity, error) {
|
||||
func (i *IdentityStore) MemDBEntityByNameInTxn(ctx context.Context, txn *memdb.Txn, entityName string, clone bool) (*identity.Entity, error) {
|
||||
if entityName == "" {
|
||||
return nil, fmt.Errorf("missing entity name")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue