1package vault
2
3import (
4	"context"
5	"fmt"
6	"strings"
7
8	"github.com/golang/protobuf/ptypes"
9	"github.com/hashicorp/vault/helper/identity"
10	"github.com/hashicorp/vault/helper/namespace"
11	"github.com/hashicorp/vault/helper/storagepacker"
12	"github.com/hashicorp/vault/logical"
13	"github.com/hashicorp/vault/logical/framework"
14)
15
16// aliasPaths returns the API endpoints to operate on aliases.
17// Following are the paths supported:
18// entity-alias - To register/modify an alias
19// entity-alias/id - To read, modify, delete and list aliases based on their ID
20func aliasPaths(i *IdentityStore) []*framework.Path {
21	return []*framework.Path{
22		{
23			Pattern: "entity-alias$",
24			Fields: map[string]*framework.FieldSchema{
25				"id": {
26					Type:        framework.TypeString,
27					Description: "ID of the entity alias. If set, updates the corresponding entity alias.",
28				},
29				// entity_id is deprecated in favor of canonical_id
30				"entity_id": {
31					Type: framework.TypeString,
32					Description: `Entity ID to which this alias belongs.
33This field is deprecated, use canonical_id.`,
34				},
35				"canonical_id": {
36					Type:        framework.TypeString,
37					Description: "Entity ID to which this alias belongs",
38				},
39				"mount_accessor": {
40					Type:        framework.TypeString,
41					Description: "Mount accessor to which this alias belongs to; unused for a modify",
42				},
43				"name": {
44					Type:        framework.TypeString,
45					Description: "Name of the alias; unused for a modify",
46				},
47			},
48			Callbacks: map[logical.Operation]framework.OperationFunc{
49				logical.UpdateOperation: i.handleAliasUpdateCommon(),
50			},
51
52			HelpSynopsis:    strings.TrimSpace(aliasHelp["alias"][0]),
53			HelpDescription: strings.TrimSpace(aliasHelp["alias"][1]),
54		},
55		{
56			Pattern: "entity-alias/id/" + framework.GenericNameRegex("id"),
57			Fields: map[string]*framework.FieldSchema{
58				"id": {
59					Type:        framework.TypeString,
60					Description: "ID of the alias",
61				},
62				// entity_id is deprecated
63				"entity_id": {
64					Type: framework.TypeString,
65					Description: `Entity ID to which this alias belongs to.
66This field is deprecated, use canonical_id.`,
67				},
68				"canonical_id": {
69					Type:        framework.TypeString,
70					Description: "Entity ID to which this alias should be tied to",
71				},
72				"mount_accessor": {
73					Type:        framework.TypeString,
74					Description: "(Unused)",
75				},
76				"name": {
77					Type:        framework.TypeString,
78					Description: "(Unused)",
79				},
80			},
81			Callbacks: map[logical.Operation]framework.OperationFunc{
82				logical.UpdateOperation: i.handleAliasUpdateCommon(),
83				logical.ReadOperation:   i.pathAliasIDRead(),
84				logical.DeleteOperation: i.pathAliasIDDelete(),
85			},
86
87			HelpSynopsis:    strings.TrimSpace(aliasHelp["alias-id"][0]),
88			HelpDescription: strings.TrimSpace(aliasHelp["alias-id"][1]),
89		},
90		{
91			Pattern: "entity-alias/id/?$",
92			Callbacks: map[logical.Operation]framework.OperationFunc{
93				logical.ListOperation: i.pathAliasIDList(),
94			},
95
96			HelpSynopsis:    strings.TrimSpace(aliasHelp["alias-id-list"][0]),
97			HelpDescription: strings.TrimSpace(aliasHelp["alias-id-list"][1]),
98		},
99	}
100}
101
102// handleAliasUpdateCommon is used to update an alias
103func (i *IdentityStore) handleAliasUpdateCommon() framework.OperationFunc {
104	return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
105		var err error
106		var alias *identity.Alias
107		var entity *identity.Entity
108		var previousEntity *identity.Entity
109
110		i.lock.Lock()
111		defer i.lock.Unlock()
112
113		// Check for update or create
114		aliasID := d.Get("id").(string)
115		if aliasID != "" {
116			alias, err = i.MemDBAliasByID(aliasID, true, false)
117			if err != nil {
118				return nil, err
119			}
120			if alias == nil {
121				return logical.ErrorResponse("invalid alias id"), nil
122			}
123		} else {
124			alias = &identity.Alias{}
125		}
126
127		// Get entity id
128		canonicalID := d.Get("canonical_id").(string)
129		if canonicalID == "" {
130			// For backwards compatibility
131			canonicalID = d.Get("entity_id").(string)
132		}
133
134		// Get alias name
135		if aliasName := d.Get("name").(string); aliasName == "" {
136			if alias.Name == "" {
137				return logical.ErrorResponse("missing alias name"), nil
138			}
139		} else {
140			alias.Name = aliasName
141		}
142
143		// Get mount accessor
144		if mountAccessor := d.Get("mount_accessor").(string); mountAccessor == "" {
145			if alias.MountAccessor == "" {
146				return logical.ErrorResponse("missing mount_accessor"), nil
147			}
148		} else {
149			alias.MountAccessor = mountAccessor
150		}
151
152		mountValidationResp := i.core.router.validateMountByAccessor(alias.MountAccessor)
153		if mountValidationResp == nil {
154			return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", alias.MountAccessor)), nil
155		}
156		if mountValidationResp.MountLocal {
157			return logical.ErrorResponse(fmt.Sprintf("mount_accessor %q is of a local mount", alias.MountAccessor)), nil
158		}
159
160		// Verify that the combination of alias name and mount is not
161		// already tied to a different alias
162		aliasByFactors, err := i.MemDBAliasByFactors(mountValidationResp.MountAccessor, alias.Name, false, false)
163		if err != nil {
164			return nil, err
165		}
166		if aliasByFactors != nil {
167			// If it's a create we won't have an alias ID so this will correctly
168			// bail. If it's an update alias will be the same as aliasbyfactors so
169			// we don't need to transfer any info over
170			if aliasByFactors.ID != alias.ID {
171				return logical.ErrorResponse("combination of mount and alias name is already in use"), nil
172			}
173
174			// Fetch the entity to which the alias is tied. We don't need to append
175			// here, so the only further checking is whether the canonical ID is
176			// different
177			entity, err = i.MemDBEntityByAliasID(alias.ID, true)
178			if err != nil {
179				return nil, err
180			}
181			if entity == nil {
182				return nil, fmt.Errorf("existing alias is not associated with an entity")
183			}
184		} else if alias.ID != "" {
185			// This is an update, not a create; if we have an associated entity
186			// already, load it
187			entity, err = i.MemDBEntityByAliasID(alias.ID, true)
188			if err != nil {
189				return nil, err
190			}
191		}
192
193		resp := &logical.Response{}
194
195		// If we found an existing alias we won't hit this condition because
196		// canonicalID being empty will result in nil being returned in the block
197		// above, so in this case we know that creating a new entity is the right
198		// thing.
199		if canonicalID == "" {
200			entity = &identity.Entity{
201				Aliases: []*identity.Alias{
202					alias,
203				},
204			}
205		} else {
206			// If we can look up by the given canonical ID, see if this is a
207			// transfer; otherwise if we found no previous entity but we find one
208			// here, use it.
209			canonicalEntity, err := i.MemDBEntityByID(canonicalID, true)
210			if err != nil {
211				return nil, err
212			}
213			if canonicalEntity == nil {
214				return logical.ErrorResponse("invalid canonical ID"), nil
215			}
216			if entity == nil {
217				// If entity is nil, we didn't find a previous alias from factors,
218				// so append to this entity
219				entity = canonicalEntity
220				entity.Aliases = append(entity.Aliases, alias)
221			} else if entity.ID != canonicalEntity.ID {
222				// In this case we found an entity from alias factors or given
223				// alias ID but it's not the same, so it's a migration
224				previousEntity = entity
225				entity = canonicalEntity
226
227				for aliasIndex, item := range previousEntity.Aliases {
228					if item.ID == alias.ID {
229						previousEntity.Aliases = append(previousEntity.Aliases[:aliasIndex], previousEntity.Aliases[aliasIndex+1:]...)
230						break
231					}
232				}
233
234				entity.Aliases = append(entity.Aliases, alias)
235				resp.AddWarning(fmt.Sprintf("alias is being transferred from entity %q to %q", previousEntity.ID, entity.ID))
236			}
237		}
238
239		// ID creation and other validations; This is more useful for new entities
240		// and may not perform anything for the existing entities. Placing the
241		// check here to make the flow common for both new and existing entities.
242		err = i.sanitizeEntity(ctx, entity)
243		if err != nil {
244			return nil, err
245		}
246
247		// Explicitly set to empty as in the past we incorrectly saved it
248		alias.MountPath = ""
249		alias.MountType = ""
250
251		// Set the canonical ID in the alias index. This should be done after
252		// sanitizing entity.
253		alias.CanonicalID = entity.ID
254
255		// ID creation and other validations
256		err = i.sanitizeAlias(ctx, alias)
257		if err != nil {
258			return nil, err
259		}
260
261		for index, item := range entity.Aliases {
262			if item.ID == alias.ID {
263				entity.Aliases[index] = alias
264			}
265		}
266
267		// Index entity and its aliases in MemDB and persist entity along with
268		// aliases in storage. If the alias is being transferred over from
269		// one entity to another, previous entity needs to get refreshed in MemDB
270		// and persisted in storage as well.
271		if err := i.upsertEntity(ctx, entity, previousEntity, true); err != nil {
272			return nil, err
273		}
274
275		// Return ID of both alias and entity
276		resp.Data = map[string]interface{}{
277			"id":           alias.ID,
278			"canonical_id": entity.ID,
279		}
280
281		return resp, nil
282	}
283}
284
285// pathAliasIDRead returns the properties of an alias for a given
286// alias ID
287func (i *IdentityStore) pathAliasIDRead() framework.OperationFunc {
288	return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
289		aliasID := d.Get("id").(string)
290		if aliasID == "" {
291			return logical.ErrorResponse("missing alias id"), nil
292		}
293
294		alias, err := i.MemDBAliasByID(aliasID, false, false)
295		if err != nil {
296			return nil, err
297		}
298
299		return i.handleAliasReadCommon(ctx, alias)
300	}
301}
302
303func (i *IdentityStore) handleAliasReadCommon(ctx context.Context, alias *identity.Alias) (*logical.Response, error) {
304	if alias == nil {
305		return nil, nil
306	}
307
308	ns, err := namespace.FromContext(ctx)
309	if err != nil {
310		return nil, err
311	}
312	if ns.ID != alias.NamespaceID {
313		return nil, nil
314	}
315
316	respData := map[string]interface{}{}
317	respData["id"] = alias.ID
318	respData["canonical_id"] = alias.CanonicalID
319	respData["mount_accessor"] = alias.MountAccessor
320	respData["metadata"] = alias.Metadata
321	respData["name"] = alias.Name
322	respData["merged_from_canonical_ids"] = alias.MergedFromCanonicalIDs
323
324	if mountValidationResp := i.core.router.validateMountByAccessor(alias.MountAccessor); mountValidationResp != nil {
325		respData["mount_path"] = mountValidationResp.MountPath
326		respData["mount_type"] = mountValidationResp.MountType
327	}
328
329	// Convert protobuf timestamp into RFC3339 format
330	respData["creation_time"] = ptypes.TimestampString(alias.CreationTime)
331	respData["last_update_time"] = ptypes.TimestampString(alias.LastUpdateTime)
332
333	return &logical.Response{
334		Data: respData,
335	}, nil
336}
337
338// pathAliasIDDelete deletes the alias for a given alias ID
339func (i *IdentityStore) pathAliasIDDelete() framework.OperationFunc {
340	return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
341		aliasID := d.Get("id").(string)
342		if aliasID == "" {
343			return logical.ErrorResponse("missing alias ID"), nil
344		}
345
346		i.lock.Lock()
347		defer i.lock.Unlock()
348
349		// Create a MemDB transaction to delete entity
350		txn := i.db.Txn(true)
351		defer txn.Abort()
352
353		// Fetch the alias
354		alias, err := i.MemDBAliasByIDInTxn(txn, aliasID, false, false)
355		if err != nil {
356			return nil, err
357		}
358
359		// If there is no alias for the ID, do nothing
360		if alias == nil {
361			return nil, nil
362		}
363
364		ns, err := namespace.FromContext(ctx)
365		if err != nil {
366			return nil, err
367		}
368		if ns.ID != alias.NamespaceID {
369			return nil, logical.ErrUnsupportedPath
370		}
371
372		// Fetch the associated entity
373		entity, err := i.MemDBEntityByAliasIDInTxn(txn, alias.ID, true)
374		if err != nil {
375			return nil, err
376		}
377
378		// If there is no entity tied to a valid alias, something is wrong
379		if entity == nil {
380			return nil, fmt.Errorf("alias not associated to an entity")
381		}
382
383		aliases := []*identity.Alias{
384			alias,
385		}
386
387		// Delete alias from the entity object
388		err = i.deleteAliasesInEntityInTxn(txn, entity, aliases)
389		if err != nil {
390			return nil, err
391		}
392
393		// Update the entity index in the entities table
394		err = i.MemDBUpsertEntityInTxn(txn, entity)
395		if err != nil {
396			return nil, err
397		}
398
399		// Persist the entity object
400		entityAsAny, err := ptypes.MarshalAny(entity)
401		if err != nil {
402			return nil, err
403		}
404		item := &storagepacker.Item{
405			ID:      entity.ID,
406			Message: entityAsAny,
407		}
408
409		err = i.entityPacker.PutItem(item)
410		if err != nil {
411			return nil, err
412		}
413
414		// Committing the transaction *after* successfully updating entity in
415		// storage
416		txn.Commit()
417
418		return nil, nil
419	}
420}
421
422// pathAliasIDList lists the IDs of all the valid aliases in the identity
423// store
424func (i *IdentityStore) pathAliasIDList() framework.OperationFunc {
425	return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
426		return i.handleAliasListCommon(ctx, false)
427	}
428}
429
430var aliasHelp = map[string][2]string{
431	"alias": {
432		"Create a new alias.",
433		"",
434	},
435	"alias-id": {
436		"Update, read or delete an alias ID.",
437		"",
438	},
439	"alias-id-list": {
440		"List all the alias IDs.",
441		"",
442	},
443}
444