1package approle
2
3import (
4	"context"
5	"fmt"
6	"sync"
7	"sync/atomic"
8	"testing"
9	"time"
10
11	"github.com/hashicorp/vault/sdk/logical"
12)
13
14func TestAppRole_TidyDanglingAccessors_Normal(t *testing.T) {
15	var resp *logical.Response
16	var err error
17	b, storage := createBackendWithStorage(t)
18
19	// Create a role
20	createRole(t, b, storage, "role1", "a,b,c")
21
22	// Create a secret-id
23	roleSecretIDReq := &logical.Request{
24		Operation: logical.UpdateOperation,
25		Path:      "role/role1/secret-id",
26		Storage:   storage,
27	}
28	resp, err = b.HandleRequest(context.Background(), roleSecretIDReq)
29	if err != nil || (resp != nil && resp.IsError()) {
30		t.Fatalf("err:%v resp:%#v", err, resp)
31	}
32
33	accessorHashes, err := storage.List(context.Background(), "accessor/")
34	if err != nil {
35		t.Fatal(err)
36	}
37	if len(accessorHashes) != 1 {
38		t.Fatalf("bad: len(accessorHashes); expect 1, got %d", len(accessorHashes))
39	}
40
41	entry1, err := logical.StorageEntryJSON(
42		"accessor/invalid1",
43		&secretIDAccessorStorageEntry{
44			SecretIDHMAC: "samplesecretidhmac",
45		},
46	)
47	if err != nil {
48		t.Fatal(err)
49	}
50
51	if err := storage.Put(context.Background(), entry1); err != nil {
52		t.Fatal(err)
53	}
54
55	entry2, err := logical.StorageEntryJSON(
56		"accessor/invalid2",
57		&secretIDAccessorStorageEntry{
58			SecretIDHMAC: "samplesecretidhmac2",
59		},
60	)
61	if err != nil {
62		t.Fatal(err)
63	}
64	if err := storage.Put(context.Background(), entry2); err != nil {
65		t.Fatal(err)
66	}
67
68	accessorHashes, err = storage.List(context.Background(), "accessor/")
69	if err != nil {
70		t.Fatal(err)
71	}
72	if len(accessorHashes) != 3 {
73		t.Fatalf("bad: len(accessorHashes); expect 3, got %d", len(accessorHashes))
74	}
75
76	_, err = b.tidySecretID(context.Background(), &logical.Request{
77		Storage: storage,
78	})
79	if err != nil {
80		t.Fatal(err)
81	}
82
83	// It runs async so we give it a bit of time to run
84	time.Sleep(10 * time.Second)
85
86	accessorHashes, err = storage.List(context.Background(), "accessor/")
87	if err != nil {
88		t.Fatal(err)
89	}
90	if len(accessorHashes) != 1 {
91		t.Fatalf("bad: len(accessorHashes); expect 1, got %d", len(accessorHashes))
92	}
93}
94
95func TestAppRole_TidyDanglingAccessors_RaceTest(t *testing.T) {
96	var resp *logical.Response
97	var err error
98	b, storage := createBackendWithStorage(t)
99
100	// Create a role
101	createRole(t, b, storage, "role1", "a,b,c")
102
103	// Create an initial entry
104	roleSecretIDReq := &logical.Request{
105		Operation: logical.UpdateOperation,
106		Path:      "role/role1/secret-id",
107		Storage:   storage,
108	}
109	resp, err = b.HandleRequest(context.Background(), roleSecretIDReq)
110	if err != nil || (resp != nil && resp.IsError()) {
111		t.Fatalf("err:%v resp:%#v", err, resp)
112	}
113	count := 1
114
115	wg := &sync.WaitGroup{}
116	start := time.Now()
117	for time.Now().Sub(start) < 10*time.Second {
118		if time.Now().Sub(start) > 100*time.Millisecond && atomic.LoadUint32(b.tidySecretIDCASGuard) == 0 {
119			_, err = b.tidySecretID(context.Background(), &logical.Request{
120				Storage: storage,
121			})
122			if err != nil {
123				t.Fatal(err)
124			}
125		}
126		wg.Add(1)
127		go func() {
128			defer wg.Done()
129			roleSecretIDReq := &logical.Request{
130				Operation: logical.UpdateOperation,
131				Path:      "role/role1/secret-id",
132				Storage:   storage,
133			}
134			resp, err := b.HandleRequest(context.Background(), roleSecretIDReq)
135			if err != nil || (resp != nil && resp.IsError()) {
136				t.Fatalf("err:%v resp:%#v", err, resp)
137			}
138		}()
139
140		entry, err := logical.StorageEntryJSON(
141			fmt.Sprintf("accessor/invalid%d", count),
142			&secretIDAccessorStorageEntry{
143				SecretIDHMAC: "samplesecretidhmac",
144			},
145		)
146		if err != nil {
147			t.Fatal(err)
148		}
149
150		if err := storage.Put(context.Background(), entry); err != nil {
151			t.Fatal(err)
152		}
153
154		count++
155		time.Sleep(100 * time.Microsecond)
156	}
157
158	logger := b.Logger().Named(t.Name())
159	logger.Info("wrote entries", "count", count)
160
161	wg.Wait()
162	// Let tidy finish
163	for atomic.LoadUint32(b.tidySecretIDCASGuard) != 0 {
164		time.Sleep(100 * time.Millisecond)
165	}
166
167	logger.Info("running tidy again")
168
169	// Run tidy again
170	secret, err := b.tidySecretID(context.Background(), &logical.Request{
171		Storage: storage,
172	})
173	if err != nil || len(secret.Warnings) > 0 {
174		t.Fatal(err, secret.Warnings)
175	}
176
177	// Wait for tidy to start
178	for atomic.LoadUint32(b.tidySecretIDCASGuard) == 0 {
179		time.Sleep(100 * time.Millisecond)
180	}
181
182	// Let tidy finish
183	for atomic.LoadUint32(b.tidySecretIDCASGuard) != 0 {
184		time.Sleep(100 * time.Millisecond)
185	}
186
187	accessorHashes, err := storage.List(context.Background(), "accessor/")
188	if err != nil {
189		t.Fatal(err)
190	}
191	if len(accessorHashes) != count {
192		t.Fatalf("bad: len(accessorHashes); expect %d, got %d", count, len(accessorHashes))
193	}
194
195	roleHMACs, err := storage.List(context.Background(), secretIDPrefix)
196	if err != nil {
197		t.Fatal(err)
198	}
199	secretIDs, err := storage.List(context.Background(), fmt.Sprintf("%s%s", secretIDPrefix, roleHMACs[0]))
200	if err != nil {
201		t.Fatal(err)
202	}
203	if len(secretIDs) != count {
204		t.Fatalf("bad: len(secretIDs); expect %d, got %d", count, len(secretIDs))
205	}
206}
207