mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +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
	 Bianca
					Bianca