Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Case insensitive identity names #5404

Merged
merged 8 commits into from
Oct 19, 2018
20 changes: 14 additions & 6 deletions vault/identity_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 6 additions & 4 deletions vault/identity_store_aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this taken out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to ensure that we always update the alias. This is useful when an existing alias name is updated with its casing altered.

// Nothing to do
return nil, nil
}
}

resp := &logical.Response{}
Expand Down Expand Up @@ -255,6 +251,12 @@ func (i *IdentityStore) handleAliasUpdateCommon() framework.OperationFunc {
return nil, err
}

for index, item := range entity.Aliases {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, can you explain what this change is for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the updates are held by the alias object. This is where we reflect those changes in the entity.

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
Expand Down
84 changes: 84 additions & 0 deletions vault/identity_store_aliases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,97 @@ package vault

import (
"reflect"
"strings"
"testing"

"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/namespace"
"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) {
Expand Down
2 changes: 1 addition & 1 deletion vault/identity_store_entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
76 changes: 74 additions & 2 deletions vault/identity_store_entities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"sort"
"strings"
"testing"

uuid "github.com/hashicorp/go-uuid"
Expand All @@ -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)
Expand Down Expand Up @@ -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"},
}

Expand Down
76 changes: 76 additions & 0 deletions vault/identity_store_group_aliases_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vault

import (
"strings"
"testing"

credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
Expand All @@ -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 {
Expand Down
Loading