1package acl
2
3import (
4	"fmt"
5	"testing"
6
7	"github.com/stretchr/testify/mock"
8	"github.com/stretchr/testify/require"
9)
10
11type mockAuthorizer struct {
12	mock.Mock
13}
14
15var _ Authorizer = (*mockAuthorizer)(nil)
16
17// ACLRead checks for permission to list all the ACLs
18func (m *mockAuthorizer) ACLRead(ctx *AuthorizerContext) EnforcementDecision {
19	ret := m.Called(ctx)
20	return ret.Get(0).(EnforcementDecision)
21}
22
23// ACLWrite checks for permission to manipulate ACLs
24func (m *mockAuthorizer) ACLWrite(ctx *AuthorizerContext) EnforcementDecision {
25	ret := m.Called(ctx)
26	return ret.Get(0).(EnforcementDecision)
27}
28
29// AgentRead checks for permission to read from agent endpoints for a
30// given node.
31func (m *mockAuthorizer) AgentRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
32	ret := m.Called(segment, ctx)
33	return ret.Get(0).(EnforcementDecision)
34}
35
36// AgentWrite checks for permission to make changes via agent endpoints
37// for a given node.
38func (m *mockAuthorizer) AgentWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
39	ret := m.Called(segment, ctx)
40	return ret.Get(0).(EnforcementDecision)
41}
42
43// EventRead determines if a specific event can be queried.
44func (m *mockAuthorizer) EventRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
45	ret := m.Called(segment, ctx)
46	return ret.Get(0).(EnforcementDecision)
47}
48
49// EventWrite determines if a specific event may be fired.
50func (m *mockAuthorizer) EventWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
51	ret := m.Called(segment, ctx)
52	return ret.Get(0).(EnforcementDecision)
53}
54
55// IntentionDefaultAllow determines the default authorized behavior
56// when no intentions match a Connect request.
57func (m *mockAuthorizer) IntentionDefaultAllow(ctx *AuthorizerContext) EnforcementDecision {
58	ret := m.Called(ctx)
59	return ret.Get(0).(EnforcementDecision)
60}
61
62// IntentionRead determines if a specific intention can be read.
63func (m *mockAuthorizer) IntentionRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
64	ret := m.Called(segment, ctx)
65	return ret.Get(0).(EnforcementDecision)
66}
67
68// IntentionWrite determines if a specific intention can be
69// created, modified, or deleted.
70func (m *mockAuthorizer) IntentionWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
71	ret := m.Called(segment, ctx)
72	return ret.Get(0).(EnforcementDecision)
73}
74
75// KeyList checks for permission to list keys under a prefix
76func (m *mockAuthorizer) KeyList(segment string, ctx *AuthorizerContext) EnforcementDecision {
77	ret := m.Called(segment, ctx)
78	return ret.Get(0).(EnforcementDecision)
79}
80
81// KeyRead checks for permission to read a given key
82func (m *mockAuthorizer) KeyRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
83	ret := m.Called(segment, ctx)
84	return ret.Get(0).(EnforcementDecision)
85}
86
87// KeyWrite checks for permission to write a given key
88func (m *mockAuthorizer) KeyWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
89	ret := m.Called(segment, ctx)
90	return ret.Get(0).(EnforcementDecision)
91}
92
93// KeyWritePrefix checks for permission to write to an
94// entire key prefix. This means there must be no sub-policies
95// that deny a write.
96func (m *mockAuthorizer) KeyWritePrefix(segment string, ctx *AuthorizerContext) EnforcementDecision {
97	ret := m.Called(segment, ctx)
98	return ret.Get(0).(EnforcementDecision)
99}
100
101// KeyringRead determines if the encryption keyring used in
102// the gossip layer can be read.
103func (m *mockAuthorizer) KeyringRead(ctx *AuthorizerContext) EnforcementDecision {
104	ret := m.Called(ctx)
105	return ret.Get(0).(EnforcementDecision)
106}
107
108// KeyringWrite determines if the keyring can be manipulated
109func (m *mockAuthorizer) KeyringWrite(ctx *AuthorizerContext) EnforcementDecision {
110	ret := m.Called(ctx)
111	return ret.Get(0).(EnforcementDecision)
112}
113
114// NodeRead checks for permission to read (discover) a given node.
115func (m *mockAuthorizer) NodeRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
116	ret := m.Called(segment, ctx)
117	return ret.Get(0).(EnforcementDecision)
118}
119
120func (m *mockAuthorizer) NodeReadAll(ctx *AuthorizerContext) EnforcementDecision {
121	ret := m.Called(ctx)
122	return ret.Get(0).(EnforcementDecision)
123}
124
125// NodeWrite checks for permission to create or update (register) a
126// given node.
127func (m *mockAuthorizer) NodeWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
128	ret := m.Called(segment, ctx)
129	return ret.Get(0).(EnforcementDecision)
130}
131
132// OperatorRead determines if the read-only Consul operator functions
133// can be used.	ret := m.Called(segment, ctx)
134func (m *mockAuthorizer) OperatorRead(ctx *AuthorizerContext) EnforcementDecision {
135	ret := m.Called(ctx)
136	return ret.Get(0).(EnforcementDecision)
137}
138
139// OperatorWrite determines if the state-changing Consul operator
140// functions can be used.
141func (m *mockAuthorizer) OperatorWrite(ctx *AuthorizerContext) EnforcementDecision {
142	ret := m.Called(ctx)
143	return ret.Get(0).(EnforcementDecision)
144}
145
146// PreparedQueryRead determines if a specific prepared query can be read
147// to show its contents (this is not used for execution).
148func (m *mockAuthorizer) PreparedQueryRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
149	ret := m.Called(segment, ctx)
150	return ret.Get(0).(EnforcementDecision)
151}
152
153// PreparedQueryWrite determines if a specific prepared query can be
154// created, modified, or deleted.
155func (m *mockAuthorizer) PreparedQueryWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
156	ret := m.Called(segment, ctx)
157	return ret.Get(0).(EnforcementDecision)
158}
159
160// ServiceRead checks for permission to read a given service
161func (m *mockAuthorizer) ServiceRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
162	ret := m.Called(segment, ctx)
163	return ret.Get(0).(EnforcementDecision)
164}
165
166func (m *mockAuthorizer) ServiceReadAll(ctx *AuthorizerContext) EnforcementDecision {
167	ret := m.Called(ctx)
168	return ret.Get(0).(EnforcementDecision)
169}
170
171// ServiceWrite checks for permission to create or update a given
172// service
173func (m *mockAuthorizer) ServiceWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
174	ret := m.Called(segment, ctx)
175	return ret.Get(0).(EnforcementDecision)
176}
177
178// SessionRead checks for permission to read sessions for a given node.
179func (m *mockAuthorizer) SessionRead(segment string, ctx *AuthorizerContext) EnforcementDecision {
180	ret := m.Called(segment, ctx)
181	return ret.Get(0).(EnforcementDecision)
182}
183
184// SessionWrite checks for permission to create sessions for a given
185// node.
186func (m *mockAuthorizer) SessionWrite(segment string, ctx *AuthorizerContext) EnforcementDecision {
187	ret := m.Called(segment, ctx)
188	return ret.Get(0).(EnforcementDecision)
189}
190
191// Snapshot checks for permission to take and restore snapshots.
192func (m *mockAuthorizer) Snapshot(ctx *AuthorizerContext) EnforcementDecision {
193	ret := m.Called(ctx)
194	return ret.Get(0).(EnforcementDecision)
195}
196
197func TestACL_Enforce(t *testing.T) {
198	type testCase struct {
199		method   string
200		resource Resource
201		segment  string
202		access   string
203		ret      EnforcementDecision
204		err      string
205	}
206
207	testName := func(t testCase) string {
208		if t.segment != "" {
209			return fmt.Sprintf("%s/%s/%s/%s", t.resource, t.segment, t.access, t.ret.String())
210		}
211		return fmt.Sprintf("%s/%s/%s", t.resource, t.access, t.ret.String())
212	}
213
214	cases := []testCase{
215		{
216			method:   "ACLRead",
217			resource: ResourceACL,
218			access:   "read",
219			ret:      Deny,
220		},
221		{
222			method:   "ACLRead",
223			resource: ResourceACL,
224			access:   "read",
225			ret:      Allow,
226		},
227		{
228			method:   "ACLWrite",
229			resource: ResourceACL,
230			access:   "write",
231			ret:      Deny,
232		},
233		{
234			method:   "ACLWrite",
235			resource: ResourceACL,
236			access:   "write",
237			ret:      Allow,
238		},
239		{
240			resource: ResourceACL,
241			access:   "list",
242			ret:      Deny,
243			err:      "Invalid access level",
244		},
245		{
246			method:   "OperatorRead",
247			resource: ResourceOperator,
248			access:   "read",
249			ret:      Deny,
250		},
251		{
252			method:   "OperatorRead",
253			resource: ResourceOperator,
254			access:   "read",
255			ret:      Allow,
256		},
257		{
258			method:   "OperatorWrite",
259			resource: ResourceOperator,
260			access:   "write",
261			ret:      Deny,
262		},
263		{
264			method:   "OperatorWrite",
265			resource: ResourceOperator,
266			access:   "write",
267			ret:      Allow,
268		},
269		{
270			resource: ResourceOperator,
271			access:   "list",
272			ret:      Deny,
273			err:      "Invalid access level",
274		},
275		{
276			method:   "KeyringRead",
277			resource: ResourceKeyring,
278			access:   "read",
279			ret:      Deny,
280		},
281		{
282			method:   "KeyringRead",
283			resource: ResourceKeyring,
284			access:   "read",
285			ret:      Allow,
286		},
287		{
288			method:   "KeyringWrite",
289			resource: ResourceKeyring,
290			access:   "write",
291			ret:      Deny,
292		},
293		{
294			method:   "KeyringWrite",
295			resource: ResourceKeyring,
296			access:   "write",
297			ret:      Allow,
298		},
299		{
300			resource: ResourceKeyring,
301			access:   "list",
302			ret:      Deny,
303			err:      "Invalid access level",
304		},
305		{
306			method:   "AgentRead",
307			resource: ResourceAgent,
308			segment:  "foo",
309			access:   "read",
310			ret:      Deny,
311		},
312		{
313			method:   "AgentRead",
314			resource: ResourceAgent,
315			segment:  "foo",
316			access:   "read",
317			ret:      Allow,
318		},
319		{
320			method:   "AgentWrite",
321			resource: ResourceAgent,
322			segment:  "foo",
323			access:   "write",
324			ret:      Deny,
325		},
326		{
327			method:   "AgentWrite",
328			resource: ResourceAgent,
329			segment:  "foo",
330			access:   "write",
331			ret:      Allow,
332		},
333		{
334			resource: ResourceAgent,
335			segment:  "foo",
336			access:   "list",
337			ret:      Deny,
338			err:      "Invalid access level",
339		},
340		{
341			method:   "EventRead",
342			resource: ResourceEvent,
343			segment:  "foo",
344			access:   "read",
345			ret:      Deny,
346		},
347		{
348			method:   "EventRead",
349			resource: ResourceEvent,
350			segment:  "foo",
351			access:   "read",
352			ret:      Allow,
353		},
354		{
355			method:   "EventWrite",
356			resource: ResourceEvent,
357			segment:  "foo",
358			access:   "write",
359			ret:      Deny,
360		},
361		{
362			method:   "EventWrite",
363			resource: ResourceEvent,
364			segment:  "foo",
365			access:   "write",
366			ret:      Allow,
367		},
368		{
369			resource: ResourceEvent,
370			segment:  "foo",
371			access:   "list",
372			ret:      Deny,
373			err:      "Invalid access level",
374		},
375		{
376			method:   "IntentionRead",
377			resource: ResourceIntention,
378			segment:  "foo",
379			access:   "read",
380			ret:      Deny,
381		},
382		{
383			method:   "IntentionRead",
384			resource: ResourceIntention,
385			segment:  "foo",
386			access:   "read",
387			ret:      Allow,
388		},
389		{
390			method:   "IntentionWrite",
391			resource: ResourceIntention,
392			segment:  "foo",
393			access:   "write",
394			ret:      Deny,
395		},
396		{
397			method:   "IntentionWrite",
398			resource: ResourceIntention,
399			segment:  "foo",
400			access:   "write",
401			ret:      Allow,
402		},
403		{
404			resource: ResourceIntention,
405			segment:  "foo",
406			access:   "list",
407			ret:      Deny,
408			err:      "Invalid access level",
409		},
410		{
411			method:   "NodeRead",
412			resource: ResourceNode,
413			segment:  "foo",
414			access:   "read",
415			ret:      Deny,
416		},
417		{
418			method:   "NodeRead",
419			resource: ResourceNode,
420			segment:  "foo",
421			access:   "read",
422			ret:      Allow,
423		},
424		{
425			method:   "NodeWrite",
426			resource: ResourceNode,
427			segment:  "foo",
428			access:   "write",
429			ret:      Deny,
430		},
431		{
432			method:   "NodeWrite",
433			resource: ResourceNode,
434			segment:  "foo",
435			access:   "write",
436			ret:      Allow,
437		},
438		{
439			resource: ResourceNode,
440			segment:  "foo",
441			access:   "list",
442			ret:      Deny,
443			err:      "Invalid access level",
444		},
445		{
446			method:   "PreparedQueryRead",
447			resource: ResourceQuery,
448			segment:  "foo",
449			access:   "read",
450			ret:      Deny,
451		},
452		{
453			method:   "PreparedQueryRead",
454			resource: ResourceQuery,
455			segment:  "foo",
456			access:   "read",
457			ret:      Allow,
458		},
459		{
460			method:   "PreparedQueryWrite",
461			resource: ResourceQuery,
462			segment:  "foo",
463			access:   "write",
464			ret:      Deny,
465		},
466		{
467			method:   "PreparedQueryWrite",
468			resource: ResourceQuery,
469			segment:  "foo",
470			access:   "write",
471			ret:      Allow,
472		},
473		{
474			resource: ResourceQuery,
475			segment:  "foo",
476			access:   "list",
477			ret:      Deny,
478			err:      "Invalid access level",
479		},
480		{
481			method:   "ServiceRead",
482			resource: ResourceService,
483			segment:  "foo",
484			access:   "read",
485			ret:      Deny,
486		},
487		{
488			method:   "ServiceRead",
489			resource: ResourceService,
490			segment:  "foo",
491			access:   "read",
492			ret:      Allow,
493		},
494		{
495			method:   "ServiceWrite",
496			resource: ResourceService,
497			segment:  "foo",
498			access:   "write",
499			ret:      Deny,
500		},
501		{
502			method:   "ServiceWrite",
503			resource: ResourceService,
504			segment:  "foo",
505			access:   "write",
506			ret:      Allow,
507		},
508		{
509			resource: ResourceSession,
510			segment:  "foo",
511			access:   "list",
512			ret:      Deny,
513			err:      "Invalid access level",
514		},
515		{
516			method:   "SessionRead",
517			resource: ResourceSession,
518			segment:  "foo",
519			access:   "read",
520			ret:      Deny,
521		},
522		{
523			method:   "SessionRead",
524			resource: ResourceSession,
525			segment:  "foo",
526			access:   "read",
527			ret:      Allow,
528		},
529		{
530			method:   "SessionWrite",
531			resource: ResourceSession,
532			segment:  "foo",
533			access:   "write",
534			ret:      Deny,
535		},
536		{
537			method:   "SessionWrite",
538			resource: ResourceSession,
539			segment:  "foo",
540			access:   "write",
541			ret:      Allow,
542		},
543		{
544			resource: ResourceSession,
545			segment:  "foo",
546			access:   "list",
547			ret:      Deny,
548			err:      "Invalid access level",
549		},
550		{
551			method:   "KeyRead",
552			resource: ResourceKey,
553			segment:  "foo",
554			access:   "read",
555			ret:      Deny,
556		},
557		{
558			method:   "KeyRead",
559			resource: ResourceKey,
560			segment:  "foo",
561			access:   "read",
562			ret:      Allow,
563		},
564		{
565			method:   "KeyWrite",
566			resource: ResourceKey,
567			segment:  "foo",
568			access:   "write",
569			ret:      Deny,
570		},
571		{
572			method:   "KeyWrite",
573			resource: ResourceKey,
574			segment:  "foo",
575			access:   "write",
576			ret:      Allow,
577		},
578		{
579			method:   "KeyList",
580			resource: ResourceKey,
581			segment:  "foo",
582			access:   "list",
583			ret:      Deny,
584		},
585		{
586			method:   "KeyList",
587			resource: ResourceKey,
588			segment:  "foo",
589			access:   "list",
590			ret:      Allow,
591		},
592		{
593			resource: ResourceKey,
594			segment:  "foo",
595			access:   "deny",
596			ret:      Deny,
597			err:      "Invalid access level",
598		},
599		{
600			resource: "not-a-real-resource",
601			access:   "read",
602			ret:      Deny,
603			err:      "Invalid ACL resource requested:",
604		},
605	}
606
607	for _, tcase := range cases {
608		t.Run(testName(tcase), func(t *testing.T) {
609			m := &mockAuthorizer{}
610
611			if tcase.err == "" {
612				var nilCtx *AuthorizerContext
613				if tcase.segment != "" {
614					m.On(tcase.method, tcase.segment, nilCtx).Return(tcase.ret)
615				} else {
616					m.On(tcase.method, nilCtx).Return(tcase.ret)
617				}
618			}
619
620			ret, err := Enforce(m, tcase.resource, tcase.segment, tcase.access, nil)
621			if tcase.err == "" {
622				require.NoError(t, err)
623			} else {
624				require.Error(t, err)
625				require.Contains(t, err.Error(), tcase.err)
626			}
627			require.Equal(t, tcase.ret, ret)
628			m.AssertExpectations(t)
629		})
630	}
631}
632