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