1package vault
2
3import (
4	"strings"
5	"testing"
6	"time"
7
8	"github.com/go-test/deep"
9	"github.com/hashicorp/vault/helper/namespace"
10)
11
12var rawPolicy = strings.TrimSpace(`
13# Developer policy
14name = "dev"
15
16# Deny all paths by default
17path "*" {
18	policy = "deny"
19}
20
21# Allow full access to staging
22path "stage/*" {
23	policy = "sudo"
24}
25
26# Limited read privilege to production
27path "prod/version" {
28	policy = "read"
29}
30
31# Read access to foobar
32# Also tests stripping of leading slash and parsing of min/max as string and
33# integer
34path "/foo/bar" {
35	policy = "read"
36	min_wrapping_ttl = 300
37	max_wrapping_ttl = "1h"
38}
39
40# Add capabilities for creation and sudo to foobar
41# This will be separate; they are combined when compiled into an ACL
42# Also tests reverse string/int handling to the above
43path "foo/bar" {
44	capabilities = ["create", "sudo"]
45	min_wrapping_ttl = "300s"
46	max_wrapping_ttl = 3600
47}
48
49# Check that only allowed_parameters are being added to foobar
50path "foo/bar" {
51	capabilities = ["create", "sudo"]
52	allowed_parameters = {
53	  "zip" = []
54	  "zap" = []
55	}
56}
57
58# Check that only denied_parameters are being added to bazbar
59path "baz/bar" {
60	capabilities = ["create", "sudo"]
61	denied_parameters = {
62	  "zip" = []
63	  "zap" = []
64	}
65}
66
67# Check that both allowed and denied parameters are being added to bizbar
68path "biz/bar" {
69	capabilities = ["create", "sudo"]
70	allowed_parameters = {
71	  "zim" = []
72	  "zam" = []
73	}
74	denied_parameters = {
75	  "zip" = []
76	  "zap" = []
77	}
78}
79path "test/types" {
80	capabilities = ["create", "sudo"]
81	allowed_parameters = {
82		"map" = [{"good" = "one"}]
83		"int" = [1, 2]
84	}
85	denied_parameters = {
86		"string" = ["test"]
87		"bool" = [false]
88	}
89}
90path "test/req" {
91	capabilities = ["create", "sudo"]
92	required_parameters = ["foo"]
93}
94path "test/mfa" {
95	capabilities = ["create", "sudo"]
96	mfa_methods = ["my_totp", "my_totp2"]
97}
98path "test/+/segment" {
99	capabilities = ["create", "sudo"]
100}
101path "test/segment/at/end/+" {
102	capabilities = ["create", "sudo"]
103}
104path "test/segment/at/end/v2/+/" {
105	capabilities = ["create", "sudo"]
106}
107path "test/+/wildcard/+/*" {
108	capabilities = ["create", "sudo"]
109}
110path "test/+/wildcard/+/end*" {
111	capabilities = ["create", "sudo"]
112}
113`)
114
115func TestPolicy_Parse(t *testing.T) {
116	p, err := ParseACLPolicy(namespace.RootNamespace, rawPolicy)
117	if err != nil {
118		t.Fatalf("err: %v", err)
119	}
120
121	if p.Name != "dev" {
122		t.Fatalf("bad name: %q", p.Name)
123	}
124
125	expect := []*PathRules{
126		{
127			Path:   "",
128			Policy: "deny",
129			Capabilities: []string{
130				"deny",
131			},
132			Permissions: &ACLPermissions{CapabilitiesBitmap: DenyCapabilityInt},
133			IsPrefix:    true,
134		},
135		{
136			Path:   "stage/",
137			Policy: "sudo",
138			Capabilities: []string{
139				"create",
140				"read",
141				"update",
142				"delete",
143				"list",
144				"sudo",
145			},
146			Permissions: &ACLPermissions{
147				CapabilitiesBitmap: (CreateCapabilityInt | ReadCapabilityInt | UpdateCapabilityInt | DeleteCapabilityInt | ListCapabilityInt | SudoCapabilityInt),
148			},
149			IsPrefix: true,
150		},
151		{
152			Path:   "prod/version",
153			Policy: "read",
154			Capabilities: []string{
155				"read",
156				"list",
157			},
158			Permissions: &ACLPermissions{CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt)},
159		},
160		{
161			Path:   "foo/bar",
162			Policy: "read",
163			Capabilities: []string{
164				"read",
165				"list",
166			},
167			MinWrappingTTLHCL: 300,
168			MaxWrappingTTLHCL: "1h",
169			Permissions: &ACLPermissions{
170				CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt),
171				MinWrappingTTL:     300 * time.Second,
172				MaxWrappingTTL:     3600 * time.Second,
173			},
174		},
175		{
176			Path: "foo/bar",
177			Capabilities: []string{
178				"create",
179				"sudo",
180			},
181			MinWrappingTTLHCL: "300s",
182			MaxWrappingTTLHCL: 3600,
183			Permissions: &ACLPermissions{
184				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
185				MinWrappingTTL:     300 * time.Second,
186				MaxWrappingTTL:     3600 * time.Second,
187			},
188		},
189		{
190			Path: "foo/bar",
191			Capabilities: []string{
192				"create",
193				"sudo",
194			},
195			AllowedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
196			Permissions: &ACLPermissions{
197				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
198				AllowedParameters:  map[string][]interface{}{"zip": {}, "zap": {}},
199			},
200		},
201		{
202			Path: "baz/bar",
203			Capabilities: []string{
204				"create",
205				"sudo",
206			},
207			DeniedParametersHCL: map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
208			Permissions: &ACLPermissions{
209				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
210				DeniedParameters:   map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
211			},
212		},
213		{
214			Path: "biz/bar",
215			Capabilities: []string{
216				"create",
217				"sudo",
218			},
219			AllowedParametersHCL: map[string][]interface{}{"zim": {}, "zam": {}},
220			DeniedParametersHCL:  map[string][]interface{}{"zip": {}, "zap": {}},
221			Permissions: &ACLPermissions{
222				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
223				AllowedParameters:  map[string][]interface{}{"zim": {}, "zam": {}},
224				DeniedParameters:   map[string][]interface{}{"zip": {}, "zap": {}},
225			},
226		},
227		{
228			Path:   "test/types",
229			Policy: "",
230			Capabilities: []string{
231				"create",
232				"sudo",
233			},
234			AllowedParametersHCL: map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
235			DeniedParametersHCL:  map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
236			Permissions: &ACLPermissions{
237				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
238				AllowedParameters:  map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
239				DeniedParameters:   map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
240			},
241			IsPrefix: false,
242		},
243		{
244			Path: "test/req",
245			Capabilities: []string{
246				"create",
247				"sudo",
248			},
249			RequiredParametersHCL: []string{"foo"},
250			Permissions: &ACLPermissions{
251				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
252				RequiredParameters: []string{"foo"},
253			},
254		},
255		{
256			Path: "test/mfa",
257			Capabilities: []string{
258				"create",
259				"sudo",
260			},
261			MFAMethodsHCL: []string{
262				"my_totp",
263				"my_totp2",
264			},
265			Permissions: &ACLPermissions{
266				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
267				MFAMethods: []string{
268					"my_totp",
269					"my_totp2",
270				},
271			},
272		},
273		{
274			Path: "test/+/segment",
275			Capabilities: []string{
276				"create",
277				"sudo",
278			},
279			Permissions: &ACLPermissions{
280				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
281			},
282			HasSegmentWildcards: true,
283		},
284		{
285			Path: "test/segment/at/end/+",
286			Capabilities: []string{
287				"create",
288				"sudo",
289			},
290			Permissions: &ACLPermissions{
291				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
292			},
293			HasSegmentWildcards: true,
294		},
295		{
296			Path: "test/segment/at/end/v2/+/",
297			Capabilities: []string{
298				"create",
299				"sudo",
300			},
301			Permissions: &ACLPermissions{
302				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
303			},
304			HasSegmentWildcards: true,
305		},
306		{
307			Path: "test/+/wildcard/+/*",
308			Capabilities: []string{
309				"create",
310				"sudo",
311			},
312			Permissions: &ACLPermissions{
313				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
314			},
315			HasSegmentWildcards: true,
316		},
317		{
318			Path: "test/+/wildcard/+/end*",
319			Capabilities: []string{
320				"create",
321				"sudo",
322			},
323			Permissions: &ACLPermissions{
324				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
325			},
326			HasSegmentWildcards: true,
327		},
328	}
329
330	if diff := deep.Equal(p.Paths, expect); diff != nil {
331		t.Error(diff)
332	}
333}
334
335func TestPolicy_ParseBadRoot(t *testing.T) {
336	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
337name = "test"
338bad  = "foo"
339nope = "yes"
340`))
341	if err == nil {
342		t.Fatalf("expected error")
343	}
344
345	if !strings.Contains(err.Error(), `invalid key "bad" on line 2`) {
346		t.Errorf("bad error: %q", err)
347	}
348
349	if !strings.Contains(err.Error(), `invalid key "nope" on line 3`) {
350		t.Errorf("bad error: %q", err)
351	}
352}
353
354func TestPolicy_ParseBadPath(t *testing.T) {
355	// The wrong spelling is intended here
356	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
357path "/" {
358	capabilities = ["read"]
359	capabilites  = ["read"]
360}
361`))
362	if err == nil {
363		t.Fatalf("expected error")
364	}
365
366	if !strings.Contains(err.Error(), `invalid key "capabilites" on line 3`) {
367		t.Errorf("bad error: %s", err)
368	}
369}
370
371func TestPolicy_ParseBadPolicy(t *testing.T) {
372	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
373path "/" {
374	policy = "banana"
375}
376`))
377	if err == nil {
378		t.Fatalf("expected error")
379	}
380
381	if !strings.Contains(err.Error(), `path "/": invalid policy "banana"`) {
382		t.Errorf("bad error: %s", err)
383	}
384}
385
386func TestPolicy_ParseBadWrapping(t *testing.T) {
387	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
388path "/" {
389	policy = "read"
390	min_wrapping_ttl = 400
391	max_wrapping_ttl = 200
392}
393`))
394	if err == nil {
395		t.Fatalf("expected error")
396	}
397
398	if !strings.Contains(err.Error(), `max_wrapping_ttl cannot be less than min_wrapping_ttl`) {
399		t.Errorf("bad error: %s", err)
400	}
401}
402
403func TestPolicy_ParseBadCapabilities(t *testing.T) {
404	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
405path "/" {
406	capabilities = ["read", "banana"]
407}
408`))
409	if err == nil {
410		t.Fatalf("expected error")
411	}
412
413	if !strings.Contains(err.Error(), `path "/": invalid capability "banana"`) {
414		t.Errorf("bad error: %s", err)
415	}
416}
417
418func TestPolicy_ParseBadSegmentWildcard(t *testing.T) {
419	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
420path "foo/+*" {
421	capabilities = ["read"]
422}
423`))
424	if err == nil {
425		t.Fatalf("expected error")
426	}
427
428	if !strings.Contains(err.Error(), `path "foo/+*": invalid use of wildcards ('+*' is forbidden)`) {
429		t.Errorf("bad error: %s", err)
430	}
431}
432