1package vault
2
3import (
4	"strings"
5	"testing"
6
7	credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
8	credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
9	"github.com/hashicorp/vault/helper/identity"
10	"github.com/hashicorp/vault/helper/namespace"
11	"github.com/hashicorp/vault/sdk/logical"
12	"github.com/kr/pretty"
13)
14
15func TestIdentityStore_CaseInsensitiveGroupAliasName(t *testing.T) {
16	ctx := namespace.RootContext(nil)
17	i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
18
19	// Create a group
20	resp, err := i.HandleRequest(ctx, &logical.Request{
21		Path:      "group",
22		Operation: logical.UpdateOperation,
23		Data: map[string]interface{}{
24			"type": "external",
25		},
26	})
27	if err != nil || (resp != nil && resp.IsError()) {
28		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
29	}
30	groupID := resp.Data["id"].(string)
31
32	testAliasName := "testAliasName"
33
34	// Create a case sensitive alias name
35	resp, err = i.HandleRequest(ctx, &logical.Request{
36		Path:      "group-alias",
37		Operation: logical.UpdateOperation,
38		Data: map[string]interface{}{
39			"mount_accessor": accessor,
40			"canonical_id":   groupID,
41			"name":           testAliasName,
42		},
43	})
44	if err != nil || (resp != nil && resp.IsError()) {
45		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
46	}
47	aliasID := resp.Data["id"].(string)
48
49	// Ensure that reading the alias returns case sensitive alias name
50	resp, err = i.HandleRequest(ctx, &logical.Request{
51		Path:      "group-alias/id/" + aliasID,
52		Operation: logical.ReadOperation,
53	})
54	if err != nil || (resp != nil && resp.IsError()) {
55		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
56	}
57	aliasName := resp.Data["name"].(string)
58	if aliasName != testAliasName {
59		t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
60	}
61
62	// Overwrite the alias using lower cased alias name. This shouldn't error.
63	resp, err = i.HandleRequest(ctx, &logical.Request{
64		Path:      "group-alias/id/" + aliasID,
65		Operation: logical.UpdateOperation,
66		Data: map[string]interface{}{
67			"mount_accessor": accessor,
68			"canonical_id":   groupID,
69			"name":           strings.ToLower(testAliasName),
70		},
71	})
72	if err != nil || (resp != nil && resp.IsError()) {
73		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
74	}
75
76	// Ensure that reading the alias returns lower cased alias name
77	resp, err = i.HandleRequest(ctx, &logical.Request{
78		Path:      "group-alias/id/" + aliasID,
79		Operation: logical.ReadOperation,
80	})
81	if err != nil || (resp != nil && resp.IsError()) {
82		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
83	}
84	aliasName = resp.Data["name"].(string)
85	if aliasName != strings.ToLower(testAliasName) {
86		t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName)
87	}
88}
89
90func TestIdentityStore_EnsureNoDanglingGroupAlias(t *testing.T) {
91	err := AddTestCredentialBackend("userpass", credUserpass.Factory)
92	if err != nil {
93		t.Fatal(err)
94	}
95
96	err = AddTestCredentialBackend("ldap", credLdap.Factory)
97	if err != nil {
98		t.Fatal(err)
99	}
100
101	c, _, _ := TestCoreUnsealed(t)
102
103	ctx := namespace.RootContext(nil)
104
105	userpassMe := &MountEntry{
106		Table:       credentialTableType,
107		Path:        "userpass/",
108		Type:        "userpass",
109		Description: "userpass",
110	}
111	err = c.enableCredential(ctx, userpassMe)
112	if err != nil {
113		t.Fatal(err)
114	}
115
116	ldapMe := &MountEntry{
117		Table:       credentialTableType,
118		Path:        "ldap/",
119		Type:        "ldap",
120		Description: "ldap",
121	}
122	err = c.enableCredential(ctx, ldapMe)
123	if err != nil {
124		t.Fatal(err)
125	}
126
127	// Create a group
128	resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
129		Path:      "group",
130		Operation: logical.UpdateOperation,
131		Data: map[string]interface{}{
132			"type": "external",
133		},
134	})
135	if err != nil || (resp != nil && resp.IsError()) {
136		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
137	}
138	groupID := resp.Data["id"].(string)
139
140	// Add an alias to the group from the userpass auth method
141	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
142		Path:      "group-alias",
143		Operation: logical.UpdateOperation,
144		Data: map[string]interface{}{
145			"name":           "testgroupalias",
146			"mount_accessor": userpassMe.Accessor,
147			"canonical_id":   groupID,
148		},
149	})
150	if err != nil || (resp != nil && resp.IsError()) {
151		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
152	}
153	userpassGroupAliasID := resp.Data["id"].(string)
154
155	// Ensure that the alias is readable
156	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
157		Path:      "group-alias/id/" + userpassGroupAliasID,
158		Operation: logical.ReadOperation,
159	})
160	if err != nil || (resp != nil && resp.IsError()) {
161		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
162	}
163	if resp == nil || resp.Data["id"].(string) != userpassGroupAliasID {
164		t.Fatalf("failed to read userpass group alias")
165	}
166
167	// Attach a different alias to the same group, overriding the previous one
168	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
169		Path:      "group-alias",
170		Operation: logical.UpdateOperation,
171		Data: map[string]interface{}{
172			"name":           "testgroupalias",
173			"mount_accessor": ldapMe.Accessor,
174			"canonical_id":   groupID,
175		},
176	})
177	if err != nil || (resp != nil && resp.IsError()) {
178		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
179	}
180	ldapGroupAliasID := resp.Data["id"].(string)
181
182	// Ensure that the new alias is readable
183	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
184		Path:      "group-alias/id/" + ldapGroupAliasID,
185		Operation: logical.ReadOperation,
186	})
187	if err != nil || (resp != nil && resp.IsError()) {
188		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
189	}
190	if resp == nil || resp.Data["id"].(string) != ldapGroupAliasID {
191		t.Fatalf("failed to read ldap group alias")
192	}
193
194	// Ensure previous alias is gone
195	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
196		Path:      "group-alias/id/" + userpassGroupAliasID,
197		Operation: logical.ReadOperation,
198	})
199	if err != nil || (resp != nil && resp.IsError()) {
200		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
201	}
202	if resp != nil {
203		t.Fatalf("expected a nil response")
204	}
205}
206
207func TestIdentityStore_GroupAliasDeletionOnGroupDeletion(t *testing.T) {
208	var resp *logical.Response
209	var err error
210
211	ctx := namespace.RootContext(nil)
212	i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
213
214	resp, err = i.HandleRequest(ctx, &logical.Request{
215		Path:      "group",
216		Operation: logical.UpdateOperation,
217		Data: map[string]interface{}{
218			"type": "external",
219		},
220	})
221	if err != nil || (resp != nil && resp.IsError()) {
222		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
223	}
224	groupID := resp.Data["id"].(string)
225
226	resp, err = i.HandleRequest(ctx, &logical.Request{
227		Path:      "group-alias",
228		Operation: logical.UpdateOperation,
229		Data: map[string]interface{}{
230			"name":           "testgroupalias",
231			"mount_accessor": accessor,
232			"canonical_id":   groupID,
233		},
234	})
235	if err != nil || (resp != nil && resp.IsError()) {
236		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
237	}
238	groupAliasID := resp.Data["id"].(string)
239
240	resp, err = i.HandleRequest(ctx, &logical.Request{
241		Path:      "group/id/" + groupID,
242		Operation: logical.DeleteOperation,
243	})
244	if err != nil || (resp != nil && resp.IsError()) {
245		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
246	}
247
248	resp, err = i.HandleRequest(ctx, &logical.Request{
249		Path:      "group-alias/id/" + groupAliasID,
250		Operation: logical.ReadOperation,
251	})
252	if err != nil || (resp != nil && resp.IsError()) {
253		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
254	}
255	if resp != nil {
256		t.Fatalf("expected a nil response")
257	}
258}
259
260func TestIdentityStore_GroupAliases_CRUD(t *testing.T) {
261	var resp *logical.Response
262	var err error
263	ctx := namespace.RootContext(nil)
264	i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
265
266	groupReq := &logical.Request{
267		Path:      "group",
268		Operation: logical.UpdateOperation,
269		Data: map[string]interface{}{
270			"type": "external",
271		},
272	}
273	resp, err = i.HandleRequest(ctx, groupReq)
274	if err != nil || (resp != nil && resp.IsError()) {
275		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
276	}
277	groupID := resp.Data["id"].(string)
278
279	groupAliasReq := &logical.Request{
280		Path:      "group-alias",
281		Operation: logical.UpdateOperation,
282		Data: map[string]interface{}{
283			"name":           "testgroupalias",
284			"mount_accessor": accessor,
285			"canonical_id":   groupID,
286			"mount_type":     "ldap",
287		},
288	}
289	resp, err = i.HandleRequest(ctx, groupAliasReq)
290	if err != nil || (resp != nil && resp.IsError()) {
291		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
292	}
293	groupAliasID := resp.Data["id"].(string)
294
295	groupAliasReq.Path = "group-alias/id/" + groupAliasID
296	groupAliasReq.Operation = logical.ReadOperation
297	resp, err = i.HandleRequest(ctx, groupAliasReq)
298	if err != nil || (resp != nil && resp.IsError()) {
299		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
300	}
301
302	if resp.Data["id"].(string) != groupAliasID {
303		t.Fatalf("bad: group alias: %#v\n", resp.Data)
304	}
305
306	resp, err = i.HandleRequest(ctx, &logical.Request{
307		Path:      "group-alias/id/" + groupAliasID,
308		Operation: logical.UpdateOperation,
309		Data: map[string]interface{}{
310			"name":           "testupdatedgroupaliasname",
311			"mount_accessor": accessor,
312			"canonical_id":   groupID,
313			"mount_type":     "ldap",
314		},
315	})
316	if err != nil || (resp != nil && resp.IsError()) {
317		t.Fatalf("bad: err: %v; resp: %#v", err, resp)
318	}
319	if resp.Data["id"].(string) != groupAliasID {
320		t.Fatalf("bad: group alias: %#v\n", resp.Data)
321	}
322
323	groupAliasReq.Operation = logical.DeleteOperation
324	resp, err = i.HandleRequest(ctx, groupAliasReq)
325	if err != nil || (resp != nil && resp.IsError()) {
326		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
327	}
328
329	groupAliasReq.Operation = logical.ReadOperation
330	resp, err = i.HandleRequest(ctx, groupAliasReq)
331	if err != nil || (resp != nil && resp.IsError()) {
332		t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
333	}
334
335	if resp != nil {
336		t.Fatalf("failed to delete group alias")
337	}
338}
339
340func TestIdentityStore_GroupAliases_MemDBIndexes(t *testing.T) {
341	var err error
342	ctx := namespace.RootContext(nil)
343	i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
344
345	group := &identity.Group{
346		ID:   "testgroupid",
347		Name: "testgroupname",
348		Metadata: map[string]string{
349			"testmetadatakey1": "testmetadatavalue1",
350			"testmetadatakey2": "testmetadatavalue2",
351		},
352		Alias: &identity.Alias{
353			ID:            "testgroupaliasid",
354			Name:          "testalias",
355			MountAccessor: accessor,
356			CanonicalID:   "testgroupid",
357			MountType:     "ldap",
358		},
359		ParentGroupIDs:  []string{"testparentgroupid1", "testparentgroupid2"},
360		MemberEntityIDs: []string{"testentityid1", "testentityid2"},
361		Policies:        []string{"testpolicy1", "testpolicy2"},
362		BucketKey:       i.groupPacker.BucketKey("testgroupid"),
363	}
364
365	txn := i.db.Txn(true)
366	defer txn.Abort()
367	err = i.MemDBUpsertAliasInTxn(txn, group.Alias, true)
368	if err != nil {
369		t.Fatal(err)
370	}
371	err = i.MemDBUpsertGroupInTxn(txn, group)
372	if err != nil {
373		t.Fatal(err)
374	}
375	txn.Commit()
376
377	alias, err := i.MemDBAliasByID("testgroupaliasid", false, true)
378	if err != nil {
379		t.Fatal(err)
380	}
381	if alias.ID != "testgroupaliasid" {
382		t.Fatalf("bad: group alias: %#v\n", alias)
383	}
384
385	group, err = i.MemDBGroupByAliasID("testgroupaliasid", false)
386	if err != nil {
387		t.Fatal(err)
388	}
389	if group.ID != "testgroupid" {
390		t.Fatalf("bad: group: %#v\n", group)
391	}
392
393	aliasByFactors, err := i.MemDBAliasByFactors(group.Alias.MountAccessor, group.Alias.Name, false, true)
394	if err != nil {
395		t.Fatal(err)
396	}
397	if aliasByFactors.ID != "testgroupaliasid" {
398		t.Fatalf("bad: group alias: %#v\n", aliasByFactors)
399	}
400}
401
402func TestIdentityStore_GroupAliases_AliasOnInternalGroup(t *testing.T) {
403	var err error
404	var resp *logical.Response
405
406	ctx := namespace.RootContext(nil)
407	i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
408
409	groupReq := &logical.Request{
410		Path:      "group",
411		Operation: logical.UpdateOperation,
412	}
413	resp, err = i.HandleRequest(ctx, groupReq)
414	if err != nil || (resp != nil && resp.IsError()) {
415		t.Fatalf("bad: resp: %#v; err: %v", resp, err)
416	}
417	groupID := resp.Data["id"].(string)
418
419	aliasReq := &logical.Request{
420		Path:      "group-alias",
421		Operation: logical.UpdateOperation,
422		Data: map[string]interface{}{
423			"name":           "testname",
424			"mount_accessor": accessor,
425			"canonical_id":   groupID,
426		},
427	}
428	resp, err = i.HandleRequest(ctx, aliasReq)
429	if err != nil {
430		t.Fatal(err)
431	}
432	if !resp.IsError() {
433		t.Fatalf("expected an error")
434	}
435}
436
437func TestIdentityStore_GroupAliasesUpdate(t *testing.T) {
438	ctx := namespace.RootContext(nil)
439	i, accessor1, c := testIdentityStoreWithGithubAuth(ctx, t)
440
441	ghme2 := &MountEntry{
442		Table:       credentialTableType,
443		Path:        "github2/",
444		Type:        "github",
445		Description: "github auth",
446	}
447
448	err := c.enableCredential(ctx, ghme2)
449	if err != nil {
450		t.Fatal(err)
451	}
452	accessor2 := ghme2.Accessor
453
454	// Create two groups
455	resp, err := i.HandleRequest(ctx, &logical.Request{
456		Path:      "group",
457		Operation: logical.UpdateOperation,
458		Data: map[string]interface{}{
459			"type": "external",
460		},
461	})
462	if err != nil || (resp != nil && resp.IsError()) {
463		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
464	}
465	groupID1 := resp.Data["id"].(string)
466
467	resp, err = i.HandleRequest(ctx, &logical.Request{
468		Path:      "group",
469		Operation: logical.UpdateOperation,
470		Data: map[string]interface{}{
471			"type": "external",
472		},
473	})
474	if err != nil || (resp != nil && resp.IsError()) {
475		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
476	}
477	groupID2 := resp.Data["id"].(string)
478
479	// Create an alias
480	resp, err = i.HandleRequest(ctx, &logical.Request{
481		Path:      "group-alias",
482		Operation: logical.UpdateOperation,
483		Data: map[string]interface{}{
484			"mount_accessor": accessor1,
485			"canonical_id":   groupID1,
486			"name":           "testalias",
487		},
488	})
489	if err != nil || (resp != nil && resp.IsError()) {
490		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
491	}
492	aliasID := resp.Data["id"].(string)
493
494	// Ensure that reading the alias returns the right group
495	resp, err = i.HandleRequest(ctx, &logical.Request{
496		Path:      "group-alias/id/" + aliasID,
497		Operation: logical.ReadOperation,
498	})
499	if err != nil || (resp != nil && resp.IsError()) {
500		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
501	}
502	aliasName := resp.Data["name"].(string)
503	if aliasName != "testalias" {
504		t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName)
505	}
506	mountAccessor := resp.Data["mount_accessor"].(string)
507	if mountAccessor != accessor1 {
508		t.Fatalf("bad mount accessor; expected: %q, actual: %q", accessor1, mountAccessor)
509	}
510	canonicalID := resp.Data["canonical_id"].(string)
511	if canonicalID != groupID1 {
512		t.Fatalf("bad canonical name; expected: %q, actual: %q", groupID1, canonicalID)
513	}
514	// Ensure that reading the group returns the alias
515	resp, err = i.HandleRequest(ctx, &logical.Request{
516		Path:      "group/id/" + groupID1,
517		Operation: logical.ReadOperation,
518	})
519	if err != nil || (resp != nil && resp.IsError()) {
520		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
521	}
522	aliasName = resp.Data["alias"].(map[string]interface{})["name"].(string)
523	if aliasName != "testalias" {
524		t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName)
525	}
526
527	// Overwrite the alias
528	resp, err = i.HandleRequest(ctx, &logical.Request{
529		Path:      "group-alias/id/" + aliasID,
530		Operation: logical.UpdateOperation,
531		Data: map[string]interface{}{
532			"mount_accessor": accessor2,
533			"canonical_id":   groupID2,
534			"name":           "testalias2",
535		},
536	})
537	if err != nil || (resp != nil && resp.IsError()) {
538		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
539	}
540
541	// Ensure that reading the alias returns the right group
542	resp, err = i.HandleRequest(ctx, &logical.Request{
543		Path:      "group-alias/id/" + aliasID,
544		Operation: logical.ReadOperation,
545	})
546	if err != nil || (resp != nil && resp.IsError()) {
547		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
548	}
549	aliasName = resp.Data["name"].(string)
550	if aliasName != "testalias2" {
551		t.Fatalf("bad alias name; expected: testalias2, actual: %q", aliasName)
552	}
553	mountAccessor = resp.Data["mount_accessor"].(string)
554	if mountAccessor != accessor2 {
555		t.Fatalf("bad mount accessor; expected: %q, actual: %q", accessor2, mountAccessor)
556	}
557	canonicalID = resp.Data["canonical_id"].(string)
558	if canonicalID != groupID2 {
559		t.Fatalf("bad canonical name; expected: %q, actual: %q", groupID2, canonicalID)
560	}
561	// Ensure that reading the group returns the alias
562	resp, err = i.HandleRequest(ctx, &logical.Request{
563		Path:      "group/id/" + groupID2,
564		Operation: logical.ReadOperation,
565	})
566	if err != nil || (resp != nil && resp.IsError()) {
567		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
568	}
569	aliasName = resp.Data["alias"].(map[string]interface{})["name"].(string)
570	if aliasName != "testalias2" {
571		t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName)
572	}
573	// Ensure the old group does not
574	resp, err = i.HandleRequest(ctx, &logical.Request{
575		Path:      "group/id/" + groupID1,
576		Operation: logical.ReadOperation,
577	})
578	if err != nil || (resp != nil && resp.IsError()) {
579		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
580	}
581	aliasInfo := resp.Data["alias"].(map[string]interface{})
582	if len(aliasInfo) > 0 {
583		t.Fatalf("still found alias with old group: %s", pretty.Sprint(resp.Data))
584	}
585}
586