Commit 48b057b6 authored by Jeff Mitchell's avatar Jeff Mitchell
Browse files

Merge branch 'master-oss' into 1.0-beta-oss

parents 3d1f0d76 7e0b47df
Showing with 483 additions and 54 deletions
+483 -54
......@@ -39,6 +39,7 @@ example.vault.d
website/vendor
website/.bundle
website/build
website/tmp
# Vagrant
.vagrant/
......@@ -92,3 +93,14 @@ ui/vault-ui-integration-server.pid
# for building static assets
node_modules
package-lock.json
# Website
website/.bundle
website/build/
website/npm-debug.log
website/vendor
website/.bundle
website/.cache
website/assets/node_modules
website/assets/public
website/components/node_modules
......@@ -3,6 +3,11 @@
CHANGES:
* core: HA lock file is no longer copied during `operator migrate` [GH-5503]
* core: Tokens are now prefixed by a designation to indicate what type of
token they are. Service tokens start with `s.` and batch tokens start with
`b.`. Existing tokens will still work (they are all of service type and will
be considered as such). Prefixing allows us to be more efficient when
consuming a token, which keeps the critical path of requests faster.
FEATURES:
......@@ -12,12 +17,15 @@ FEATURES:
IMPROVEMENTS:
* auth/token: New tokens are salted using SHA2-256 HMAC instead of SHA1 hash
* identity: Identity names will now be handled case insensitively by default.
This includes names of entities, aliases and groups [GH-5404]
* secret/database: Allow Cassandra user to be non-superuser so long as it has
role creation permissions [GH-5402]
* secret/radius: Allow setting the NAS Identifier value in the generated
packet [GH-5465]
* ui: Allow viewing and updating Vault license via the UI
* ui: Onboarding will now display your progress through the chosen tutorials
* ui: Dynamic secret backends obfuscate sensitive data by default and visibility is toggleable
BUG FIXES:
......@@ -25,6 +33,7 @@ BUG FIXES:
* core: Fix generate-root operations requiring empty `otp` to be provided
instead of an empty body [GH-5495]
* identity: Remove lookup check during alias removal from entity [GH-5524]
* secret/pki: Fix TTL/MaxTTL check when using `sign-verbatim` [GH-5549]
* secret/pki: Fix regression in 0.11.2+ causing the NotBefore value of
generated certificates to be set to the Unix epoch if the role value was not
set, instead of using the default of 30 seconds [GH-5481]
......
......@@ -78,7 +78,7 @@ func prepareCassandraTestContainer(t *testing.T) (func(), string, int) {
}
func TestBackend_basic(t *testing.T) {
if os.Getenv("TRAVIS") != "true" {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
config := logical.TestBackendConfig()
......@@ -102,7 +102,7 @@ func TestBackend_basic(t *testing.T) {
}
func TestBackend_roleCrud(t *testing.T) {
if os.Getenv("TRAVIS") != "true" {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
config := logical.TestBackendConfig()
......
......@@ -154,8 +154,6 @@ func (b *backend) pathSignVerbatim(ctx context.Context, req *logical.Request, da
}
entry := &roleEntry{
TTL: b.System().DefaultLeaseTTL(),
MaxTTL: b.System().MaxLeaseTTL(),
AllowLocalhost: true,
AllowAnyName: true,
AllowIPSANs: true,
......@@ -186,10 +184,6 @@ func (b *backend) pathSignVerbatim(ctx context.Context, req *logical.Request, da
entry.NoStore = role.NoStore
}
if entry.MaxTTL > 0 && entry.TTL > entry.MaxTTL {
return logical.ErrorResponse(fmt.Sprintf("requested ttl of %s is greater than max ttl of %s", entry.TTL, entry.MaxTTL)), nil
}
return b.pathIssueSignCert(ctx, req, data, entry, true, true)
}
......@@ -244,6 +238,7 @@ func (b *backend) pathIssueSignCert(ctx context.Context, req *logical.Request, d
}
respData := map[string]interface{}{
"expiration": int64(parsedBundle.Certificate.NotAfter.Unix()),
"serial_number": cb.SerialNumber,
}
......
......@@ -73,7 +73,7 @@ func prepareCassandraTestContainer(t *testing.T) (func(), string, int) {
}
func TestCassandra_Initialize(t *testing.T) {
if os.Getenv("TRAVIS") != "true" {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
cleanup, address, port := prepareCassandraTestContainer(t)
......@@ -118,7 +118,7 @@ func TestCassandra_Initialize(t *testing.T) {
}
func TestCassandra_CreateUser(t *testing.T) {
if os.Getenv("TRAVIS") != "true" {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
cleanup, address, port := prepareCassandraTestContainer(t)
......@@ -158,7 +158,7 @@ func TestCassandra_CreateUser(t *testing.T) {
}
func TestMyCassandra_RenewUser(t *testing.T) {
if os.Getenv("TRAVIS") != "true" {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
cleanup, address, port := prepareCassandraTestContainer(t)
......@@ -203,7 +203,7 @@ func TestMyCassandra_RenewUser(t *testing.T) {
}
func TestCassandra_RevokeUser(t *testing.T) {
if os.Getenv("TRAVIS") != "true" {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
cleanup, address, port := prepareCassandraTestContainer(t)
......@@ -253,7 +253,7 @@ func TestCassandra_RevokeUser(t *testing.T) {
}
func TestCassandra_RotateRootCredentials(t *testing.T) {
if os.Getenv("TRAVIS") != "true" {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
cleanup, address, port := prepareCassandraTestContainer(t)
......
......@@ -42,7 +42,22 @@
{{#if (eq attr.type "object")}}
{{info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(stringify (get model attr.name))}}
{{else}}
{{info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
{{#if (or
(eq attr.name "key")
(eq attr.name "secretKey")
(eq attr.name "securityToken")
(eq attr.name "privateKey")
)}}
{{#info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
<MaskedInput
@value={{get model attr.name}}
@name={{attr.name}}
@displayOnly={{true}}
/>
{{/info-table-row}}
{{else}}
{{info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
{{/if}}
{{/if}}
{{/each}}
</div>
......
......@@ -52,7 +52,7 @@
Access Key
</label>
<div class="control">
{{input type="text" id="access" name="access" class="input" value=accessKey data-test-aws-input="accessKey"}}
{{input type="text" id="access" name="access" class="input" autocomplete="off" value=accessKey data-test-aws-input="accessKey"}}
</div>
</div>
......@@ -61,7 +61,7 @@
Secret Key
</label>
<div class="control">
{{input type="text" id="secret" name="secret" class="input" value=secretKey data-test-aws-input="secretKey"}}
{{input type="password" id="secret" name="secret" class="input" value=secretKey data-test-aws-input="secretKey"}}
</div>
</div>
......@@ -119,4 +119,3 @@
</div>
</form>
{{/if}}
......@@ -52,7 +52,7 @@ module('Acceptance | ssh secret backend', function(hooks) {
'otp credential url is correct'
);
assert.dom('[data-test-row-label="Key"]').exists({ count: 1 }, 'renders the key');
assert.dom('[data-test-row-value="Key"]').exists({ count: 1 }, "renders the key's value");
assert.dom('[data-test-masked-input]').exists({ count: 1 }, 'renders mask for key value');
assert.dom('[data-test-row-label="Port"]').exists({ count: 1 }, 'renders the port');
assert.dom('[data-test-row-value="Port"]').exists({ count: 1 }, "renders the port's value");
},
......
......@@ -497,9 +497,12 @@ func (m *ExpirationManager) Restore(errorFunc func()) (retErr error) {
wg.Wait()
m.restoreModeLock.Lock()
m.restoreLoaded = sync.Map{}
m.restoreLocks = nil
atomic.StoreInt32(m.restoreMode, 0)
m.restoreLoaded.Range(func(k, v interface{}) bool {
m.restoreLoaded.Delete(k)
return true
})
m.restoreLocks = nil
m.restoreModeLock.Unlock()
m.logger.Info("lease restore complete")
......
......@@ -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"},
}
......
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)
if err != nil {
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
}
err = c.identityStore.loadGroups(ctx)
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
}
return nil
// Attempt to load identity artifacts once more after memdb is reset to
// accept case sensitive names
return loadFunc(ctx)
}
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")
}
......
source "https://rubygems.org"
gem "ffi", "~> 1.9.24"
gem "middleman-hashicorp", "0.3.34"
gem 'middleman', '~> 4.2'
gem 'middleman-hashicorp', git: 'https://github.com/carrot/middleman-hashicorp'
# gem 'middleman-hashicorp', path: '/Users/jeff/Sites/middleman-hashicorp-carrot'
gem 'builder'
gem 'tzinfo-data', platforms: [:mswin, :mingw, :jruby]
gem 'wdm', '~> 0.1', platforms: [:mswin, :mingw]
gem 'middleman-dato'
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment