mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 02:57:59 +00:00
Add testonly endpoints for Identity testing (#29461)
This commit is contained in:
@@ -141,6 +141,7 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo
|
|||||||
func (i *IdentityStore) paths() []*framework.Path {
|
func (i *IdentityStore) paths() []*framework.Path {
|
||||||
return framework.PathAppend(
|
return framework.PathAppend(
|
||||||
entityPaths(i),
|
entityPaths(i),
|
||||||
|
entityTestonlyPaths(i),
|
||||||
aliasPaths(i),
|
aliasPaths(i),
|
||||||
groupAliasPaths(i),
|
groupAliasPaths(i),
|
||||||
groupPaths(i),
|
groupPaths(i),
|
||||||
|
|||||||
13
vault/identity_store_injector.go
Normal file
13
vault/identity_store_injector.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
//go:build !testonly
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import "github.com/hashicorp/vault/sdk/framework"
|
||||||
|
|
||||||
|
// entityTestonlyPaths is a stub for non-testonly builds.
|
||||||
|
func entityTestonlyPaths(i *IdentityStore) []*framework.Path {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
836
vault/identity_store_injector_testonly.go
Normal file
836
vault/identity_store_injector_testonly.go
Normal file
@@ -0,0 +1,836 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
//go:build testonly
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
|
"github.com/hashicorp/vault/helper/identity"
|
||||||
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
|
"github.com/hashicorp/vault/helper/storagepacker"
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
"google.golang.org/protobuf/types/known/anypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// entityTestonlyPaths returns a list of testonly API endpoints supported to
|
||||||
|
// operate on entities in a way that is not supported by production Vault. These
|
||||||
|
// all generate duplicate identity resources IN STORAGE. MemDB won't reflect
|
||||||
|
// them until Vault has been sealed and unsealed again!
|
||||||
|
//
|
||||||
|
// Use of these endpoints is a bit nuanced as they are low level and do almost
|
||||||
|
// no validation. By design, they are allowing you to write invalid state into
|
||||||
|
// storage because that is what is needed to replicate some customer scenarios
|
||||||
|
// caused by historical bugs. Bear the following non-obvious things in mind if
|
||||||
|
// you use them.
|
||||||
|
//
|
||||||
|
// - Very little validation is done. You can create state that in invalid in
|
||||||
|
// ways that Vault, even with it's bugs, has never been able to create.
|
||||||
|
// - These write the duplicates directly to storage without checking contents.
|
||||||
|
// So if you call the same endpoint with the same name multiple times you
|
||||||
|
// will end up with even more duplicates of the same name.
|
||||||
|
// - Because they write direct to storage, they DON'T update MemDB so regular
|
||||||
|
// API calls won't see the created resources until you seal and unseal.
|
||||||
|
func entityTestonlyPaths(i *IdentityStore) []*framework.Path {
|
||||||
|
return []*framework.Path{
|
||||||
|
{
|
||||||
|
Pattern: "duplicate/entity-aliases",
|
||||||
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
|
OperationPrefix: "entity-aliases",
|
||||||
|
OperationVerb: "create-duplicates",
|
||||||
|
},
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"name": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Name of the entities to create",
|
||||||
|
},
|
||||||
|
"namespace_id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "NamespaceID of the entities to create",
|
||||||
|
},
|
||||||
|
"different_case": {
|
||||||
|
Type: framework.TypeBool,
|
||||||
|
Description: "Create entities with different case variations",
|
||||||
|
},
|
||||||
|
"mount_accessor": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Mount accessor ID for the alias",
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
Type: framework.TypeKVPairs,
|
||||||
|
Description: "Metadata",
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
Type: framework.TypeInt,
|
||||||
|
Description: "Number of entity aliases to create",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: i.createDuplicateEntityAliases(),
|
||||||
|
ForwardPerformanceStandby: true,
|
||||||
|
// Writing global (non-local) state should be replicated.
|
||||||
|
ForwardPerformanceSecondary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "duplicate/local-entity-alias",
|
||||||
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
|
OperationPrefix: "entity-alias",
|
||||||
|
OperationVerb: "create-duplicates",
|
||||||
|
},
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"name": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Name of the entities to create",
|
||||||
|
},
|
||||||
|
"namespace_id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "NamespaceID of the entities to create",
|
||||||
|
},
|
||||||
|
"canonical_id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "The canonical entity ID to attach the local alias to",
|
||||||
|
},
|
||||||
|
"mount_accessor": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Mount accessor ID for the alias",
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
Type: framework.TypeKVPairs,
|
||||||
|
Description: "Metadata",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: i.createDuplicateLocalEntityAlias(),
|
||||||
|
ForwardPerformanceStandby: true,
|
||||||
|
ForwardPerformanceSecondary: false, // Allow this on a perf secondary.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "duplicate/entities",
|
||||||
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
|
OperationPrefix: "entities",
|
||||||
|
OperationVerb: "create-duplicates",
|
||||||
|
},
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"name": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Name of the entities to create",
|
||||||
|
},
|
||||||
|
"namespace_id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "NamespaceID of the entities to create",
|
||||||
|
},
|
||||||
|
"different_case": {
|
||||||
|
Type: framework.TypeBool,
|
||||||
|
Description: "Create entities with different case variations",
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
Type: framework.TypeKVPairs,
|
||||||
|
Description: `Metadata to be associated with the entity.
|
||||||
|
In CLI, this parameter can be repeated multiple times, and it all gets merged together.
|
||||||
|
For example:
|
||||||
|
vault <command> <path> metadata=key1=value1 metadata=key2=value2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"policies": {
|
||||||
|
Type: framework.TypeCommaStringSlice,
|
||||||
|
Description: "Policies to be tied to the entity.",
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
Type: framework.TypeInt,
|
||||||
|
Description: "Number of entities to create",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: i.createDuplicateEntities(),
|
||||||
|
ForwardPerformanceStandby: true,
|
||||||
|
// Writing global (non-local) state should be replicated.
|
||||||
|
ForwardPerformanceSecondary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "duplicate/groups",
|
||||||
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
|
OperationPrefix: "groups",
|
||||||
|
OperationVerb: "create-duplicates",
|
||||||
|
},
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "ID of the group. If set, updates the corresponding existing group.",
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Type of the group, 'internal' or 'external'. Defaults to 'internal'",
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Name of the group.",
|
||||||
|
},
|
||||||
|
"namespace_id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "NamespaceID of the entities to create",
|
||||||
|
},
|
||||||
|
"different_case": {
|
||||||
|
Type: framework.TypeBool,
|
||||||
|
Description: "Create entities with different case variations",
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
Type: framework.TypeKVPairs,
|
||||||
|
Description: `Metadata to be associated with the group.
|
||||||
|
In CLI, this parameter can be repeated multiple times, and it all gets merged together.
|
||||||
|
For example:
|
||||||
|
vault <command> <path> metadata=key1=value1 metadata=key2=value2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"policies": {
|
||||||
|
Type: framework.TypeCommaStringSlice,
|
||||||
|
Description: "Policies to be tied to the group.",
|
||||||
|
},
|
||||||
|
"member_group_ids": {
|
||||||
|
Type: framework.TypeCommaStringSlice,
|
||||||
|
Description: "Group IDs to be assigned as group members.",
|
||||||
|
},
|
||||||
|
"member_entity_ids": {
|
||||||
|
Type: framework.TypeCommaStringSlice,
|
||||||
|
Description: "Entity IDs to be assigned as group members.",
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
Type: framework.TypeInt,
|
||||||
|
Description: "Number of groups to create",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: i.createDuplicateGroups(),
|
||||||
|
ForwardPerformanceStandby: true,
|
||||||
|
// Writing global (non-local) state should be replicated.
|
||||||
|
ForwardPerformanceSecondary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "entity/from-storage/?$",
|
||||||
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
|
OperationPrefix: "entity",
|
||||||
|
OperationVerb: "list",
|
||||||
|
OperationSuffix: "from-storage",
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.ListOperation: &framework.PathOperation{
|
||||||
|
Callback: i.listEntitiesFromStorage(),
|
||||||
|
ForwardPerformanceStandby: true,
|
||||||
|
// Allow reading local cluster state
|
||||||
|
ForwardPerformanceSecondary: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "group/from-storage/?$",
|
||||||
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
|
OperationPrefix: "group",
|
||||||
|
OperationVerb: "list",
|
||||||
|
OperationSuffix: "from-storage",
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.ListOperation: &framework.PathOperation{
|
||||||
|
Callback: i.listGroupsFromStorage(),
|
||||||
|
ForwardPerformanceStandby: true,
|
||||||
|
// Allow reading local cluster state
|
||||||
|
ForwardPerformanceSecondary: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonDuplicateFlags struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
NamespaceID string `json:"namespace_id"`
|
||||||
|
DifferentCase bool `json:"different_case"`
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonAliasFlags struct {
|
||||||
|
MountAccessor string `json:"mount_accessor"`
|
||||||
|
CanonicalID string `json:"canonical_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DuplicateEntityFlags struct {
|
||||||
|
CommonDuplicateFlags
|
||||||
|
Policies []string `json:"policies"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DuplicateGroupFlags struct {
|
||||||
|
CommonDuplicateFlags
|
||||||
|
Type string `json:"type"`
|
||||||
|
Policies []string `json:"policies"`
|
||||||
|
MemberGroupIDs []string `json:"member_group_ids"`
|
||||||
|
MemberEntityIDs []string `json:"member_entity_ids"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DuplicateEntityAliasFlags struct {
|
||||||
|
CommonDuplicateFlags
|
||||||
|
CommonAliasFlags
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DuplicateGroupAliasFlags struct {
|
||||||
|
CommonAliasFlags
|
||||||
|
Name string `json:"name"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) createDuplicateEntities() framework.OperationFunc {
|
||||||
|
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
metadata, ok := data.GetOk("metadata")
|
||||||
|
if !ok {
|
||||||
|
metadata = make(map[string]string)
|
||||||
|
}
|
||||||
|
flags := DuplicateEntityFlags{
|
||||||
|
CommonDuplicateFlags: CommonDuplicateFlags{
|
||||||
|
Name: data.Get("name").(string),
|
||||||
|
NamespaceID: data.Get("namespace_id").(string),
|
||||||
|
DifferentCase: data.Get("different_case").(bool),
|
||||||
|
Metadata: metadata.(map[string]string),
|
||||||
|
},
|
||||||
|
Policies: data.Get("policies").([]string),
|
||||||
|
Count: data.Get("count").(int),
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.Count < 1 {
|
||||||
|
flags.Count = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := i.CreateDuplicateEntitiesInStorage(ctx, flags)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Error("error creating duplicate entities", "error", err)
|
||||||
|
return logical.ErrorResponse("error creating duplicate entities"), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"entity_ids": ids,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) createDuplicateEntityAliases() framework.OperationFunc {
|
||||||
|
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
metadata, ok := data.GetOk("metadata")
|
||||||
|
if !ok {
|
||||||
|
metadata = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := DuplicateEntityAliasFlags{
|
||||||
|
CommonDuplicateFlags: CommonDuplicateFlags{
|
||||||
|
Name: data.Get("name").(string),
|
||||||
|
NamespaceID: data.Get("namespace_id").(string),
|
||||||
|
DifferentCase: data.Get("different_case").(bool),
|
||||||
|
Metadata: metadata.(map[string]string),
|
||||||
|
},
|
||||||
|
CommonAliasFlags: CommonAliasFlags{
|
||||||
|
MountAccessor: data.Get("mount_accessor").(string),
|
||||||
|
},
|
||||||
|
Count: data.Get("count").(int),
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.Count < 1 {
|
||||||
|
flags.Count = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, _, err := i.CreateDuplicateEntityAliasesInStorage(ctx, flags)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Error("error creating duplicate entities", "error", err)
|
||||||
|
return logical.ErrorResponse("error creating duplicate entities"), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"entity_ids": ids,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) createDuplicateLocalEntityAlias() framework.OperationFunc {
|
||||||
|
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
metadata, ok := data.GetOk("metadata")
|
||||||
|
if !ok {
|
||||||
|
metadata = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := DuplicateEntityAliasFlags{
|
||||||
|
CommonDuplicateFlags: CommonDuplicateFlags{
|
||||||
|
Name: data.Get("name").(string),
|
||||||
|
NamespaceID: data.Get("namespace_id").(string),
|
||||||
|
Metadata: metadata.(map[string]string),
|
||||||
|
},
|
||||||
|
CommonAliasFlags: CommonAliasFlags{
|
||||||
|
MountAccessor: data.Get("mount_accessor").(string),
|
||||||
|
CanonicalID: data.Get("canonical_id").(string),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.Name == "" {
|
||||||
|
return logical.ErrorResponse("name is required"), nil
|
||||||
|
}
|
||||||
|
if flags.CanonicalID == "" {
|
||||||
|
return logical.ErrorResponse("canonical_id is required"), nil
|
||||||
|
}
|
||||||
|
if flags.MountAccessor == "" {
|
||||||
|
return logical.ErrorResponse("mount_accessor is required"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := i.CreateDuplicateLocalEntityAliasInStorage(ctx, flags)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Error("error creating duplicate local alias", "error", err)
|
||||||
|
return logical.ErrorResponse("error creating duplicate local alias"), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"alias_ids": ids,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) createDuplicateGroups() framework.OperationFunc {
|
||||||
|
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
metadata, ok := data.GetOk("metadata")
|
||||||
|
if !ok {
|
||||||
|
metadata = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := DuplicateGroupFlags{
|
||||||
|
CommonDuplicateFlags: CommonDuplicateFlags{
|
||||||
|
Name: data.Get("name").(string),
|
||||||
|
NamespaceID: data.Get("namespace_id").(string),
|
||||||
|
DifferentCase: data.Get("different_case").(bool),
|
||||||
|
Metadata: metadata.(map[string]string),
|
||||||
|
},
|
||||||
|
Type: data.Get("type").(string),
|
||||||
|
Policies: data.Get("policies").([]string),
|
||||||
|
MemberGroupIDs: data.Get("member_group_ids").([]string),
|
||||||
|
MemberEntityIDs: data.Get("member_entity_ids").([]string),
|
||||||
|
Count: data.Get("count").(int),
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.Count < 1 {
|
||||||
|
flags.Count = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := i.CreateDuplicateGroupsInStorage(ctx, flags)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Error("error creating duplicate entities", "error", err)
|
||||||
|
return logical.ErrorResponse("error creating duplicate entities"), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"group_ids": ids,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) CreateDuplicateGroupsInStorage(ctx context.Context, flags DuplicateGroupFlags) ([]string, error) {
|
||||||
|
var groupIDs []string
|
||||||
|
if flags.NamespaceID == "" {
|
||||||
|
flags.NamespaceID = namespace.RootNamespaceID
|
||||||
|
}
|
||||||
|
for d := 0; d < flags.Count; d++ {
|
||||||
|
groupID, err := uuid.GenerateUUID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
groupIDs = append(groupIDs, groupID)
|
||||||
|
|
||||||
|
// Alias name is either exact match or different case
|
||||||
|
groupName := flags.Name
|
||||||
|
if flags.DifferentCase {
|
||||||
|
groupName = randomCase(flags.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := &identity.Group{
|
||||||
|
ID: groupID,
|
||||||
|
Name: groupName,
|
||||||
|
Policies: flags.Policies,
|
||||||
|
MemberEntityIDs: flags.MemberEntityIDs,
|
||||||
|
ParentGroupIDs: flags.MemberGroupIDs,
|
||||||
|
Type: flags.Type,
|
||||||
|
NamespaceID: flags.NamespaceID,
|
||||||
|
BucketKey: i.groupPacker.BucketKey(groupID),
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := ptypes.MarshalAny(g)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item := &storagepacker.Item{
|
||||||
|
ID: g.ID,
|
||||||
|
Message: group,
|
||||||
|
}
|
||||||
|
if err = i.groupPacker.PutItem(ctx, item); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDuplicateEntityAliasesInStorage creates n entities with a duplicate alias in storage
|
||||||
|
// This should only be used in testing
|
||||||
|
//
|
||||||
|
// Pass in mount type and accessor to create the entities
|
||||||
|
func (i *IdentityStore) CreateDuplicateEntityAliasesInStorage(ctx context.Context, flags DuplicateEntityAliasFlags) ([]string, []string, error) {
|
||||||
|
var bucketKeys []string
|
||||||
|
var entityIDs []string
|
||||||
|
|
||||||
|
for d := 0; d < flags.Count; d++ {
|
||||||
|
entityID := fmt.Sprintf("%s-%d", flags.Name, d)
|
||||||
|
|
||||||
|
policyID := fmt.Sprintf("policy-%s-%d", flags.Name, d)
|
||||||
|
|
||||||
|
entityDupName := fmt.Sprintf("%s-entity-dup-%d", flags.Name, d)
|
||||||
|
aliasDupName := fmt.Sprintf("%s-alias-dup", flags.Name)
|
||||||
|
|
||||||
|
a := &identity.Alias{
|
||||||
|
ID: entityID,
|
||||||
|
CanonicalID: entityID,
|
||||||
|
MountAccessor: flags.CommonAliasFlags.MountAccessor,
|
||||||
|
Name: aliasDupName,
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketKey := i.entityPacker.BucketKey(entityID)
|
||||||
|
bucketKeys = append(bucketKeys, bucketKey)
|
||||||
|
entityIDs = append(entityIDs, entityID)
|
||||||
|
|
||||||
|
e := &identity.Entity{
|
||||||
|
ID: entityID,
|
||||||
|
Name: entityDupName,
|
||||||
|
Aliases: []*identity.Alias{
|
||||||
|
a,
|
||||||
|
},
|
||||||
|
NamespaceID: namespace.RootNamespaceID,
|
||||||
|
BucketKey: bucketKey,
|
||||||
|
Policies: []string{policyID},
|
||||||
|
}
|
||||||
|
|
||||||
|
entity, err := ptypes.MarshalAny(e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
item := &storagepacker.Item{
|
||||||
|
ID: e.ID,
|
||||||
|
Message: entity,
|
||||||
|
}
|
||||||
|
if err = i.entityPacker.PutItem(ctx, item); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityIDs, bucketKeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDuplicateLocalEntityAliasInStorage creates a single local entity alias
|
||||||
|
// directly in storage. This should only be used in testing. This method can
|
||||||
|
// only create local aliases and assumes that the entity is already created
|
||||||
|
// separately and it's ID passed as CanonicalID. No validation of the mounts or
|
||||||
|
// entity is done so if you need these to be realistic the caller must ensure
|
||||||
|
// the entity and mount exist and that the mount is a local auth method of the
|
||||||
|
// right type.
|
||||||
|
//
|
||||||
|
// Pass in mount type and accessor to create the entities
|
||||||
|
func (i *IdentityStore) CreateDuplicateLocalEntityAliasInStorage(ctx context.Context, flags DuplicateEntityAliasFlags) ([]string, error) {
|
||||||
|
var aliasIDs []string
|
||||||
|
if flags.NamespaceID == "" {
|
||||||
|
flags.NamespaceID = namespace.RootNamespaceID
|
||||||
|
}
|
||||||
|
|
||||||
|
aliasID, err := uuid.GenerateUUID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aliasIDs = append(aliasIDs, aliasID)
|
||||||
|
|
||||||
|
a := &identity.Alias{
|
||||||
|
ID: aliasID,
|
||||||
|
CanonicalID: flags.CommonAliasFlags.CanonicalID,
|
||||||
|
MountAccessor: flags.CommonAliasFlags.MountAccessor,
|
||||||
|
Name: flags.Name,
|
||||||
|
Local: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
localAliases, err := i.parseLocalAliases(flags.CommonAliasFlags.CanonicalID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if localAliases == nil {
|
||||||
|
localAliases = &identity.LocalAliases{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't check if this is a duplicate, since we're allowing the developer to
|
||||||
|
// create duplicates here.
|
||||||
|
localAliases.Aliases = append(localAliases.Aliases, a)
|
||||||
|
|
||||||
|
marshaledAliases, err := anypb.New(localAliases)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := i.localAliasPacker.PutItem(ctx, &storagepacker.Item{
|
||||||
|
ID: flags.CommonAliasFlags.CanonicalID,
|
||||||
|
Message: marshaledAliases,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliasIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) CreateDuplicateEntitiesInStorage(ctx context.Context, flags DuplicateEntityFlags) ([]string, error) {
|
||||||
|
var entityIDs []string
|
||||||
|
for d := 0; d < flags.Count; d++ {
|
||||||
|
entityID, err := uuid.GenerateUUID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entityIDs = append(entityIDs, entityID)
|
||||||
|
|
||||||
|
dupName := flags.Name
|
||||||
|
if flags.DifferentCase {
|
||||||
|
dupName = randomCase(flags.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := &identity.Entity{
|
||||||
|
ID: entityID,
|
||||||
|
Name: dupName,
|
||||||
|
NamespaceID: flags.NamespaceID,
|
||||||
|
BucketKey: i.entityPacker.BucketKey(entityID),
|
||||||
|
}
|
||||||
|
|
||||||
|
entity, err := ptypes.MarshalAny(e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item := &storagepacker.Item{
|
||||||
|
ID: e.ID,
|
||||||
|
Message: entity,
|
||||||
|
}
|
||||||
|
if err = i.entityPacker.PutItem(ctx, item); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomCase(s string) string {
|
||||||
|
return strings.Map(func(r rune) rune {
|
||||||
|
if rand.Intn(2) == 0 {
|
||||||
|
return unicode.ToUpper(r)
|
||||||
|
}
|
||||||
|
return unicode.ToLower(r)
|
||||||
|
}, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) ListEntitiesFromStorage(ctx context.Context) ([]*identity.Entity, error) {
|
||||||
|
// Get Existing Buckets
|
||||||
|
existing, err := i.entityPacker.View().List(ctx, storagepacker.StoragePackerBucketsPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan for entity buckets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
workerCount := 64
|
||||||
|
entities := make([]*identity.Entity, 0)
|
||||||
|
|
||||||
|
// Make channels for worker pool
|
||||||
|
broker := make(chan string)
|
||||||
|
quit := make(chan bool)
|
||||||
|
|
||||||
|
errs := make(chan error, (len(existing)))
|
||||||
|
result := make(chan *storagepacker.Bucket, len(existing))
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
// Stand up workers
|
||||||
|
for j := 0; j < workerCount; j++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case key, ok := <-broker:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket, err := i.entityPacker.GetBucket(ctx, storagepacker.StoragePackerBucketsPrefix+key)
|
||||||
|
if err != nil {
|
||||||
|
errs <- err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result <- bucket
|
||||||
|
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute the collected keys to the workers in a go routine
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j, key := range existing {
|
||||||
|
if j%500 == 0 {
|
||||||
|
i.logger.Debug("entities loading", "progress", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
broker <- key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the broker, causing worker routines to exit
|
||||||
|
close(broker)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Restore each key by pulling from the result chan
|
||||||
|
LOOP:
|
||||||
|
for j := 0; j < len(existing); j++ {
|
||||||
|
select {
|
||||||
|
case err = <-errs:
|
||||||
|
// Close all go routines
|
||||||
|
close(quit)
|
||||||
|
break LOOP
|
||||||
|
|
||||||
|
case bucket := <-result:
|
||||||
|
// If there is no entry, nothing to restore
|
||||||
|
if bucket == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range bucket.Items {
|
||||||
|
entity, err := i.parseEntityFromBucketItem(ctx, item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if entity == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load local aliases for entity
|
||||||
|
localAliases, err := i.parseLocalAliases(entity.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if localAliases != nil {
|
||||||
|
entity.Aliases = append(entity.Aliases, localAliases.Aliases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
entities = append(entities, entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let all go routines finish
|
||||||
|
wg.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) listEntitiesFromStorage() framework.OperationFunc {
|
||||||
|
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
entities, err := i.ListEntitiesFromStorage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Error("error listing entities", "error", err)
|
||||||
|
return logical.ErrorResponse("error listing entities"), err
|
||||||
|
}
|
||||||
|
resp := &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"entities": entities,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) ListGroupsFromStorage(ctx context.Context) ([]*identity.Group, error) {
|
||||||
|
existing, err := i.groupPacker.View().List(ctx, groupBucketsPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan for groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := make([]*identity.Group, 0)
|
||||||
|
|
||||||
|
for _, key := range existing {
|
||||||
|
bucket, err := i.groupPacker.GetBucket(ctx, groupBucketsPrefix+key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bucket == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range bucket.Items {
|
||||||
|
group, err := i.parseGroupFromBucketItem(item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if group == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentityStore) listGroupsFromStorage() framework.OperationFunc {
|
||||||
|
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
groups, err := i.ListGroupsFromStorage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Error("error listing groups", "error", err)
|
||||||
|
return logical.ErrorResponse("error listing groups"), err
|
||||||
|
}
|
||||||
|
resp := &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"groups": groups,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user