1package vault
2
3import (
4	"context"
5	"reflect"
6	"strings"
7	"testing"
8
9	"github.com/hashicorp/vault/helper/namespace"
10	"github.com/hashicorp/vault/sdk/helper/jsonutil"
11	"github.com/hashicorp/vault/sdk/logical"
12)
13
14func TestAuth_ReadOnlyViewDuringMount(t *testing.T) {
15	c, _, _ := TestCoreUnsealed(t)
16	c.credentialBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
17		err := config.StorageView.Put(ctx, &logical.StorageEntry{
18			Key:   "bar",
19			Value: []byte("baz"),
20		})
21		if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
22			t.Fatalf("expected a read-only error")
23		}
24		return &NoopBackend{
25			BackendType: logical.TypeCredential,
26		}, nil
27	}
28
29	me := &MountEntry{
30		Table: credentialTableType,
31		Path:  "foo",
32		Type:  "noop",
33	}
34	err := c.enableCredential(namespace.RootContext(nil), me)
35	if err != nil {
36		t.Fatalf("err: %v", err)
37	}
38}
39
40func TestCore_DefaultAuthTable(t *testing.T) {
41	c, keys, _ := TestCoreUnsealed(t)
42	verifyDefaultAuthTable(t, c.auth)
43
44	// Start a second core with same physical
45	conf := &CoreConfig{
46		Physical:     c.physical,
47		DisableMlock: true,
48	}
49	c2, err := NewCore(conf)
50	if err != nil {
51		t.Fatalf("err: %v", err)
52	}
53	for i, key := range keys {
54		unseal, err := TestCoreUnseal(c2, key)
55		if err != nil {
56			t.Fatalf("err: %v", err)
57		}
58		if i+1 == len(keys) && !unseal {
59			t.Fatalf("should be unsealed")
60		}
61	}
62
63	// Verify matching mount tables
64	if !reflect.DeepEqual(c.auth, c2.auth) {
65		t.Fatalf("mismatch: %v %v", c.auth, c2.auth)
66	}
67}
68
69func TestCore_EnableCredential(t *testing.T) {
70	c, keys, _ := TestCoreUnsealed(t)
71	c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
72		return &NoopBackend{
73			BackendType: logical.TypeCredential,
74		}, nil
75	}
76
77	me := &MountEntry{
78		Table: credentialTableType,
79		Path:  "foo",
80		Type:  "noop",
81	}
82	err := c.enableCredential(namespace.RootContext(nil), me)
83	if err != nil {
84		t.Fatalf("err: %v", err)
85	}
86
87	match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
88	if match != "auth/foo/" {
89		t.Fatalf("missing mount, match: %q", match)
90	}
91
92	conf := &CoreConfig{
93		Physical:     c.physical,
94		DisableMlock: true,
95	}
96	c2, err := NewCore(conf)
97	if err != nil {
98		t.Fatalf("err: %v", err)
99	}
100	c2.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
101		return &NoopBackend{
102			BackendType: logical.TypeCredential,
103		}, nil
104	}
105	for i, key := range keys {
106		unseal, err := TestCoreUnseal(c2, key)
107		if err != nil {
108			t.Fatalf("err: %v", err)
109		}
110		if i+1 == len(keys) && !unseal {
111			t.Fatalf("should be unsealed")
112		}
113	}
114
115	// Verify matching auth tables
116	if !reflect.DeepEqual(c.auth, c2.auth) {
117		t.Fatalf("mismatch: %v %v", c.auth, c2.auth)
118	}
119}
120
121// Test that the local table actually gets populated as expected with local
122// entries, and that upon reading the entries from both are recombined
123// correctly
124func TestCore_EnableCredential_Local(t *testing.T) {
125	c, _, _ := TestCoreUnsealed(t)
126	c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
127		return &NoopBackend{
128			BackendType: logical.TypeCredential,
129		}, nil
130	}
131
132	c.auth = &MountTable{
133		Type: credentialTableType,
134		Entries: []*MountEntry{
135			&MountEntry{
136				Table:            credentialTableType,
137				Path:             "noop/",
138				Type:             "noop",
139				UUID:             "abcd",
140				Accessor:         "noop-abcd",
141				BackendAwareUUID: "abcde",
142				NamespaceID:      namespace.RootNamespaceID,
143				namespace:        namespace.RootNamespace,
144			},
145			&MountEntry{
146				Table:            credentialTableType,
147				Path:             "noop2/",
148				Type:             "noop",
149				UUID:             "bcde",
150				Accessor:         "noop-bcde",
151				BackendAwareUUID: "bcdea",
152				NamespaceID:      namespace.RootNamespaceID,
153				namespace:        namespace.RootNamespace,
154			},
155		},
156	}
157
158	// Both should set up successfully
159	err := c.setupCredentials(context.Background())
160	if err != nil {
161		t.Fatal(err)
162	}
163
164	rawLocal, err := c.barrier.Get(context.Background(), coreLocalAuthConfigPath)
165	if err != nil {
166		t.Fatal(err)
167	}
168	if rawLocal == nil {
169		t.Fatal("expected non-nil local credential")
170	}
171	localCredentialTable := &MountTable{}
172	if err := jsonutil.DecodeJSON(rawLocal.Value, localCredentialTable); err != nil {
173		t.Fatal(err)
174	}
175	if len(localCredentialTable.Entries) > 0 {
176		t.Fatalf("expected no entries in local credential table, got %#v", localCredentialTable)
177	}
178
179	c.auth.Entries[1].Local = true
180	if err := c.persistAuth(context.Background(), c.auth, nil); err != nil {
181		t.Fatal(err)
182	}
183
184	rawLocal, err = c.barrier.Get(context.Background(), coreLocalAuthConfigPath)
185	if err != nil {
186		t.Fatal(err)
187	}
188	if rawLocal == nil {
189		t.Fatal("expected non-nil local credential")
190	}
191	localCredentialTable = &MountTable{}
192	if err := jsonutil.DecodeJSON(rawLocal.Value, localCredentialTable); err != nil {
193		t.Fatal(err)
194	}
195	if len(localCredentialTable.Entries) != 1 {
196		t.Fatalf("expected one entry in local credential table, got %#v", localCredentialTable)
197	}
198
199	oldCredential := c.auth
200	if err := c.loadCredentials(context.Background()); err != nil {
201		t.Fatal(err)
202	}
203
204	if !reflect.DeepEqual(oldCredential, c.auth) {
205		t.Fatalf("expected\n%#v\ngot\n%#v\n", oldCredential, c.auth)
206	}
207
208	if len(c.auth.Entries) != 2 {
209		t.Fatalf("expected two credential entries, got %#v", localCredentialTable)
210	}
211}
212
213func TestCore_EnableCredential_twice_409(t *testing.T) {
214	c, _, _ := TestCoreUnsealed(t)
215	c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
216		return &NoopBackend{
217			BackendType: logical.TypeCredential,
218		}, nil
219	}
220
221	me := &MountEntry{
222		Table: credentialTableType,
223		Path:  "foo",
224		Type:  "noop",
225	}
226	err := c.enableCredential(namespace.RootContext(nil), me)
227	if err != nil {
228		t.Fatalf("err: %v", err)
229	}
230
231	// 2nd should be a 409 error
232	err2 := c.enableCredential(namespace.RootContext(nil), me)
233	switch err2.(type) {
234	case logical.HTTPCodedError:
235		if err2.(logical.HTTPCodedError).Code() != 409 {
236			t.Fatalf("invalid code given")
237		}
238	default:
239		t.Fatalf("expected a different error type")
240	}
241}
242
243func TestCore_EnableCredential_Token(t *testing.T) {
244	c, _, _ := TestCoreUnsealed(t)
245	me := &MountEntry{
246		Table: credentialTableType,
247		Path:  "foo",
248		Type:  "token",
249	}
250	err := c.enableCredential(namespace.RootContext(nil), me)
251	if err.Error() != "token credential backend cannot be instantiated" {
252		t.Fatalf("err: %v", err)
253	}
254}
255
256func TestCore_DisableCredential(t *testing.T) {
257	c, keys, _ := TestCoreUnsealed(t)
258	c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
259		return &NoopBackend{
260			BackendType: logical.TypeCredential,
261		}, nil
262	}
263
264	err := c.disableCredential(namespace.RootContext(nil), "foo")
265	if err != nil && !strings.HasPrefix(err.Error(), "no matching mount") {
266		t.Fatal(err)
267	}
268
269	me := &MountEntry{
270		Table: credentialTableType,
271		Path:  "foo",
272		Type:  "noop",
273	}
274	err = c.enableCredential(namespace.RootContext(nil), me)
275	if err != nil {
276		t.Fatalf("err: %v", err)
277	}
278
279	err = c.disableCredential(namespace.RootContext(nil), "foo")
280	if err != nil {
281		t.Fatalf("err: %v", err)
282	}
283
284	match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
285	if match != "" {
286		t.Fatalf("backend present")
287	}
288
289	conf := &CoreConfig{
290		Physical:     c.physical,
291		DisableMlock: true,
292	}
293	c2, err := NewCore(conf)
294	if err != nil {
295		t.Fatalf("err: %v", err)
296	}
297	for i, key := range keys {
298		unseal, err := TestCoreUnseal(c2, key)
299		if err != nil {
300			t.Fatalf("err: %v", err)
301		}
302		if i+1 == len(keys) && !unseal {
303			t.Fatalf("should be unsealed")
304		}
305	}
306
307	// Verify matching mount tables
308	if !reflect.DeepEqual(c.auth, c2.auth) {
309		t.Fatalf("mismatch: %v %v", c.auth, c2.auth)
310	}
311}
312
313func TestCore_DisableCredential_Protected(t *testing.T) {
314	c, _, _ := TestCoreUnsealed(t)
315	err := c.disableCredential(namespace.RootContext(nil), "token")
316	if err.Error() != "token credential backend cannot be disabled" {
317		t.Fatalf("err: %v", err)
318	}
319}
320
321func TestCore_DisableCredential_Cleanup(t *testing.T) {
322	noop := &NoopBackend{
323		Login:       []string{"login"},
324		BackendType: logical.TypeCredential,
325	}
326	c, _, _ := TestCoreUnsealed(t)
327	c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
328		return noop, nil
329	}
330
331	me := &MountEntry{
332		Table: credentialTableType,
333		Path:  "foo",
334		Type:  "noop",
335	}
336	err := c.enableCredential(namespace.RootContext(nil), me)
337	if err != nil {
338		t.Fatalf("err: %v", err)
339	}
340
341	// Store the view
342	view := c.router.MatchingStorageByAPIPath(namespace.RootContext(nil), "auth/foo/")
343
344	// Inject data
345	se := &logical.StorageEntry{
346		Key:   "plstodelete",
347		Value: []byte("test"),
348	}
349	if err := view.Put(context.Background(), se); err != nil {
350		t.Fatalf("err: %v", err)
351	}
352
353	// Generate a new token auth
354	noop.Response = &logical.Response{
355		Auth: &logical.Auth{
356			Policies: []string{"foo"},
357		},
358	}
359	r := &logical.Request{
360		Operation: logical.ReadOperation,
361		Path:      "auth/foo/login",
362	}
363	resp, err := c.HandleRequest(namespace.RootContext(nil), r)
364	if err != nil {
365		t.Fatalf("err: %v", err)
366	}
367	if resp.Auth.ClientToken == "" {
368		t.Fatalf("bad: %#v", resp)
369	}
370
371	// Disable should cleanup
372	err = c.disableCredential(namespace.RootContext(nil), "foo")
373	if err != nil {
374		t.Fatalf("err: %v", err)
375	}
376
377	// Token should be revoked
378	te, err := c.tokenStore.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
379	if err != nil {
380		t.Fatalf("err: %v", err)
381	}
382	if te != nil {
383		t.Fatalf("bad: %#v", te)
384	}
385
386	// View should be empty
387	out, err := logical.CollectKeys(context.Background(), view)
388	if err != nil {
389		t.Fatalf("err: %v", err)
390	}
391	if len(out) != 0 {
392		t.Fatalf("bad: %#v", out)
393	}
394}
395
396func TestDefaultAuthTable(t *testing.T) {
397	c, _, _ := TestCoreUnsealed(t)
398	table := c.defaultAuthTable()
399	verifyDefaultAuthTable(t, table)
400}
401
402func verifyDefaultAuthTable(t *testing.T, table *MountTable) {
403	if len(table.Entries) != 1 {
404		t.Fatalf("bad: %v", table.Entries)
405	}
406	if table.Type != credentialTableType {
407		t.Fatalf("bad: %v", *table)
408	}
409	for idx, entry := range table.Entries {
410		switch idx {
411		case 0:
412			if entry.Path != "token/" {
413				t.Fatalf("bad: %v", entry)
414			}
415			if entry.Type != "token" {
416				t.Fatalf("bad: %v", entry)
417			}
418		}
419		if entry.Description == "" {
420			t.Fatalf("bad: %v", entry)
421		}
422		if entry.UUID == "" {
423			t.Fatalf("bad: %v", entry)
424		}
425	}
426}
427
428func TestCore_CredentialInitialize(t *testing.T) {
429	{
430		backend := &InitializableBackend{
431			&NoopBackend{
432				BackendType: logical.TypeCredential,
433			}, false}
434
435		c, _, _ := TestCoreUnsealed(t)
436		c.credentialBackends["initable"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
437			return backend, nil
438		}
439
440		me := &MountEntry{
441			Table: credentialTableType,
442			Path:  "foo/",
443			Type:  "initable",
444		}
445		err := c.enableCredential(namespace.RootContext(nil), me)
446		if err != nil {
447			t.Fatalf("err: %v", err)
448		}
449
450		if !backend.isInitialized {
451			t.Fatal("backend is not initialized")
452		}
453	}
454	{
455		backend := &InitializableBackend{
456			&NoopBackend{
457				BackendType: logical.TypeCredential,
458			}, false}
459
460		c, _, _ := TestCoreUnsealed(t)
461		c.credentialBackends["initable"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
462			return backend, nil
463		}
464
465		c.auth = &MountTable{
466			Type: credentialTableType,
467			Entries: []*MountEntry{
468				&MountEntry{
469					Table:            credentialTableType,
470					Path:             "foo/",
471					Type:             "initable",
472					UUID:             "abcd",
473					Accessor:         "initable-abcd",
474					BackendAwareUUID: "abcde",
475					NamespaceID:      namespace.RootNamespaceID,
476					namespace:        namespace.RootNamespace,
477				},
478			},
479		}
480
481		err := c.setupCredentials(context.Background())
482		if err != nil {
483			t.Fatal(err)
484		}
485
486		// run the postUnseal funcs, so that the backend will be inited
487		for _, f := range c.postUnsealFuncs {
488			f()
489		}
490
491		if !backend.isInitialized {
492			t.Fatal("backend is not initialized")
493		}
494	}
495}
496