1package vault
2
3import (
4	"reflect"
5	"strings"
6	"testing"
7
8	"github.com/hashicorp/vault/helper/identity"
9	"github.com/hashicorp/vault/helper/namespace"
10	"github.com/hashicorp/vault/sdk/logical"
11)
12
13// Issue 5729
14func TestIdentityStore_DuplicateAliases(t *testing.T) {
15	c, _, _ := TestCoreUnsealed(t)
16
17	resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{
18		Path:      "auth",
19		Operation: logical.ReadOperation,
20	})
21	if err != nil || (resp != nil && resp.IsError()) {
22		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
23	}
24
25	tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
26
27	// Create an entity and attach an alias to it
28	resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
29		Path:      "entity-alias",
30		Operation: logical.UpdateOperation,
31		Data: map[string]interface{}{
32			"mount_accessor": tokenMountAccessor,
33			"name":           "testaliasname",
34		},
35	})
36	if err != nil || (resp != nil && resp.IsError()) {
37		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
38	}
39	aliasID := resp.Data["id"].(string)
40
41	// Create another entity without an alias
42	resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
43		Path:      "entity",
44		Operation: logical.UpdateOperation,
45	})
46	if err != nil || (resp != nil && resp.IsError()) {
47		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
48	}
49	entityID2 := resp.Data["id"].(string)
50
51	// Set the second entity ID as the canonical ID for the previous alias,
52	// initiating an alias transfer
53	resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
54		Path:      "entity-alias/id/" + aliasID,
55		Operation: logical.UpdateOperation,
56		Data: map[string]interface{}{
57			"canonical_id": entityID2,
58		},
59	})
60	if err != nil || (resp != nil && resp.IsError()) {
61		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
62	}
63
64	// Read the new entity
65	resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
66		Path:      "entity/id/" + entityID2,
67		Operation: logical.ReadOperation,
68	})
69	if err != nil || (resp != nil && resp.IsError()) {
70		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
71	}
72
73	// Ensure that there is only one alias
74	aliases := resp.Data["aliases"].([]interface{})
75	if len(aliases) != 1 {
76		t.Fatalf("bad: length of aliases; expected: %d, actual: %d", 1, len(aliases))
77	}
78
79	// Ensure that no merging activity has taken place
80	if len(aliases[0].(map[string]interface{})["merged_from_canonical_ids"].([]string)) != 0 {
81		t.Fatalf("expected no merging to take place")
82	}
83}
84
85func TestIdentityStore_CaseInsensitiveEntityAliasName(t *testing.T) {
86	ctx := namespace.RootContext(nil)
87	i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
88
89	// Create an entity
90	resp, err := i.HandleRequest(ctx, &logical.Request{
91		Path:      "entity",
92		Operation: logical.UpdateOperation,
93	})
94	if err != nil || (resp != nil && resp.IsError()) {
95		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
96	}
97	entityID := resp.Data["id"].(string)
98
99	testAliasName := "testAliasName"
100	// Create a case sensitive alias name
101	resp, err = i.HandleRequest(ctx, &logical.Request{
102		Path:      "entity-alias",
103		Operation: logical.UpdateOperation,
104		Data: map[string]interface{}{
105			"mount_accessor": accessor,
106			"canonical_id":   entityID,
107			"name":           testAliasName,
108		},
109	})
110	if err != nil || (resp != nil && resp.IsError()) {
111		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
112	}
113	aliasID := resp.Data["id"].(string)
114
115	// Ensure that reading the alias returns case sensitive alias name
116	resp, err = i.HandleRequest(ctx, &logical.Request{
117		Path:      "entity-alias/id/" + aliasID,
118		Operation: logical.ReadOperation,
119	})
120	if err != nil || (resp != nil && resp.IsError()) {
121		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
122	}
123	aliasName := resp.Data["name"].(string)
124	if aliasName != testAliasName {
125		t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
126	}
127
128	// Overwrite the alias using lower cased alias name. This shouldn't error.
129	resp, err = i.HandleRequest(ctx, &logical.Request{
130		Path:      "entity-alias/id/" + aliasID,
131		Operation: logical.UpdateOperation,
132		Data: map[string]interface{}{
133			"mount_accessor": accessor,
134			"canonical_id":   entityID,
135			"name":           strings.ToLower(testAliasName),
136		},
137	})
138	if err != nil || (resp != nil && resp.IsError()) {
139		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
140	}
141
142	// Ensure that reading the alias returns lower cased alias name
143	resp, err = i.HandleRequest(ctx, &logical.Request{
144		Path:      "entity-alias/id/" + aliasID,
145		Operation: logical.ReadOperation,
146	})
147	if err != nil || (resp != nil && resp.IsError()) {
148		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
149	}
150	aliasName = resp.Data["name"].(string)
151	if aliasName != strings.ToLower(testAliasName) {
152		t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
153	}
154
155	// Ensure that there is one entity alias
156	resp, err = i.HandleRequest(ctx, &logical.Request{
157		Path:      "entity-alias/id",
158		Operation: logical.ListOperation,
159	})
160	if err != nil || (resp != nil && resp.IsError()) {
161		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
162	}
163	if len(resp.Data["keys"].([]string)) != 1 {
164		t.Fatalf("bad length of entity aliases; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
165	}
166}
167
168// This test is required because MemDB does not take care of ensuring
169// uniqueness of indexes that are marked unique.
170func TestIdentityStore_AliasSameAliasNames(t *testing.T) {
171	var err error
172	var resp *logical.Response
173
174	ctx := namespace.RootContext(nil)
175	is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
176
177	aliasData := map[string]interface{}{
178		"name":           "testaliasname",
179		"mount_accessor": githubAccessor,
180	}
181
182	aliasReq := &logical.Request{
183		Operation: logical.UpdateOperation,
184		Path:      "entity-alias",
185		Data:      aliasData,
186	}
187
188	// Register an alias
189	resp, err = is.HandleRequest(ctx, aliasReq)
190	if err != nil || (resp != nil && resp.IsError()) {
191		t.Fatalf("err:%v resp:%#v", err, resp)
192	}
193
194	// Register another alias with same name
195	resp, err = is.HandleRequest(ctx, aliasReq)
196	if err != nil {
197		t.Fatal(err)
198	}
199	if resp != nil {
200		t.Fatalf("expected no response since this modification should be idempotent")
201	}
202}
203
204func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
205	var err error
206
207	ctx := namespace.RootContext(nil)
208	is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
209	if is == nil {
210		t.Fatal("failed to create test identity store")
211	}
212
213	validateMountResp := is.core.router.validateMountByAccessor(githubAccessor)
214	if validateMountResp == nil {
215		t.Fatal("failed to validate github auth mount")
216	}
217
218	entity := &identity.Entity{
219		ID:   "testentityid",
220		Name: "testentityname",
221	}
222
223	entity.BucketKey = is.entityPacker.BucketKey(entity.ID)
224
225	txn := is.db.Txn(true)
226	defer txn.Abort()
227	err = is.MemDBUpsertEntityInTxn(txn, entity)
228	if err != nil {
229		t.Fatal(err)
230	}
231	txn.Commit()
232
233	alias := &identity.Alias{
234		CanonicalID:   entity.ID,
235		ID:            "testaliasid",
236		MountAccessor: githubAccessor,
237		MountType:     validateMountResp.MountType,
238		Name:          "testaliasname",
239		Metadata: map[string]string{
240			"testkey1": "testmetadatavalue1",
241			"testkey2": "testmetadatavalue2",
242		},
243	}
244
245	txn = is.db.Txn(true)
246	defer txn.Abort()
247	err = is.MemDBUpsertAliasInTxn(txn, alias, false)
248	if err != nil {
249		t.Fatal(err)
250	}
251	txn.Commit()
252
253	aliasFetched, err := is.MemDBAliasByID("testaliasid", false, false)
254	if err != nil {
255		t.Fatal(err)
256	}
257
258	if !reflect.DeepEqual(alias, aliasFetched) {
259		t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
260	}
261
262	aliasFetched, err = is.MemDBAliasByFactors(validateMountResp.MountAccessor, "testaliasname", false, false)
263	if err != nil {
264		t.Fatal(err)
265	}
266
267	if !reflect.DeepEqual(alias, aliasFetched) {
268		t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
269	}
270
271	alias2 := &identity.Alias{
272		CanonicalID:   entity.ID,
273		ID:            "testaliasid2",
274		MountAccessor: validateMountResp.MountAccessor,
275		MountType:     validateMountResp.MountType,
276		Name:          "testaliasname2",
277		Metadata: map[string]string{
278			"testkey1": "testmetadatavalue1",
279			"testkey3": "testmetadatavalue3",
280		},
281	}
282
283	txn = is.db.Txn(true)
284	defer txn.Abort()
285	err = is.MemDBUpsertAliasInTxn(txn, alias2, false)
286	if err != nil {
287		t.Fatal(err)
288	}
289	err = is.MemDBDeleteAliasByIDInTxn(txn, "testaliasid", false)
290	if err != nil {
291		t.Fatal(err)
292	}
293	txn.Commit()
294
295	aliasFetched, err = is.MemDBAliasByID("testaliasid", false, false)
296	if err != nil {
297		t.Fatal(err)
298	}
299
300	if aliasFetched != nil {
301		t.Fatalf("expected a nil alias")
302	}
303}
304
305func TestIdentityStore_AliasRegister(t *testing.T) {
306	var err error
307	var resp *logical.Response
308
309	ctx := namespace.RootContext(nil)
310	is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
311
312	if is == nil {
313		t.Fatal("failed to create test alias store")
314	}
315
316	aliasData := map[string]interface{}{
317		"name":           "testaliasname",
318		"mount_accessor": githubAccessor,
319		"metadata":       []string{"organization=hashicorp", "team=vault"},
320	}
321
322	aliasReq := &logical.Request{
323		Operation: logical.UpdateOperation,
324		Path:      "entity-alias",
325		Data:      aliasData,
326	}
327
328	// Register the alias
329	resp, err = is.HandleRequest(ctx, aliasReq)
330	if err != nil || (resp != nil && resp.IsError()) {
331		t.Fatalf("err:%v resp:%#v", err, resp)
332	}
333
334	idRaw, ok := resp.Data["id"]
335	if !ok {
336		t.Fatalf("alias id not present in alias register response")
337	}
338
339	id := idRaw.(string)
340	if id == "" {
341		t.Fatalf("invalid alias id in alias register response")
342	}
343
344	entityIDRaw, ok := resp.Data["canonical_id"]
345	if !ok {
346		t.Fatalf("entity id not present in alias register response")
347	}
348
349	entityID := entityIDRaw.(string)
350	if entityID == "" {
351		t.Fatalf("invalid entity id in alias register response")
352	}
353}
354
355func TestIdentityStore_AliasUpdate(t *testing.T) {
356	var err error
357	var resp *logical.Response
358	ctx := namespace.RootContext(nil)
359	is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
360
361	aliasData := map[string]interface{}{
362		"name":           "testaliasname",
363		"mount_accessor": githubAccessor,
364	}
365
366	aliasReq := &logical.Request{
367		Operation: logical.UpdateOperation,
368		Path:      "entity-alias",
369		Data:      aliasData,
370	}
371
372	// This will create an alias and a corresponding entity
373	resp, err = is.HandleRequest(ctx, aliasReq)
374	if err != nil || (resp != nil && resp.IsError()) {
375		t.Fatalf("err:%v resp:%#v", err, resp)
376	}
377	aliasID := resp.Data["id"].(string)
378
379	updateData := map[string]interface{}{
380		"name":           "updatedaliasname",
381		"mount_accessor": githubAccessor,
382	}
383
384	aliasReq.Data = updateData
385	aliasReq.Path = "entity-alias/id/" + aliasID
386	resp, err = is.HandleRequest(ctx, aliasReq)
387	if err != nil || (resp != nil && resp.IsError()) {
388		t.Fatalf("err:%v resp:%#v", err, resp)
389	}
390
391	aliasReq.Operation = logical.ReadOperation
392	resp, err = is.HandleRequest(ctx, aliasReq)
393	if err != nil || (resp != nil && resp.IsError()) {
394		t.Fatalf("err:%v resp:%#v", err, resp)
395	}
396
397	if resp.Data["name"] != "updatedaliasname" {
398		t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data)
399	}
400}
401
402func TestIdentityStore_AliasUpdate_ByID(t *testing.T) {
403	var err error
404	var resp *logical.Response
405	ctx := namespace.RootContext(nil)
406	is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
407
408	updateData := map[string]interface{}{
409		"name":           "updatedaliasname",
410		"mount_accessor": githubAccessor,
411	}
412
413	updateReq := &logical.Request{
414		Operation: logical.UpdateOperation,
415		Path:      "entity-alias/id/invalidaliasid",
416		Data:      updateData,
417	}
418
419	// Try to update an non-existent alias
420	resp, err = is.HandleRequest(ctx, updateReq)
421	if err != nil {
422		t.Fatal(err)
423	}
424	if resp == nil || !resp.IsError() {
425		t.Fatalf("expected an error due to invalid alias id")
426	}
427
428	registerData := map[string]interface{}{
429		"name":           "testaliasname",
430		"mount_accessor": githubAccessor,
431	}
432
433	registerReq := &logical.Request{
434		Operation: logical.UpdateOperation,
435		Path:      "entity-alias",
436		Data:      registerData,
437	}
438
439	resp, err = is.HandleRequest(ctx, registerReq)
440	if err != nil || (resp != nil && resp.IsError()) {
441		t.Fatalf("err:%v resp:%#v", err, resp)
442	}
443
444	idRaw, ok := resp.Data["id"]
445	if !ok {
446		t.Fatalf("alias id not present in response")
447	}
448	id := idRaw.(string)
449	if id == "" {
450		t.Fatalf("invalid alias id")
451	}
452
453	updateReq.Path = "entity-alias/id/" + id
454	resp, err = is.HandleRequest(ctx, updateReq)
455	if err != nil || (resp != nil && resp.IsError()) {
456		t.Fatalf("err:%v resp:%#v", err, resp)
457	}
458
459	readReq := &logical.Request{
460		Operation: logical.ReadOperation,
461		Path:      updateReq.Path,
462	}
463	resp, err = is.HandleRequest(ctx, readReq)
464	if err != nil || (resp != nil && resp.IsError()) {
465		t.Fatalf("err:%v resp:%#v", err, resp)
466	}
467
468	if resp.Data["name"] != "updatedaliasname" {
469		t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data)
470	}
471
472	delete(registerReq.Data, "name")
473
474	resp, err = is.HandleRequest(ctx, registerReq)
475	if err != nil {
476		t.Fatal(err)
477	}
478	if resp == nil || !resp.IsError() {
479		t.Fatalf("expected error due to missing alias name")
480	}
481
482	registerReq.Data["name"] = "testaliasname"
483	delete(registerReq.Data, "mount_accessor")
484
485	resp, err = is.HandleRequest(ctx, registerReq)
486	if err != nil {
487		t.Fatal(err)
488	}
489	if resp == nil || !resp.IsError() {
490		t.Fatalf("expected error due to missing mount accessor")
491	}
492}
493
494func TestIdentityStore_AliasReadDelete(t *testing.T) {
495	var err error
496	var resp *logical.Response
497
498	ctx := namespace.RootContext(nil)
499	is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
500
501	registerData := map[string]interface{}{
502		"name":           "testaliasname",
503		"mount_accessor": githubAccessor,
504		"metadata":       []string{"organization=hashicorp", "team=vault"},
505	}
506
507	registerReq := &logical.Request{
508		Operation: logical.UpdateOperation,
509		Path:      "entity-alias",
510		Data:      registerData,
511	}
512
513	resp, err = is.HandleRequest(ctx, registerReq)
514	if err != nil || (resp != nil && resp.IsError()) {
515		t.Fatalf("err:%v resp:%#v", err, resp)
516	}
517
518	idRaw, ok := resp.Data["id"]
519	if !ok {
520		t.Fatalf("alias id not present in response")
521	}
522	id := idRaw.(string)
523	if id == "" {
524		t.Fatalf("invalid alias id")
525	}
526
527	// Read it back using alias id
528	aliasReq := &logical.Request{
529		Operation: logical.ReadOperation,
530		Path:      "entity-alias/id/" + id,
531	}
532	resp, err = is.HandleRequest(ctx, aliasReq)
533	if err != nil || (resp != nil && resp.IsError()) {
534		t.Fatalf("err:%v resp:%#v", err, resp)
535	}
536
537	if resp.Data["id"].(string) == "" ||
538		resp.Data["canonical_id"].(string) == "" ||
539		resp.Data["name"].(string) != registerData["name"] ||
540		resp.Data["mount_type"].(string) != "github" {
541		t.Fatalf("bad: alias read response; \nexpected: %#v \nactual: %#v\n", registerData, resp.Data)
542	}
543
544	aliasReq.Operation = logical.DeleteOperation
545	resp, err = is.HandleRequest(ctx, aliasReq)
546	if err != nil || (resp != nil && resp.IsError()) {
547		t.Fatalf("err:%v resp:%#v", err, resp)
548	}
549
550	aliasReq.Operation = logical.ReadOperation
551	resp, err = is.HandleRequest(ctx, aliasReq)
552	if err != nil || (resp != nil && resp.IsError()) {
553		t.Fatalf("err:%v resp:%#v", err, resp)
554	}
555	if resp != nil {
556		t.Fatalf("bad: alias read response; expected: nil, actual: %#v\n", resp)
557	}
558}
559