2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2017-10-11 17:21:20 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2018-01-08 18:31:38 +00:00
|
|
|
"context"
|
2017-10-11 17:21:20 +00:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
2021-09-17 18:03:47 +00:00
|
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
2017-10-11 17:21:20 +00:00
|
|
|
"github.com/hashicorp/vault/helper/identity"
|
2018-09-18 03:03:00 +00:00
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
2018-06-24 11:45:53 +00:00
|
|
|
"github.com/hashicorp/vault/helper/storagepacker"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
2022-07-28 14:40:38 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/custommetadata"
|
2019-04-13 07:44:06 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2017-10-11 17:21:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// aliasPaths returns the API endpoints to operate on aliases.
|
|
|
|
// Following are the paths supported:
|
2017-11-14 01:59:42 +00:00
|
|
|
// entity-alias - To register/modify an alias
|
|
|
|
// entity-alias/id - To read, modify, delete and list aliases based on their ID
|
2017-10-11 17:21:20 +00:00
|
|
|
func aliasPaths(i *IdentityStore) []*framework.Path {
|
|
|
|
return []*framework.Path{
|
2017-11-02 20:05:48 +00:00
|
|
|
{
|
|
|
|
Pattern: "entity-alias$",
|
2023-04-12 19:44:43 +00:00
|
|
|
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "entity",
|
|
|
|
OperationVerb: "create",
|
|
|
|
OperationSuffix: "alias",
|
|
|
|
},
|
|
|
|
|
2017-11-02 20:05:48 +00:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"id": {
|
|
|
|
Type: framework.TypeString,
|
2017-11-14 01:59:42 +00:00
|
|
|
Description: "ID of the entity alias. If set, updates the corresponding entity alias.",
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
2017-11-14 01:59:42 +00:00
|
|
|
// entity_id is deprecated in favor of canonical_id
|
2017-11-02 20:05:48 +00:00
|
|
|
"entity_id": {
|
2018-07-25 02:01:58 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `Entity ID to which this alias belongs.
|
|
|
|
This field is deprecated, use canonical_id.`,
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
|
|
|
"canonical_id": {
|
|
|
|
Type: framework.TypeString,
|
2018-07-25 02:01:58 +00:00
|
|
|
Description: "Entity ID to which this alias belongs",
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
|
|
|
"mount_accessor": {
|
|
|
|
Type: framework.TypeString,
|
2018-07-25 02:01:58 +00:00
|
|
|
Description: "Mount accessor to which this alias belongs to; unused for a modify",
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
|
|
|
"name": {
|
|
|
|
Type: framework.TypeString,
|
2018-07-25 02:01:58 +00:00
|
|
|
Description: "Name of the alias; unused for a modify",
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
2021-09-17 18:03:47 +00:00
|
|
|
"custom_metadata": {
|
|
|
|
Type: framework.TypeKVPairs,
|
|
|
|
Description: "User provided key-value pairs",
|
|
|
|
},
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
2019-06-14 16:53:00 +00:00
|
|
|
logical.UpdateOperation: i.handleAliasCreateUpdate(),
|
2017-10-11 17:21:20 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: strings.TrimSpace(aliasHelp["alias"][0]),
|
|
|
|
HelpDescription: strings.TrimSpace(aliasHelp["alias"][1]),
|
|
|
|
},
|
2017-11-02 20:05:48 +00:00
|
|
|
{
|
|
|
|
Pattern: "entity-alias/id/" + framework.GenericNameRegex("id"),
|
2023-04-12 19:44:43 +00:00
|
|
|
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "entity",
|
|
|
|
OperationSuffix: "alias-by-id",
|
|
|
|
},
|
|
|
|
|
2017-11-02 20:05:48 +00:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "ID of the alias",
|
|
|
|
},
|
|
|
|
// entity_id is deprecated
|
|
|
|
"entity_id": {
|
2018-07-25 02:01:58 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `Entity ID to which this alias belongs to.
|
|
|
|
This field is deprecated, use canonical_id.`,
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
|
|
|
"canonical_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Entity ID to which this alias should be tied to",
|
|
|
|
},
|
|
|
|
"mount_accessor": {
|
|
|
|
Type: framework.TypeString,
|
2018-07-25 02:01:58 +00:00
|
|
|
Description: "(Unused)",
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
|
|
|
"name": {
|
|
|
|
Type: framework.TypeString,
|
2018-07-25 02:01:58 +00:00
|
|
|
Description: "(Unused)",
|
2017-10-11 17:21:20 +00:00
|
|
|
},
|
2021-09-17 18:03:47 +00:00
|
|
|
"custom_metadata": {
|
|
|
|
Type: framework.TypeKVPairs,
|
|
|
|
Description: "User provided key-value pairs",
|
|
|
|
},
|
2017-10-11 17:21:20 +00:00
|
|
|
},
|
2023-04-12 19:44:43 +00:00
|
|
|
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleAliasCreateUpdate(),
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationVerb: "update",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
logical.ReadOperation: &framework.PathOperation{
|
|
|
|
Callback: i.pathAliasIDRead(),
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationVerb: "read",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
logical.DeleteOperation: &framework.PathOperation{
|
|
|
|
Callback: i.pathAliasIDDelete(),
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationVerb: "delete",
|
|
|
|
},
|
|
|
|
},
|
2017-10-11 17:21:20 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: strings.TrimSpace(aliasHelp["alias-id"][0]),
|
|
|
|
HelpDescription: strings.TrimSpace(aliasHelp["alias-id"][1]),
|
|
|
|
},
|
2017-11-02 20:05:48 +00:00
|
|
|
{
|
|
|
|
Pattern: "entity-alias/id/?$",
|
2023-04-12 19:44:43 +00:00
|
|
|
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "entity",
|
|
|
|
OperationVerb: "list",
|
|
|
|
OperationSuffix: "aliases-by-id",
|
|
|
|
},
|
|
|
|
|
2017-11-02 20:05:48 +00:00
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
2018-01-08 18:31:38 +00:00
|
|
|
logical.ListOperation: i.pathAliasIDList(),
|
2017-11-02 20:05:48 +00:00
|
|
|
},
|
|
|
|
|
2017-10-11 17:21:20 +00:00
|
|
|
HelpSynopsis: strings.TrimSpace(aliasHelp["alias-id-list"][0]),
|
|
|
|
HelpDescription: strings.TrimSpace(aliasHelp["alias-id-list"][1]),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// handleAliasCreateUpdate is used to create or update an alias
|
|
|
|
func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
|
2018-01-08 18:31:38 +00:00
|
|
|
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2018-07-25 02:01:58 +00:00
|
|
|
var err error
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2018-01-08 18:31:38 +00:00
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// Get alias name, if any
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
|
|
|
|
// Get mount accessor, if any
|
|
|
|
mountAccessor := d.Get("mount_accessor").(string)
|
|
|
|
|
|
|
|
// Get ID, if any
|
|
|
|
id := d.Get("id").(string)
|
|
|
|
|
2021-09-17 18:03:47 +00:00
|
|
|
// Get custom metadata, if any
|
|
|
|
customMetadata := make(map[string]string)
|
|
|
|
data, customMetadataExists := d.GetOk("custom_metadata")
|
|
|
|
if customMetadataExists {
|
2021-12-10 23:07:47 +00:00
|
|
|
customMetadata = data.(map[string]string)
|
2021-09-17 18:03:47 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 02:01:58 +00:00
|
|
|
// Get entity id
|
|
|
|
canonicalID := d.Get("canonical_id").(string)
|
|
|
|
if canonicalID == "" {
|
|
|
|
// For backwards compatibility
|
|
|
|
canonicalID = d.Get("entity_id").(string)
|
2018-01-08 18:31:38 +00:00
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2021-12-10 23:07:47 +00:00
|
|
|
// validate customMetadata if provided
|
2021-09-17 18:03:47 +00:00
|
|
|
if len(customMetadata) != 0 {
|
2022-07-28 14:40:38 +00:00
|
|
|
if err := custommetadata.Validate(customMetadata); err != nil {
|
2021-09-17 18:03:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
i.lock.Lock()
|
|
|
|
defer i.lock.Unlock()
|
|
|
|
|
|
|
|
// This block is run if they provided an ID
|
|
|
|
{
|
|
|
|
// If they provide an ID it must be an update. Find the alias, perform
|
|
|
|
// due diligence, call the update function.
|
|
|
|
if id != "" {
|
|
|
|
alias, err := i.MemDBAliasByID(id, true, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if alias == nil {
|
|
|
|
return logical.ErrorResponse("invalid alias ID provided"), nil
|
|
|
|
}
|
|
|
|
if alias.NamespaceID != ns.ID {
|
|
|
|
return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied
|
|
|
|
}
|
2021-12-10 23:07:47 +00:00
|
|
|
if !customMetadataExists {
|
|
|
|
customMetadata = alias.CustomMetadata
|
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
switch {
|
2021-12-10 23:07:47 +00:00
|
|
|
case mountAccessor == "" && name == "":
|
2023-05-01 10:42:30 +00:00
|
|
|
// Check if the canonicalID or the customMetadata are being
|
|
|
|
// updated
|
|
|
|
if canonicalID == "" && !customMetadataExists {
|
2019-06-14 16:53:00 +00:00
|
|
|
// Nothing to do, so be idempotent
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
name = alias.Name
|
|
|
|
mountAccessor = alias.MountAccessor
|
|
|
|
case mountAccessor == "":
|
|
|
|
// No change to mount accessor
|
|
|
|
mountAccessor = alias.MountAccessor
|
|
|
|
case name == "":
|
|
|
|
// No change to mount name
|
|
|
|
name = alias.Name
|
|
|
|
default:
|
2021-09-17 18:03:47 +00:00
|
|
|
// mountAccessor, name and customMetadata provided
|
2019-06-14 16:53:00 +00:00
|
|
|
}
|
2021-10-15 19:20:00 +00:00
|
|
|
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata)
|
2018-07-25 02:01:58 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// If they didn't provide an ID, we must have both accessor and name provided
|
|
|
|
if mountAccessor == "" || name == "" {
|
|
|
|
return logical.ErrorResponse("'id' or 'mount_accessor' and 'name' must be provided"), nil
|
2018-07-25 02:01:58 +00:00
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// Look up the alias by factors; if it's found it's an update
|
2021-08-30 19:31:11 +00:00
|
|
|
mountEntry := i.router.MatchingMountByAccessor(mountAccessor)
|
2019-06-14 16:53:00 +00:00
|
|
|
if mountEntry == nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", mountAccessor)), nil
|
2018-07-25 02:01:58 +00:00
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
if mountEntry.NamespaceID != ns.ID {
|
|
|
|
return logical.ErrorResponse("matching mount is in a different namespace than request"), logical.ErrPermissionDenied
|
|
|
|
}
|
|
|
|
alias, err := i.MemDBAliasByFactors(mountAccessor, name, false, false)
|
2017-10-11 17:21:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
if alias != nil {
|
|
|
|
if alias.NamespaceID != ns.ID {
|
|
|
|
return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied
|
2018-07-25 02:01:58 +00:00
|
|
|
}
|
2021-10-15 19:20:00 +00:00
|
|
|
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata)
|
2017-10-11 17:21:20 +00:00
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
// At this point we know it's a new creation request
|
2021-10-15 19:20:00 +00:00
|
|
|
return i.handleAliasCreate(ctx, canonicalID, name, mountAccessor, mountEntry.Local, customMetadata)
|
2019-06-14 16:53:00 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-25 02:01:58 +00:00
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name, mountAccessor string, local bool, customMetadata map[string]string) (*logical.Response, error) {
|
2019-06-14 16:53:00 +00:00
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-06-24 11:45:53 +00:00
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
var entity *identity.Entity
|
2019-06-14 16:53:00 +00:00
|
|
|
if canonicalID != "" {
|
|
|
|
entity, err = i.MemDBEntityByID(canonicalID, true)
|
2018-07-25 02:01:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
if entity == nil {
|
|
|
|
return logical.ErrorResponse("invalid canonical ID"), nil
|
|
|
|
}
|
|
|
|
if entity.NamespaceID != ns.ID {
|
|
|
|
return logical.ErrorResponse("entity found with 'canonical_id' not in request namespace"), logical.ErrPermissionDenied
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
if entity == nil && local {
|
|
|
|
// Check to see if the entity creation should be forwarded.
|
|
|
|
entity, err = i.entityCreator.CreateEntity(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-10-14 16:52:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
persist := false
|
|
|
|
// If the request was not forwarded, then this is the active node of the
|
|
|
|
// primary. Create the entity here itself.
|
|
|
|
if entity == nil {
|
|
|
|
persist = true
|
|
|
|
entity = new(identity.Entity)
|
|
|
|
err = i.sanitizeEntity(ctx, entity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
}
|
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
for _, currentAlias := range entity.Aliases {
|
|
|
|
if currentAlias.MountAccessor == mountAccessor {
|
|
|
|
return logical.ErrorResponse("Alias already exists for requested entity and mount accessor"), nil
|
|
|
|
}
|
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
var alias *identity.Alias
|
|
|
|
switch local {
|
|
|
|
case true:
|
|
|
|
alias, err = i.processLocalAlias(ctx, &logical.Alias{
|
|
|
|
MountAccessor: mountAccessor,
|
|
|
|
Name: name,
|
|
|
|
Local: local,
|
|
|
|
CustomMetadata: customMetadata,
|
|
|
|
}, entity, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
alias = &identity.Alias{
|
|
|
|
MountAccessor: mountAccessor,
|
|
|
|
Name: name,
|
|
|
|
CustomMetadata: customMetadata,
|
|
|
|
CanonicalID: entity.ID,
|
|
|
|
}
|
|
|
|
err = i.sanitizeAlias(ctx, alias)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
entity.UpsertAlias(alias)
|
|
|
|
persist = true
|
2019-06-14 16:53:00 +00:00
|
|
|
}
|
2018-05-25 18:34:24 +00:00
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// Index entity and its aliases in MemDB and persist entity along with
|
|
|
|
// aliases in storage.
|
2021-10-15 19:20:00 +00:00
|
|
|
if err := i.upsertEntity(ctx, entity, nil, persist); err != nil {
|
2019-06-14 16:53:00 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// Return ID of both alias and entity
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"id": alias.ID,
|
|
|
|
"canonical_id": entity.ID,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name, mountAccessor string, alias *identity.Alias, customMetadata map[string]string) (*logical.Response, error) {
|
2019-06-14 16:53:00 +00:00
|
|
|
if name == alias.Name &&
|
|
|
|
mountAccessor == alias.MountAccessor &&
|
2021-09-17 18:03:47 +00:00
|
|
|
(canonicalID == alias.CanonicalID || canonicalID == "") && (strutil.EqualStringMaps(customMetadata, alias.CustomMetadata)) {
|
2019-06-14 16:53:00 +00:00
|
|
|
// Nothing to do; return nil to be idempotent
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
alias.LastUpdateTime = ptypes.TimestampNow()
|
|
|
|
|
2021-10-14 16:52:07 +00:00
|
|
|
// Get our current entity, which may be the same as the new one if the
|
|
|
|
// canonical ID hasn't changed
|
|
|
|
currentEntity, err := i.MemDBEntityByAliasID(alias.ID, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if currentEntity == nil {
|
|
|
|
return logical.ErrorResponse("given alias is not associated with an entity"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if currentEntity.NamespaceID != alias.NamespaceID {
|
|
|
|
return logical.ErrorResponse("alias and entity do not belong to the same namespace"), logical.ErrPermissionDenied
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the accessor is being changed but the entity is not, check if the entity
|
|
|
|
// already has an alias corresponding to the new accessor
|
|
|
|
if mountAccessor != alias.MountAccessor && (canonicalID == "" || canonicalID == alias.CanonicalID) {
|
|
|
|
for _, currentAlias := range currentEntity.Aliases {
|
|
|
|
if currentAlias.MountAccessor == mountAccessor {
|
|
|
|
return logical.ErrorResponse("Alias cannot be updated as the entity already has an alias for the given 'mount_accessor' "), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
// If we're changing one or the other or both of these, make sure that
|
|
|
|
// there isn't a matching alias already, and make sure it's in the same
|
|
|
|
// namespace.
|
2021-09-17 18:03:47 +00:00
|
|
|
if name != alias.Name || mountAccessor != alias.MountAccessor || !strutil.EqualStringMaps(customMetadata, alias.CustomMetadata) {
|
2019-06-14 16:53:00 +00:00
|
|
|
// Check here to see if such an alias already exists, if so bail
|
2021-08-30 19:31:11 +00:00
|
|
|
mountEntry := i.router.MatchingMountByAccessor(mountAccessor)
|
2019-06-14 16:53:00 +00:00
|
|
|
if mountEntry == nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", mountAccessor)), nil
|
|
|
|
}
|
|
|
|
if mountEntry.NamespaceID != alias.NamespaceID {
|
|
|
|
return logical.ErrorResponse("given mount accessor is not in the same namespace as the existing alias"), logical.ErrPermissionDenied
|
|
|
|
}
|
|
|
|
|
|
|
|
existingAlias, err := i.MemDBAliasByFactors(mountAccessor, name, false, false)
|
2018-07-25 02:01:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-09-17 18:03:47 +00:00
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// Bail unless it's just a case change
|
2021-09-21 12:19:44 +00:00
|
|
|
if existingAlias != nil && existingAlias.ID != alias.ID {
|
2019-06-14 16:53:00 +00:00
|
|
|
return logical.ErrorResponse("alias with combination of mount accessor and name already exists"), nil
|
2018-10-19 19:47:26 +00:00
|
|
|
}
|
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// Update the values in the alias
|
|
|
|
alias.Name = name
|
|
|
|
alias.MountAccessor = mountAccessor
|
2021-09-17 18:03:47 +00:00
|
|
|
alias.CustomMetadata = customMetadata
|
2019-06-14 16:53:00 +00:00
|
|
|
}
|
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
mountValidationResp := i.router.ValidateMountByAccessor(alias.MountAccessor)
|
|
|
|
if mountValidationResp == nil {
|
|
|
|
return nil, fmt.Errorf("invalid mount accessor %q", alias.MountAccessor)
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
newEntity := currentEntity
|
|
|
|
if canonicalID != "" && canonicalID != alias.CanonicalID {
|
2021-10-15 19:20:00 +00:00
|
|
|
// Don't allow moving local aliases between entities.
|
|
|
|
if mountValidationResp.MountLocal {
|
|
|
|
return logical.ErrorResponse("local aliases can't be moved between entities"), nil
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
newEntity, err = i.MemDBEntityByID(canonicalID, true)
|
|
|
|
if err != nil {
|
2018-07-25 02:01:58 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
if newEntity == nil {
|
|
|
|
return logical.ErrorResponse("given 'canonical_id' is not associated with an entity"), nil
|
|
|
|
}
|
|
|
|
if newEntity.NamespaceID != alias.NamespaceID {
|
|
|
|
return logical.ErrorResponse("given 'canonical_id' associated with entity in a different namespace from the alias"), logical.ErrPermissionDenied
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2021-10-14 16:52:07 +00:00
|
|
|
// Check if the entity the alias is being updated to, already has an alias for the mount
|
|
|
|
for _, alias := range newEntity.Aliases {
|
|
|
|
if alias.MountAccessor == mountAccessor {
|
|
|
|
return logical.ErrorResponse("Alias cannot be updated as the given entity already has an alias for this mount "), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-21 12:19:44 +00:00
|
|
|
// Update the canonical ID value and move it from the current entity to the new one
|
2019-06-14 16:53:00 +00:00
|
|
|
alias.CanonicalID = newEntity.ID
|
|
|
|
newEntity.Aliases = append(newEntity.Aliases, alias)
|
|
|
|
for aliasIndex, item := range currentEntity.Aliases {
|
|
|
|
if item.ID == alias.ID {
|
|
|
|
currentEntity.Aliases = append(currentEntity.Aliases[:aliasIndex], currentEntity.Aliases[aliasIndex+1:]...)
|
|
|
|
break
|
|
|
|
}
|
2018-07-25 02:01:58 +00:00
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
} else {
|
|
|
|
// If it's not moving we still need to update it in the existing
|
|
|
|
// entity's aliases
|
|
|
|
for aliasIndex, item := range currentEntity.Aliases {
|
|
|
|
if item.ID == alias.ID {
|
|
|
|
currentEntity.Aliases[aliasIndex] = alias
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// newEntity will be pointing to the same entity; set currentEntity nil
|
|
|
|
// so the upsertCall gets nil for the previous entity as we're only
|
|
|
|
// changing one.
|
|
|
|
currentEntity = nil
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
if mountValidationResp.MountLocal {
|
|
|
|
alias, err = i.processLocalAlias(ctx, &logical.Alias{
|
|
|
|
MountAccessor: mountAccessor,
|
|
|
|
Name: name,
|
|
|
|
Local: mountValidationResp.MountLocal,
|
|
|
|
CustomMetadata: customMetadata,
|
|
|
|
}, newEntity, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"id": alias.ID,
|
|
|
|
"canonical_id": newEntity.ID,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:53:00 +00:00
|
|
|
// 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
|
|
|
|
// and persisted in storage as well.
|
|
|
|
if err := i.upsertEntity(ctx, newEntity, currentEntity, true); err != nil {
|
|
|
|
return nil, err
|
2017-10-11 17:21:20 +00:00
|
|
|
}
|
2019-06-14 16:53:00 +00:00
|
|
|
|
|
|
|
// Return ID of both alias and entity
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"id": alias.ID,
|
|
|
|
"canonical_id": newEntity.ID,
|
|
|
|
},
|
|
|
|
}, nil
|
2017-10-11 17:21:20 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 20:05:48 +00:00
|
|
|
// pathAliasIDRead returns the properties of an alias for a given
|
2017-10-11 17:21:20 +00:00
|
|
|
// alias ID
|
2018-01-08 18:31:38 +00:00
|
|
|
func (i *IdentityStore) pathAliasIDRead() framework.OperationFunc {
|
|
|
|
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
|
|
aliasID := d.Get("id").(string)
|
|
|
|
if aliasID == "" {
|
|
|
|
return logical.ErrorResponse("missing alias id"), nil
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
alias, err := i.MemDBAliasByID(aliasID, false, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
return i.handleAliasReadCommon(ctx, alias)
|
2018-01-08 18:31:38 +00:00
|
|
|
}
|
2017-11-02 20:05:48 +00:00
|
|
|
}
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
func (i *IdentityStore) handleAliasReadCommon(ctx context.Context, alias *identity.Alias) (*logical.Response, error) {
|
2017-10-11 17:21:20 +00:00
|
|
|
if alias == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if ns.ID != alias.NamespaceID {
|
2019-06-14 16:53:00 +00:00
|
|
|
return logical.ErrorResponse("alias and request are in different namespaces"), logical.ErrPermissionDenied
|
2018-09-18 03:03:00 +00:00
|
|
|
}
|
|
|
|
|
2017-10-11 17:21:20 +00:00
|
|
|
respData := map[string]interface{}{}
|
|
|
|
respData["id"] = alias.ID
|
2017-11-02 20:05:48 +00:00
|
|
|
respData["canonical_id"] = alias.CanonicalID
|
2017-10-11 17:21:20 +00:00
|
|
|
respData["mount_accessor"] = alias.MountAccessor
|
|
|
|
respData["metadata"] = alias.Metadata
|
2021-09-17 18:03:47 +00:00
|
|
|
respData["custom_metadata"] = alias.CustomMetadata
|
2017-10-11 17:21:20 +00:00
|
|
|
respData["name"] = alias.Name
|
2017-11-02 20:05:48 +00:00
|
|
|
respData["merged_from_canonical_ids"] = alias.MergedFromCanonicalIDs
|
2019-06-14 16:53:00 +00:00
|
|
|
respData["namespace_id"] = alias.NamespaceID
|
2021-10-15 19:20:00 +00:00
|
|
|
respData["local"] = alias.Local
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2021-08-30 19:31:11 +00:00
|
|
|
if mountValidationResp := i.router.ValidateMountByAccessor(alias.MountAccessor); mountValidationResp != nil {
|
2018-05-25 18:34:24 +00:00
|
|
|
respData["mount_path"] = mountValidationResp.MountPath
|
|
|
|
respData["mount_type"] = mountValidationResp.MountType
|
|
|
|
}
|
|
|
|
|
2017-10-11 17:21:20 +00:00
|
|
|
// Convert protobuf timestamp into RFC3339 format
|
|
|
|
respData["creation_time"] = ptypes.TimestampString(alias.CreationTime)
|
|
|
|
respData["last_update_time"] = ptypes.TimestampString(alias.LastUpdateTime)
|
|
|
|
|
|
|
|
return &logical.Response{
|
|
|
|
Data: respData,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-11-02 20:05:48 +00:00
|
|
|
// pathAliasIDDelete deletes the alias for a given alias ID
|
2018-01-08 18:31:38 +00:00
|
|
|
func (i *IdentityStore) pathAliasIDDelete() framework.OperationFunc {
|
|
|
|
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
|
|
aliasID := d.Get("id").(string)
|
|
|
|
if aliasID == "" {
|
|
|
|
return logical.ErrorResponse("missing alias ID"), nil
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
2018-07-25 02:01:58 +00:00
|
|
|
i.lock.Lock()
|
|
|
|
defer i.lock.Unlock()
|
2018-06-24 11:45:53 +00:00
|
|
|
|
|
|
|
// Create a MemDB transaction to delete entity
|
|
|
|
txn := i.db.Txn(true)
|
|
|
|
defer txn.Abort()
|
|
|
|
|
2018-07-25 02:01:58 +00:00
|
|
|
// Fetch the alias
|
|
|
|
alias, err := i.MemDBAliasByIDInTxn(txn, aliasID, false, false)
|
2018-06-24 11:45:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no alias for the ID, do nothing
|
|
|
|
if alias == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if ns.ID != alias.NamespaceID {
|
2019-06-14 16:53:00 +00:00
|
|
|
return logical.ErrorResponse("request and alias are in different namespaces"), logical.ErrPermissionDenied
|
2018-09-18 03:03:00 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 02:01:58 +00:00
|
|
|
// Fetch the associated entity
|
2018-06-24 11:45:53 +00:00
|
|
|
entity, err := i.MemDBEntityByAliasIDInTxn(txn, alias.ID, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no entity tied to a valid alias, something is wrong
|
|
|
|
if entity == nil {
|
|
|
|
return nil, fmt.Errorf("alias not associated to an entity")
|
|
|
|
}
|
|
|
|
|
|
|
|
aliases := []*identity.Alias{
|
|
|
|
alias,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete alias from the entity object
|
|
|
|
err = i.deleteAliasesInEntityInTxn(txn, entity, aliases)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the entity index in the entities table
|
|
|
|
err = i.MemDBUpsertEntityInTxn(txn, entity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
switch alias.Local {
|
|
|
|
case true:
|
|
|
|
localAliases, err := i.parseLocalAliases(entity.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-06-24 11:45:53 +00:00
|
|
|
|
2021-10-15 19:20:00 +00:00
|
|
|
if localAliases == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, item := range localAliases.Aliases {
|
|
|
|
if item.ID == alias.ID {
|
|
|
|
localAliases.Aliases = append(localAliases.Aliases[:i], localAliases.Aliases[i+1:]...)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
marshaledAliases, err := ptypes.MarshalAny(localAliases)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := i.localAliasPacker.PutItem(ctx, &storagepacker.Item{
|
|
|
|
ID: entity.ID,
|
|
|
|
Message: marshaledAliases,
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if err := i.persistEntity(ctx, entity); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-06-24 11:45:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Committing the transaction *after* successfully updating entity in
|
|
|
|
// storage
|
|
|
|
txn.Commit()
|
|
|
|
|
|
|
|
return nil, nil
|
2018-01-08 18:31:38 +00:00
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// pathAliasIDList lists the IDs of all the valid aliases in the identity
|
|
|
|
// store
|
2018-01-08 18:31:38 +00:00
|
|
|
func (i *IdentityStore) pathAliasIDList() framework.OperationFunc {
|
|
|
|
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2018-09-18 03:03:00 +00:00
|
|
|
return i.handleAliasListCommon(ctx, false)
|
2018-01-08 18:31:38 +00:00
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var aliasHelp = map[string][2]string{
|
|
|
|
"alias": {
|
2017-11-02 20:05:48 +00:00
|
|
|
"Create a new alias.",
|
2017-10-11 17:21:20 +00:00
|
|
|
"",
|
|
|
|
},
|
|
|
|
"alias-id": {
|
2017-11-02 20:05:48 +00:00
|
|
|
"Update, read or delete an alias ID.",
|
2017-10-11 17:21:20 +00:00
|
|
|
"",
|
|
|
|
},
|
|
|
|
"alias-id-list": {
|
2018-04-17 17:54:04 +00:00
|
|
|
"List all the alias IDs.",
|
2017-10-11 17:21:20 +00:00
|
|
|
"",
|
|
|
|
},
|
|
|
|
}
|