1package consul
2
3import (
4	"os"
5	"testing"
6	"time"
7
8	"github.com/hashicorp/consul/acl"
9	"github.com/hashicorp/consul/agent/structs"
10	"github.com/hashicorp/consul/testrpc"
11	msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/require"
14)
15
16// Test basic creation
17func TestIntentionApply_new(t *testing.T) {
18	t.Parallel()
19
20	assert := assert.New(t)
21	dir1, s1 := testServer(t)
22	defer os.RemoveAll(dir1)
23	defer s1.Shutdown()
24	codec := rpcClient(t, s1)
25	defer codec.Close()
26
27	testrpc.WaitForLeader(t, s1.RPC, "dc1")
28
29	// Setup a basic record to create
30	ixn := structs.IntentionRequest{
31		Datacenter: "dc1",
32		Op:         structs.IntentionOpCreate,
33		Intention: &structs.Intention{
34			SourceNS:        structs.IntentionDefaultNamespace,
35			SourceName:      "test",
36			DestinationNS:   structs.IntentionDefaultNamespace,
37			DestinationName: "test",
38			Action:          structs.IntentionActionAllow,
39			SourceType:      structs.IntentionSourceConsul,
40			Meta:            map[string]string{},
41		},
42	}
43	var reply string
44
45	// Record now to check created at time
46	now := time.Now()
47
48	// Create
49	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
50	assert.NotEmpty(reply)
51
52	// Read
53	ixn.Intention.ID = reply
54	{
55		req := &structs.IntentionQueryRequest{
56			Datacenter:  "dc1",
57			IntentionID: ixn.Intention.ID,
58		}
59		var resp structs.IndexedIntentions
60		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
61		assert.Len(resp.Intentions, 1)
62		actual := resp.Intentions[0]
63		assert.Equal(resp.Index, actual.ModifyIndex)
64		assert.WithinDuration(now, actual.CreatedAt, 5*time.Second)
65		assert.WithinDuration(now, actual.UpdatedAt, 5*time.Second)
66
67		actual.CreateIndex, actual.ModifyIndex = 0, 0
68		actual.CreatedAt = ixn.Intention.CreatedAt
69		actual.UpdatedAt = ixn.Intention.UpdatedAt
70		actual.Hash = ixn.Intention.Hash
71		ixn.Intention.UpdatePrecedence()
72		assert.Equal(ixn.Intention, actual)
73	}
74}
75
76// Test the source type defaults
77func TestIntentionApply_defaultSourceType(t *testing.T) {
78	t.Parallel()
79
80	assert := assert.New(t)
81	dir1, s1 := testServer(t)
82	defer os.RemoveAll(dir1)
83	defer s1.Shutdown()
84	codec := rpcClient(t, s1)
85	defer codec.Close()
86
87	testrpc.WaitForLeader(t, s1.RPC, "dc1")
88
89	// Setup a basic record to create
90	ixn := structs.IntentionRequest{
91		Datacenter: "dc1",
92		Op:         structs.IntentionOpCreate,
93		Intention: &structs.Intention{
94			SourceNS:        structs.IntentionDefaultNamespace,
95			SourceName:      "test",
96			DestinationNS:   structs.IntentionDefaultNamespace,
97			DestinationName: "test",
98			Action:          structs.IntentionActionAllow,
99		},
100	}
101	var reply string
102
103	// Create
104	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
105	assert.NotEmpty(reply)
106
107	// Read
108	ixn.Intention.ID = reply
109	{
110		req := &structs.IntentionQueryRequest{
111			Datacenter:  "dc1",
112			IntentionID: ixn.Intention.ID,
113		}
114		var resp structs.IndexedIntentions
115		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
116		assert.Len(resp.Intentions, 1)
117		actual := resp.Intentions[0]
118		assert.Equal(structs.IntentionSourceConsul, actual.SourceType)
119	}
120}
121
122// Shouldn't be able to create with an ID set
123func TestIntentionApply_createWithID(t *testing.T) {
124	t.Parallel()
125
126	assert := assert.New(t)
127	dir1, s1 := testServer(t)
128	defer os.RemoveAll(dir1)
129	defer s1.Shutdown()
130	codec := rpcClient(t, s1)
131	defer codec.Close()
132
133	testrpc.WaitForLeader(t, s1.RPC, "dc1")
134
135	// Setup a basic record to create
136	ixn := structs.IntentionRequest{
137		Datacenter: "dc1",
138		Op:         structs.IntentionOpCreate,
139		Intention: &structs.Intention{
140			ID:         generateUUID(),
141			SourceName: "test",
142		},
143	}
144	var reply string
145
146	// Create
147	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
148	assert.NotNil(err)
149	assert.Contains(err, "ID must be empty")
150}
151
152// Test basic updating
153func TestIntentionApply_updateGood(t *testing.T) {
154	t.Parallel()
155
156	assert := assert.New(t)
157	dir1, s1 := testServer(t)
158	defer os.RemoveAll(dir1)
159	defer s1.Shutdown()
160	codec := rpcClient(t, s1)
161	defer codec.Close()
162
163	testrpc.WaitForLeader(t, s1.RPC, "dc1")
164
165	// Setup a basic record to create
166	ixn := structs.IntentionRequest{
167		Datacenter: "dc1",
168		Op:         structs.IntentionOpCreate,
169		Intention: &structs.Intention{
170			SourceNS:        structs.IntentionDefaultNamespace,
171			SourceName:      "test",
172			DestinationNS:   structs.IntentionDefaultNamespace,
173			DestinationName: "test",
174			Action:          structs.IntentionActionAllow,
175			SourceType:      structs.IntentionSourceConsul,
176			Meta:            map[string]string{},
177		},
178	}
179	var reply string
180
181	// Create
182	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
183	assert.NotEmpty(reply)
184
185	// Read CreatedAt
186	var createdAt time.Time
187	ixn.Intention.ID = reply
188	{
189		req := &structs.IntentionQueryRequest{
190			Datacenter:  "dc1",
191			IntentionID: ixn.Intention.ID,
192		}
193		var resp structs.IndexedIntentions
194		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
195		assert.Len(resp.Intentions, 1)
196		actual := resp.Intentions[0]
197		createdAt = actual.CreatedAt
198	}
199
200	// Sleep a bit so that the updated at will definitely be different, not much
201	time.Sleep(1 * time.Millisecond)
202
203	// Update
204	ixn.Op = structs.IntentionOpUpdate
205	ixn.Intention.ID = reply
206	ixn.Intention.SourceName = "*"
207	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
208
209	// Read
210	ixn.Intention.ID = reply
211	{
212		req := &structs.IntentionQueryRequest{
213			Datacenter:  "dc1",
214			IntentionID: ixn.Intention.ID,
215		}
216		var resp structs.IndexedIntentions
217		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
218		assert.Len(resp.Intentions, 1)
219		actual := resp.Intentions[0]
220		assert.Equal(createdAt, actual.CreatedAt)
221		assert.WithinDuration(time.Now(), actual.UpdatedAt, 5*time.Second)
222
223		actual.CreateIndex, actual.ModifyIndex = 0, 0
224		actual.CreatedAt = ixn.Intention.CreatedAt
225		actual.UpdatedAt = ixn.Intention.UpdatedAt
226		actual.Hash = ixn.Intention.Hash
227		ixn.Intention.UpdatePrecedence()
228		assert.Equal(ixn.Intention, actual)
229	}
230}
231
232// Shouldn't be able to update a non-existent intention
233func TestIntentionApply_updateNonExist(t *testing.T) {
234	t.Parallel()
235
236	assert := assert.New(t)
237	dir1, s1 := testServer(t)
238	defer os.RemoveAll(dir1)
239	defer s1.Shutdown()
240	codec := rpcClient(t, s1)
241	defer codec.Close()
242
243	testrpc.WaitForLeader(t, s1.RPC, "dc1")
244
245	// Setup a basic record to create
246	ixn := structs.IntentionRequest{
247		Datacenter: "dc1",
248		Op:         structs.IntentionOpUpdate,
249		Intention: &structs.Intention{
250			ID:         generateUUID(),
251			SourceName: "test",
252		},
253	}
254	var reply string
255
256	// Create
257	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
258	assert.NotNil(err)
259	assert.Contains(err, "Cannot modify non-existent intention")
260}
261
262// Test basic deleting
263func TestIntentionApply_deleteGood(t *testing.T) {
264	t.Parallel()
265
266	assert := assert.New(t)
267	dir1, s1 := testServer(t)
268	defer os.RemoveAll(dir1)
269	defer s1.Shutdown()
270	codec := rpcClient(t, s1)
271	defer codec.Close()
272
273	testrpc.WaitForLeader(t, s1.RPC, "dc1")
274
275	// Setup a basic record to create
276	ixn := structs.IntentionRequest{
277		Datacenter: "dc1",
278		Op:         structs.IntentionOpCreate,
279		Intention: &structs.Intention{
280			SourceNS:        "test",
281			SourceName:      "test",
282			DestinationNS:   "test",
283			DestinationName: "test",
284			Action:          structs.IntentionActionAllow,
285		},
286	}
287	var reply string
288
289	// Create
290	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
291	assert.NotEmpty(reply)
292
293	// Delete
294	ixn.Op = structs.IntentionOpDelete
295	ixn.Intention.ID = reply
296	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
297
298	// Read
299	ixn.Intention.ID = reply
300	{
301		req := &structs.IntentionQueryRequest{
302			Datacenter:  "dc1",
303			IntentionID: ixn.Intention.ID,
304		}
305		var resp structs.IndexedIntentions
306		err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp)
307		assert.NotNil(err)
308		assert.Contains(err, ErrIntentionNotFound.Error())
309	}
310}
311
312// Test apply with a deny ACL
313func TestIntentionApply_aclDeny(t *testing.T) {
314	t.Parallel()
315
316	assert := assert.New(t)
317	dir1, s1 := testServerWithConfig(t, func(c *Config) {
318		c.ACLDatacenter = "dc1"
319		c.ACLsEnabled = true
320		c.ACLMasterToken = "root"
321		c.ACLDefaultPolicy = "deny"
322	})
323	defer os.RemoveAll(dir1)
324	defer s1.Shutdown()
325	codec := rpcClient(t, s1)
326	defer codec.Close()
327
328	testrpc.WaitForLeader(t, s1.RPC, "dc1")
329
330	// Create an ACL with write permissions
331	var token string
332	{
333		var rules = `
334service "foo" {
335	policy = "deny"
336	intentions = "write"
337}`
338
339		req := structs.ACLRequest{
340			Datacenter: "dc1",
341			Op:         structs.ACLSet,
342			ACL: structs.ACL{
343				Name:  "User token",
344				Type:  structs.ACLTokenTypeClient,
345				Rules: rules,
346			},
347			WriteRequest: structs.WriteRequest{Token: "root"},
348		}
349		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
350	}
351
352	// Setup a basic record to create
353	ixn := structs.IntentionRequest{
354		Datacenter: "dc1",
355		Op:         structs.IntentionOpCreate,
356		Intention:  structs.TestIntention(t),
357	}
358	ixn.Intention.DestinationName = "foobar"
359
360	// Create without a token should error since default deny
361	var reply string
362	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
363	assert.True(acl.IsErrPermissionDenied(err))
364
365	// Now add the token and try again.
366	ixn.WriteRequest.Token = token
367	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
368
369	// Read
370	ixn.Intention.ID = reply
371	{
372		req := &structs.IntentionQueryRequest{
373			Datacenter:   "dc1",
374			IntentionID:  ixn.Intention.ID,
375			QueryOptions: structs.QueryOptions{Token: "root"},
376		}
377		var resp structs.IndexedIntentions
378		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
379		assert.Len(resp.Intentions, 1)
380		actual := resp.Intentions[0]
381		assert.Equal(resp.Index, actual.ModifyIndex)
382
383		actual.CreateIndex, actual.ModifyIndex = 0, 0
384		actual.CreatedAt = ixn.Intention.CreatedAt
385		actual.UpdatedAt = ixn.Intention.UpdatedAt
386		actual.Hash = ixn.Intention.Hash
387		ixn.Intention.UpdatePrecedence()
388		assert.Equal(ixn.Intention, actual)
389	}
390}
391
392func TestIntention_WildcardACLEnforcement(t *testing.T) {
393	t.Parallel()
394
395	_, srv, codec := testACLServerWithConfig(t, nil, false)
396	waitForLeaderEstablishment(t, srv)
397
398	// create some test policies.
399
400	writeToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service_prefix "" { policy = "deny" intentions = "write" }`)
401	require.NoError(t, err)
402	readToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service_prefix "" { policy = "deny" intentions = "read" }`)
403	require.NoError(t, err)
404	exactToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service "*" { policy = "deny" intentions = "write" }`)
405	require.NoError(t, err)
406	wildcardPrefixToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service_prefix "*" { policy = "deny" intentions = "write" }`)
407	require.NoError(t, err)
408	fooToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service "foo" { policy = "deny" intentions = "write" }`)
409	require.NoError(t, err)
410	denyToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service_prefix "" { policy = "deny" intentions = "deny" }`)
411	require.NoError(t, err)
412
413	doIntentionCreate := func(t *testing.T, token string, deny bool) string {
414		t.Helper()
415		ixn := structs.IntentionRequest{
416			Datacenter: "dc1",
417			Op:         structs.IntentionOpCreate,
418			Intention: &structs.Intention{
419				SourceNS:        "default",
420				SourceName:      "*",
421				DestinationNS:   "default",
422				DestinationName: "*",
423				Action:          structs.IntentionActionAllow,
424				SourceType:      structs.IntentionSourceConsul,
425			},
426			WriteRequest: structs.WriteRequest{Token: token},
427		}
428		var reply string
429		err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
430		if deny {
431			require.Error(t, err)
432			require.True(t, acl.IsErrPermissionDenied(err))
433			return ""
434		} else {
435			require.NoError(t, err)
436			require.NotEmpty(t, reply)
437			return reply
438		}
439	}
440
441	t.Run("deny-write-for-read-token", func(t *testing.T) {
442		// This tests ensures that tokens with only read access to all intentions
443		// cannot create a wildcard intention
444		doIntentionCreate(t, readToken.SecretID, true)
445	})
446
447	t.Run("deny-write-for-exact-wildcard-rule", func(t *testing.T) {
448		// This test ensures that having a rules like:
449		// service "*" {
450		//    intentions = "write"
451		// }
452		// will not actually allow creating an intention with a wildcard service name
453		doIntentionCreate(t, exactToken.SecretID, true)
454	})
455
456	t.Run("deny-write-for-prefix-wildcard-rule", func(t *testing.T) {
457		// This test ensures that having a rules like:
458		// service_prefix "*" {
459		//    intentions = "write"
460		// }
461		// will not actually allow creating an intention with a wildcard service name
462		doIntentionCreate(t, wildcardPrefixToken.SecretID, true)
463	})
464
465	var intentionID string
466	allowWriteOk := t.Run("allow-write", func(t *testing.T) {
467		// tests that a token with all the required privileges can create
468		// intentions with a wildcard destination
469		intentionID = doIntentionCreate(t, writeToken.SecretID, false)
470	})
471
472	requireAllowWrite := func(t *testing.T) {
473		t.Helper()
474		if !allowWriteOk {
475			t.Skip("Skipping because the allow-write subtest failed")
476		}
477	}
478
479	doIntentionRead := func(t *testing.T, token string, deny bool) {
480		t.Helper()
481		requireAllowWrite(t)
482		req := &structs.IntentionQueryRequest{
483			Datacenter:   "dc1",
484			IntentionID:  intentionID,
485			QueryOptions: structs.QueryOptions{Token: token},
486		}
487
488		var resp structs.IndexedIntentions
489		err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp)
490		if deny {
491			require.Error(t, err)
492			require.True(t, acl.IsErrPermissionDenied(err))
493		} else {
494			require.NoError(t, err)
495			require.Len(t, resp.Intentions, 1)
496			require.Equal(t, "*", resp.Intentions[0].DestinationName)
497		}
498	}
499
500	t.Run("allow-read-for-write-token", func(t *testing.T) {
501		doIntentionRead(t, writeToken.SecretID, false)
502	})
503
504	t.Run("allow-read-for-read-token", func(t *testing.T) {
505		doIntentionRead(t, readToken.SecretID, false)
506	})
507
508	t.Run("allow-read-for-exact-wildcard-token", func(t *testing.T) {
509		// this is allowed because, the effect of the policy is to grant
510		// intention:write on the service named "*". When reading the
511		// intention we will validate that the token has read permissions
512		// for any intention that would match the wildcard.
513		doIntentionRead(t, exactToken.SecretID, false)
514	})
515
516	t.Run("allow-read-for-prefix-wildcard-token", func(t *testing.T) {
517		// this is allowed for the same reasons as for the
518		// exact-wildcard-token case
519		doIntentionRead(t, wildcardPrefixToken.SecretID, false)
520	})
521
522	t.Run("deny-read-for-deny-token", func(t *testing.T) {
523		doIntentionRead(t, denyToken.SecretID, true)
524	})
525
526	doIntentionList := func(t *testing.T, token string, deny bool) {
527		t.Helper()
528		requireAllowWrite(t)
529		req := &structs.DCSpecificRequest{
530			Datacenter:   "dc1",
531			QueryOptions: structs.QueryOptions{Token: token},
532		}
533
534		var resp structs.IndexedIntentions
535		err := msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp)
536		// even with permission denied this should return success but with an empty list
537		require.NoError(t, err)
538		if deny {
539			require.Empty(t, resp.Intentions)
540		} else {
541			require.Len(t, resp.Intentions, 1)
542			require.Equal(t, "*", resp.Intentions[0].DestinationName)
543		}
544	}
545
546	t.Run("allow-list-for-write-token", func(t *testing.T) {
547		doIntentionList(t, writeToken.SecretID, false)
548	})
549
550	t.Run("allow-list-for-read-token", func(t *testing.T) {
551		doIntentionList(t, readToken.SecretID, false)
552	})
553
554	t.Run("allow-list-for-exact-wildcard-token", func(t *testing.T) {
555		doIntentionList(t, exactToken.SecretID, false)
556	})
557
558	t.Run("allow-list-for-prefix-wildcard-token", func(t *testing.T) {
559		doIntentionList(t, wildcardPrefixToken.SecretID, false)
560	})
561
562	t.Run("deny-list-for-deny-token", func(t *testing.T) {
563		doIntentionList(t, denyToken.SecretID, true)
564	})
565
566	doIntentionMatch := func(t *testing.T, token string, deny bool) {
567		t.Helper()
568		requireAllowWrite(t)
569		req := &structs.IntentionQueryRequest{
570			Datacenter: "dc1",
571			Match: &structs.IntentionQueryMatch{
572				Type: structs.IntentionMatchDestination,
573				Entries: []structs.IntentionMatchEntry{
574					structs.IntentionMatchEntry{
575						Namespace: "default",
576						Name:      "*",
577					},
578				},
579			},
580			QueryOptions: structs.QueryOptions{Token: token},
581		}
582
583		var resp structs.IndexedIntentionMatches
584		err := msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp)
585		if deny {
586			require.Error(t, err)
587			require.Empty(t, resp.Matches)
588		} else {
589			require.NoError(t, err)
590			require.Len(t, resp.Matches, 1)
591			require.Len(t, resp.Matches[0], 1)
592			require.Equal(t, "*", resp.Matches[0][0].DestinationName)
593		}
594	}
595
596	t.Run("allow-match-for-write-token", func(t *testing.T) {
597		doIntentionMatch(t, writeToken.SecretID, false)
598	})
599
600	t.Run("allow-match-for-read-token", func(t *testing.T) {
601		doIntentionMatch(t, readToken.SecretID, false)
602	})
603
604	t.Run("allow-match-for-exact-wildcard-token", func(t *testing.T) {
605		doIntentionMatch(t, exactToken.SecretID, false)
606	})
607
608	t.Run("allow-match-for-prefix-wildcard-token", func(t *testing.T) {
609		doIntentionMatch(t, wildcardPrefixToken.SecretID, false)
610	})
611
612	t.Run("deny-match-for-deny-token", func(t *testing.T) {
613		doIntentionMatch(t, denyToken.SecretID, true)
614	})
615
616	doIntentionUpdate := func(t *testing.T, token string, dest string, deny bool) {
617		t.Helper()
618		requireAllowWrite(t)
619		ixn := structs.IntentionRequest{
620			Datacenter: "dc1",
621			Op:         structs.IntentionOpUpdate,
622			Intention: &structs.Intention{
623				ID:              intentionID,
624				SourceNS:        "default",
625				SourceName:      "*",
626				DestinationNS:   "default",
627				DestinationName: dest,
628				Action:          structs.IntentionActionAllow,
629				SourceType:      structs.IntentionSourceConsul,
630			},
631			WriteRequest: structs.WriteRequest{Token: token},
632		}
633		var reply string
634		err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
635		if deny {
636			require.Error(t, err)
637			require.True(t, acl.IsErrPermissionDenied(err))
638		} else {
639			require.NoError(t, err)
640		}
641	}
642
643	t.Run("deny-update-for-foo-token", func(t *testing.T) {
644		doIntentionUpdate(t, fooToken.SecretID, "foo", true)
645	})
646
647	t.Run("allow-update-for-prefix-token", func(t *testing.T) {
648		// this tests that regardless of going from a wildcard intention
649		// to a non-wildcard or the opposite direction that the permissions
650		// are checked correctly. This also happens to leave the intention
651		// in a state ready for verifying similar things with deletion
652		doIntentionUpdate(t, writeToken.SecretID, "foo", false)
653		doIntentionUpdate(t, writeToken.SecretID, "*", false)
654	})
655
656	doIntentionDelete := func(t *testing.T, token string, deny bool) {
657		t.Helper()
658		requireAllowWrite(t)
659		ixn := structs.IntentionRequest{
660			Datacenter: "dc1",
661			Op:         structs.IntentionOpDelete,
662			Intention: &structs.Intention{
663				ID: intentionID,
664			},
665			WriteRequest: structs.WriteRequest{Token: token},
666		}
667		var reply string
668		err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
669		if deny {
670			require.Error(t, err)
671			require.True(t, acl.IsErrPermissionDenied(err))
672		} else {
673			require.NoError(t, err)
674		}
675	}
676
677	t.Run("deny-delete-for-read-token", func(t *testing.T) {
678		doIntentionDelete(t, readToken.SecretID, true)
679	})
680
681	t.Run("deny-delete-for-exact-wildcard-rule", func(t *testing.T) {
682		// This test ensures that having a rules like:
683		// service "*" {
684		//    intentions = "write"
685		// }
686		// will not actually allow deleting an intention with a wildcard service name
687		doIntentionDelete(t, exactToken.SecretID, true)
688	})
689
690	t.Run("deny-delete-for-prefix-wildcard-rule", func(t *testing.T) {
691		// This test ensures that having a rules like:
692		// service_prefix "*" {
693		//    intentions = "write"
694		// }
695		// will not actually allow creating an intention with a wildcard service name
696		doIntentionDelete(t, wildcardPrefixToken.SecretID, true)
697	})
698
699	t.Run("allow-delete", func(t *testing.T) {
700		// tests that a token with all the required privileges can delete
701		// intentions with a wildcard destination
702		doIntentionDelete(t, writeToken.SecretID, false)
703	})
704}
705
706// Test apply with delete and a default deny ACL
707func TestIntentionApply_aclDelete(t *testing.T) {
708	t.Parallel()
709
710	assert := assert.New(t)
711	dir1, s1 := testServerWithConfig(t, func(c *Config) {
712		c.ACLDatacenter = "dc1"
713		c.ACLsEnabled = true
714		c.ACLMasterToken = "root"
715		c.ACLDefaultPolicy = "deny"
716	})
717	defer os.RemoveAll(dir1)
718	defer s1.Shutdown()
719	codec := rpcClient(t, s1)
720	defer codec.Close()
721
722	testrpc.WaitForLeader(t, s1.RPC, "dc1")
723
724	// Create an ACL with write permissions
725	var token string
726	{
727		var rules = `
728service "foo" {
729	policy = "deny"
730	intentions = "write"
731}`
732
733		req := structs.ACLRequest{
734			Datacenter: "dc1",
735			Op:         structs.ACLSet,
736			ACL: structs.ACL{
737				Name:  "User token",
738				Type:  structs.ACLTokenTypeClient,
739				Rules: rules,
740			},
741			WriteRequest: structs.WriteRequest{Token: "root"},
742		}
743		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
744	}
745
746	// Setup a basic record to create
747	ixn := structs.IntentionRequest{
748		Datacenter: "dc1",
749		Op:         structs.IntentionOpCreate,
750		Intention:  structs.TestIntention(t),
751	}
752	ixn.Intention.DestinationName = "foobar"
753	ixn.WriteRequest.Token = token
754
755	// Create
756	var reply string
757	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
758
759	// Try to do a delete with no token; this should get rejected.
760	ixn.Op = structs.IntentionOpDelete
761	ixn.Intention.ID = reply
762	ixn.WriteRequest.Token = ""
763	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
764	assert.True(acl.IsErrPermissionDenied(err))
765
766	// Try again with the original token. This should go through.
767	ixn.WriteRequest.Token = token
768	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
769
770	// Verify it is gone
771	{
772		req := &structs.IntentionQueryRequest{
773			Datacenter:  "dc1",
774			IntentionID: ixn.Intention.ID,
775		}
776		var resp structs.IndexedIntentions
777		err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp)
778		assert.NotNil(err)
779		assert.Contains(err.Error(), ErrIntentionNotFound.Error())
780	}
781}
782
783// Test apply with update and a default deny ACL
784func TestIntentionApply_aclUpdate(t *testing.T) {
785	t.Parallel()
786
787	assert := assert.New(t)
788	dir1, s1 := testServerWithConfig(t, func(c *Config) {
789		c.ACLDatacenter = "dc1"
790		c.ACLsEnabled = true
791		c.ACLMasterToken = "root"
792		c.ACLDefaultPolicy = "deny"
793	})
794	defer os.RemoveAll(dir1)
795	defer s1.Shutdown()
796	codec := rpcClient(t, s1)
797	defer codec.Close()
798
799	testrpc.WaitForLeader(t, s1.RPC, "dc1")
800
801	// Create an ACL with write permissions
802	var token string
803	{
804		var rules = `
805service "foo" {
806	policy = "deny"
807	intentions = "write"
808}`
809
810		req := structs.ACLRequest{
811			Datacenter: "dc1",
812			Op:         structs.ACLSet,
813			ACL: structs.ACL{
814				Name:  "User token",
815				Type:  structs.ACLTokenTypeClient,
816				Rules: rules,
817			},
818			WriteRequest: structs.WriteRequest{Token: "root"},
819		}
820		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
821	}
822
823	// Setup a basic record to create
824	ixn := structs.IntentionRequest{
825		Datacenter: "dc1",
826		Op:         structs.IntentionOpCreate,
827		Intention:  structs.TestIntention(t),
828	}
829	ixn.Intention.DestinationName = "foobar"
830	ixn.WriteRequest.Token = token
831
832	// Create
833	var reply string
834	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
835
836	// Try to do an update without a token; this should get rejected.
837	ixn.Op = structs.IntentionOpUpdate
838	ixn.Intention.ID = reply
839	ixn.WriteRequest.Token = ""
840	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
841	assert.True(acl.IsErrPermissionDenied(err))
842
843	// Try again with the original token; this should go through.
844	ixn.WriteRequest.Token = token
845	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
846}
847
848// Test apply with a management token
849func TestIntentionApply_aclManagement(t *testing.T) {
850	t.Parallel()
851
852	assert := assert.New(t)
853	dir1, s1 := testServerWithConfig(t, func(c *Config) {
854		c.ACLDatacenter = "dc1"
855		c.ACLsEnabled = true
856		c.ACLMasterToken = "root"
857		c.ACLDefaultPolicy = "deny"
858	})
859	defer os.RemoveAll(dir1)
860	defer s1.Shutdown()
861	codec := rpcClient(t, s1)
862	defer codec.Close()
863
864	testrpc.WaitForLeader(t, s1.RPC, "dc1")
865
866	// Setup a basic record to create
867	ixn := structs.IntentionRequest{
868		Datacenter: "dc1",
869		Op:         structs.IntentionOpCreate,
870		Intention:  structs.TestIntention(t),
871	}
872	ixn.Intention.DestinationName = "foobar"
873	ixn.WriteRequest.Token = "root"
874
875	// Create
876	var reply string
877	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
878	ixn.Intention.ID = reply
879
880	// Update
881	ixn.Op = structs.IntentionOpUpdate
882	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
883
884	// Delete
885	ixn.Op = structs.IntentionOpDelete
886	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
887}
888
889// Test update changing the name where an ACL won't allow it
890func TestIntentionApply_aclUpdateChange(t *testing.T) {
891	t.Parallel()
892
893	assert := assert.New(t)
894	dir1, s1 := testServerWithConfig(t, func(c *Config) {
895		c.ACLDatacenter = "dc1"
896		c.ACLsEnabled = true
897		c.ACLMasterToken = "root"
898		c.ACLDefaultPolicy = "deny"
899	})
900	defer os.RemoveAll(dir1)
901	defer s1.Shutdown()
902	codec := rpcClient(t, s1)
903	defer codec.Close()
904
905	testrpc.WaitForLeader(t, s1.RPC, "dc1")
906
907	// Create an ACL with write permissions
908	var token string
909	{
910		var rules = `
911service "foo" {
912	policy = "deny"
913	intentions = "write"
914}`
915
916		req := structs.ACLRequest{
917			Datacenter: "dc1",
918			Op:         structs.ACLSet,
919			ACL: structs.ACL{
920				Name:  "User token",
921				Type:  structs.ACLTokenTypeClient,
922				Rules: rules,
923			},
924			WriteRequest: structs.WriteRequest{Token: "root"},
925		}
926		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
927	}
928
929	// Setup a basic record to create
930	ixn := structs.IntentionRequest{
931		Datacenter: "dc1",
932		Op:         structs.IntentionOpCreate,
933		Intention:  structs.TestIntention(t),
934	}
935	ixn.Intention.DestinationName = "bar"
936	ixn.WriteRequest.Token = "root"
937
938	// Create
939	var reply string
940	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
941
942	// Try to do an update without a token; this should get rejected.
943	ixn.Op = structs.IntentionOpUpdate
944	ixn.Intention.ID = reply
945	ixn.Intention.DestinationName = "foo"
946	ixn.WriteRequest.Token = token
947	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
948	assert.True(acl.IsErrPermissionDenied(err))
949}
950
951// Test reading with ACLs
952func TestIntentionGet_acl(t *testing.T) {
953	t.Parallel()
954
955	assert := assert.New(t)
956	dir1, s1 := testServerWithConfig(t, func(c *Config) {
957		c.ACLDatacenter = "dc1"
958		c.ACLsEnabled = true
959		c.ACLMasterToken = "root"
960		c.ACLDefaultPolicy = "deny"
961	})
962	defer os.RemoveAll(dir1)
963	defer s1.Shutdown()
964	codec := rpcClient(t, s1)
965	defer codec.Close()
966
967	testrpc.WaitForLeader(t, s1.RPC, "dc1")
968
969	// Create an ACL with service write permissions. This will grant
970	// intentions read.
971	var token string
972	{
973		var rules = `
974service "foo" {
975	policy = "write"
976}`
977
978		req := structs.ACLRequest{
979			Datacenter: "dc1",
980			Op:         structs.ACLSet,
981			ACL: structs.ACL{
982				Name:  "User token",
983				Type:  structs.ACLTokenTypeClient,
984				Rules: rules,
985			},
986			WriteRequest: structs.WriteRequest{Token: "root"},
987		}
988		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
989	}
990
991	// Setup a basic record to create
992	ixn := structs.IntentionRequest{
993		Datacenter: "dc1",
994		Op:         structs.IntentionOpCreate,
995		Intention:  structs.TestIntention(t),
996	}
997	ixn.Intention.DestinationName = "foobar"
998	ixn.WriteRequest.Token = "root"
999
1000	// Create
1001	var reply string
1002	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
1003	ixn.Intention.ID = reply
1004
1005	// Read without token should be error
1006	{
1007		req := &structs.IntentionQueryRequest{
1008			Datacenter:  "dc1",
1009			IntentionID: ixn.Intention.ID,
1010		}
1011
1012		var resp structs.IndexedIntentions
1013		err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp)
1014		assert.True(acl.IsErrPermissionDenied(err))
1015		assert.Len(resp.Intentions, 0)
1016	}
1017
1018	// Read with token should work
1019	{
1020		req := &structs.IntentionQueryRequest{
1021			Datacenter:   "dc1",
1022			IntentionID:  ixn.Intention.ID,
1023			QueryOptions: structs.QueryOptions{Token: token},
1024		}
1025
1026		var resp structs.IndexedIntentions
1027		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
1028		assert.Len(resp.Intentions, 1)
1029	}
1030}
1031
1032func TestIntentionList(t *testing.T) {
1033	t.Parallel()
1034
1035	assert := assert.New(t)
1036	dir1, s1 := testServer(t)
1037	defer os.RemoveAll(dir1)
1038	defer s1.Shutdown()
1039
1040	codec := rpcClient(t, s1)
1041	defer codec.Close()
1042	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1043
1044	// Test with no intentions inserted yet
1045	{
1046		req := &structs.DCSpecificRequest{
1047			Datacenter: "dc1",
1048		}
1049		var resp structs.IndexedIntentions
1050		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
1051		assert.NotNil(resp.Intentions)
1052		assert.Len(resp.Intentions, 0)
1053	}
1054}
1055
1056// Test listing with ACLs
1057func TestIntentionList_acl(t *testing.T) {
1058	t.Parallel()
1059
1060	dir1, s1 := testServerWithConfig(t, testServerACLConfig(nil))
1061	defer os.RemoveAll(dir1)
1062	defer s1.Shutdown()
1063	codec := rpcClient(t, s1)
1064	defer codec.Close()
1065
1066	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1067	waitForNewACLs(t, s1)
1068
1069	token, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service_prefix "foo" { policy = "write" }`)
1070	require.NoError(t, err)
1071
1072	// Create a few records
1073	for _, name := range []string{"foobar", "bar", "baz"} {
1074		ixn := structs.IntentionRequest{
1075			Datacenter: "dc1",
1076			Op:         structs.IntentionOpCreate,
1077			Intention:  structs.TestIntention(t),
1078		}
1079		ixn.Intention.SourceNS = "default"
1080		ixn.Intention.DestinationNS = "default"
1081		ixn.Intention.DestinationName = name
1082		ixn.WriteRequest.Token = TestDefaultMasterToken
1083
1084		// Create
1085		var reply string
1086		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
1087	}
1088
1089	// Test with no token
1090	t.Run("no-token", func(t *testing.T) {
1091		req := &structs.DCSpecificRequest{
1092			Datacenter: "dc1",
1093		}
1094		var resp structs.IndexedIntentions
1095		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
1096		require.Len(t, resp.Intentions, 0)
1097	})
1098
1099	// Test with management token
1100	t.Run("master-token", func(t *testing.T) {
1101		req := &structs.DCSpecificRequest{
1102			Datacenter:   "dc1",
1103			QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken},
1104		}
1105		var resp structs.IndexedIntentions
1106		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
1107		require.Len(t, resp.Intentions, 3)
1108	})
1109
1110	// Test with user token
1111	t.Run("user-token", func(t *testing.T) {
1112		req := &structs.DCSpecificRequest{
1113			Datacenter:   "dc1",
1114			QueryOptions: structs.QueryOptions{Token: token.SecretID},
1115		}
1116		var resp structs.IndexedIntentions
1117		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
1118		require.Len(t, resp.Intentions, 1)
1119	})
1120
1121	t.Run("filtered", func(t *testing.T) {
1122		req := &structs.DCSpecificRequest{
1123			Datacenter: "dc1",
1124			QueryOptions: structs.QueryOptions{
1125				Token:  TestDefaultMasterToken,
1126				Filter: "DestinationName == foobar",
1127			},
1128		}
1129
1130		var resp structs.IndexedIntentions
1131		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
1132		require.Len(t, resp.Intentions, 1)
1133	})
1134}
1135
1136// Test basic matching. We don't need to exhaustively test inputs since this
1137// is tested in the agent/consul/state package.
1138func TestIntentionMatch_good(t *testing.T) {
1139	t.Parallel()
1140
1141	assert := assert.New(t)
1142	dir1, s1 := testServer(t)
1143	defer os.RemoveAll(dir1)
1144	defer s1.Shutdown()
1145	codec := rpcClient(t, s1)
1146	defer codec.Close()
1147
1148	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1149
1150	// Create some records
1151	{
1152		insert := [][]string{
1153			{"foo", "*", "foo", "*"},
1154			{"foo", "*", "foo", "bar"},
1155			{"foo", "*", "foo", "baz"}, // shouldn't match
1156			{"foo", "*", "bar", "bar"}, // shouldn't match
1157			{"foo", "*", "bar", "*"},   // shouldn't match
1158			{"foo", "*", "*", "*"},
1159			{"bar", "*", "foo", "bar"}, // duplicate destination different source
1160		}
1161
1162		for _, v := range insert {
1163			ixn := structs.IntentionRequest{
1164				Datacenter: "dc1",
1165				Op:         structs.IntentionOpCreate,
1166				Intention: &structs.Intention{
1167					SourceNS:        v[0],
1168					SourceName:      v[1],
1169					DestinationNS:   v[2],
1170					DestinationName: v[3],
1171					Action:          structs.IntentionActionAllow,
1172				},
1173			}
1174
1175			// Create
1176			var reply string
1177			assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
1178		}
1179	}
1180
1181	// Match
1182	req := &structs.IntentionQueryRequest{
1183		Datacenter: "dc1",
1184		Match: &structs.IntentionQueryMatch{
1185			Type: structs.IntentionMatchDestination,
1186			Entries: []structs.IntentionMatchEntry{
1187				{
1188					Namespace: "foo",
1189					Name:      "bar",
1190				},
1191			},
1192		},
1193	}
1194	var resp structs.IndexedIntentionMatches
1195	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp))
1196	assert.Len(resp.Matches, 1)
1197
1198	expected := [][]string{
1199		{"bar", "*", "foo", "bar"},
1200		{"foo", "*", "foo", "bar"},
1201		{"foo", "*", "foo", "*"},
1202		{"foo", "*", "*", "*"},
1203	}
1204	var actual [][]string
1205	for _, ixn := range resp.Matches[0] {
1206		actual = append(actual, []string{
1207			ixn.SourceNS,
1208			ixn.SourceName,
1209			ixn.DestinationNS,
1210			ixn.DestinationName,
1211		})
1212	}
1213	assert.Equal(expected, actual)
1214}
1215
1216// Test matching with ACLs
1217func TestIntentionMatch_acl(t *testing.T) {
1218	t.Parallel()
1219
1220	_, srv, codec := testACLServerWithConfig(t, nil, false)
1221	waitForLeaderEstablishment(t, srv)
1222
1223	token, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service "bar" { policy = "write" }`)
1224	require.NoError(t, err)
1225
1226	// Create some records
1227	{
1228		insert := []string{
1229			"*",
1230			"bar",
1231			"baz",
1232		}
1233
1234		for _, v := range insert {
1235			ixn := structs.IntentionRequest{
1236				Datacenter: "dc1",
1237				Op:         structs.IntentionOpCreate,
1238				Intention:  structs.TestIntention(t),
1239			}
1240			ixn.Intention.DestinationName = v
1241			ixn.WriteRequest.Token = TestDefaultMasterToken
1242
1243			// Create
1244			var reply string
1245			require.Nil(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
1246		}
1247	}
1248
1249	// Test with no token
1250	{
1251		req := &structs.IntentionQueryRequest{
1252			Datacenter: "dc1",
1253			Match: &structs.IntentionQueryMatch{
1254				Type: structs.IntentionMatchDestination,
1255				Entries: []structs.IntentionMatchEntry{
1256					{
1257						Namespace: "default",
1258						Name:      "bar",
1259					},
1260				},
1261			},
1262		}
1263		var resp structs.IndexedIntentionMatches
1264		err := msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp)
1265		require.True(t, acl.IsErrPermissionDenied(err))
1266		require.Len(t, resp.Matches, 0)
1267	}
1268
1269	// Test with proper token
1270	{
1271		req := &structs.IntentionQueryRequest{
1272			Datacenter: "dc1",
1273			Match: &structs.IntentionQueryMatch{
1274				Type: structs.IntentionMatchDestination,
1275				Entries: []structs.IntentionMatchEntry{
1276					{
1277						Namespace: "default",
1278						Name:      "bar",
1279					},
1280				},
1281			},
1282			QueryOptions: structs.QueryOptions{Token: token.SecretID},
1283		}
1284		var resp structs.IndexedIntentionMatches
1285		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp))
1286		require.Len(t, resp.Matches, 1)
1287
1288		expected := []string{"bar", "*"}
1289		var actual []string
1290		for _, ixn := range resp.Matches[0] {
1291			actual = append(actual, ixn.DestinationName)
1292		}
1293
1294		require.ElementsMatch(t, expected, actual)
1295	}
1296}
1297
1298// Test the Check method defaults to allow with no ACL set.
1299func TestIntentionCheck_defaultNoACL(t *testing.T) {
1300	t.Parallel()
1301
1302	require := require.New(t)
1303	dir1, s1 := testServer(t)
1304	defer os.RemoveAll(dir1)
1305	defer s1.Shutdown()
1306	codec := rpcClient(t, s1)
1307	defer codec.Close()
1308
1309	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1310
1311	// Test
1312	req := &structs.IntentionQueryRequest{
1313		Datacenter: "dc1",
1314		Check: &structs.IntentionQueryCheck{
1315			SourceNS:        "foo",
1316			SourceName:      "bar",
1317			DestinationNS:   "foo",
1318			DestinationName: "qux",
1319			SourceType:      structs.IntentionSourceConsul,
1320		},
1321	}
1322	var resp structs.IntentionQueryCheckResponse
1323	require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1324	require.True(resp.Allowed)
1325}
1326
1327// Test the Check method defaults to deny with allowlist ACLs.
1328func TestIntentionCheck_defaultACLDeny(t *testing.T) {
1329	t.Parallel()
1330
1331	require := require.New(t)
1332	dir1, s1 := testServerWithConfig(t, func(c *Config) {
1333		c.ACLDatacenter = "dc1"
1334		c.ACLsEnabled = true
1335		c.ACLMasterToken = "root"
1336		c.ACLDefaultPolicy = "deny"
1337	})
1338	defer os.RemoveAll(dir1)
1339	defer s1.Shutdown()
1340	codec := rpcClient(t, s1)
1341	defer codec.Close()
1342
1343	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1344
1345	// Check
1346	req := &structs.IntentionQueryRequest{
1347		Datacenter: "dc1",
1348		Check: &structs.IntentionQueryCheck{
1349			SourceNS:        "foo",
1350			SourceName:      "bar",
1351			DestinationNS:   "foo",
1352			DestinationName: "qux",
1353			SourceType:      structs.IntentionSourceConsul,
1354		},
1355	}
1356	req.Token = "root"
1357	var resp structs.IntentionQueryCheckResponse
1358	require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1359	require.False(resp.Allowed)
1360}
1361
1362// Test the Check method defaults to deny with denylist ACLs.
1363func TestIntentionCheck_defaultACLAllow(t *testing.T) {
1364	t.Parallel()
1365
1366	require := require.New(t)
1367	dir1, s1 := testServerWithConfig(t, func(c *Config) {
1368		c.ACLDatacenter = "dc1"
1369		c.ACLsEnabled = true
1370		c.ACLMasterToken = "root"
1371		c.ACLDefaultPolicy = "allow"
1372	})
1373	defer os.RemoveAll(dir1)
1374	defer s1.Shutdown()
1375	codec := rpcClient(t, s1)
1376	defer codec.Close()
1377
1378	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1379
1380	// Check
1381	req := &structs.IntentionQueryRequest{
1382		Datacenter: "dc1",
1383		Check: &structs.IntentionQueryCheck{
1384			SourceNS:        "foo",
1385			SourceName:      "bar",
1386			DestinationNS:   "foo",
1387			DestinationName: "qux",
1388			SourceType:      structs.IntentionSourceConsul,
1389		},
1390	}
1391	req.Token = "root"
1392	var resp structs.IntentionQueryCheckResponse
1393	require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1394	require.True(resp.Allowed)
1395}
1396
1397// Test the Check method requires service:read permission.
1398func TestIntentionCheck_aclDeny(t *testing.T) {
1399	t.Parallel()
1400
1401	require := require.New(t)
1402	dir1, s1 := testServerWithConfig(t, func(c *Config) {
1403		c.ACLDatacenter = "dc1"
1404		c.ACLsEnabled = true
1405		c.ACLMasterToken = "root"
1406		c.ACLDefaultPolicy = "deny"
1407	})
1408	defer os.RemoveAll(dir1)
1409	defer s1.Shutdown()
1410	codec := rpcClient(t, s1)
1411	defer codec.Close()
1412
1413	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1414
1415	// Create an ACL with service read permissions. This will grant permission.
1416	var token string
1417	{
1418		var rules = `
1419service "bar" {
1420	policy = "read"
1421}`
1422
1423		req := structs.ACLRequest{
1424			Datacenter: "dc1",
1425			Op:         structs.ACLSet,
1426			ACL: structs.ACL{
1427				Name:  "User token",
1428				Type:  structs.ACLTokenTypeClient,
1429				Rules: rules,
1430			},
1431			WriteRequest: structs.WriteRequest{Token: "root"},
1432		}
1433		require.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
1434	}
1435
1436	// Check
1437	req := &structs.IntentionQueryRequest{
1438		Datacenter: "dc1",
1439		Check: &structs.IntentionQueryCheck{
1440			SourceNS:        "foo",
1441			SourceName:      "qux",
1442			DestinationNS:   "foo",
1443			DestinationName: "baz",
1444			SourceType:      structs.IntentionSourceConsul,
1445		},
1446	}
1447	req.Token = token
1448	var resp structs.IntentionQueryCheckResponse
1449	err := msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp)
1450	require.True(acl.IsErrPermissionDenied(err))
1451}
1452
1453// Test the Check method returns allow/deny properly.
1454func TestIntentionCheck_match(t *testing.T) {
1455	t.Parallel()
1456
1457	_, srv, codec := testACLServerWithConfig(t, nil, false)
1458	waitForLeaderEstablishment(t, srv)
1459
1460	token, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service "api" { policy = "read" }`)
1461	require.NoError(t, err)
1462
1463	// Create some intentions
1464	{
1465		insert := [][]string{
1466			{"web", "db"},
1467			{"api", "db"},
1468			{"web", "api"},
1469		}
1470
1471		for _, v := range insert {
1472			ixn := structs.IntentionRequest{
1473				Datacenter: "dc1",
1474				Op:         structs.IntentionOpCreate,
1475				Intention: &structs.Intention{
1476					SourceNS:        "default",
1477					SourceName:      v[0],
1478					DestinationNS:   "default",
1479					DestinationName: v[1],
1480					Action:          structs.IntentionActionAllow,
1481				},
1482				WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
1483			}
1484			// Create
1485			var reply string
1486			require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
1487		}
1488	}
1489
1490	// Check
1491	req := &structs.IntentionQueryRequest{
1492		Datacenter: "dc1",
1493		Check: &structs.IntentionQueryCheck{
1494			SourceNS:        "default",
1495			SourceName:      "web",
1496			DestinationNS:   "default",
1497			DestinationName: "api",
1498			SourceType:      structs.IntentionSourceConsul,
1499		},
1500		QueryOptions: structs.QueryOptions{Token: token.SecretID},
1501	}
1502	var resp structs.IntentionQueryCheckResponse
1503	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1504	require.True(t, resp.Allowed)
1505
1506	// Test no match for sanity
1507	{
1508		req := &structs.IntentionQueryRequest{
1509			Datacenter: "dc1",
1510			Check: &structs.IntentionQueryCheck{
1511				SourceNS:        "default",
1512				SourceName:      "db",
1513				DestinationNS:   "default",
1514				DestinationName: "api",
1515				SourceType:      structs.IntentionSourceConsul,
1516			},
1517			QueryOptions: structs.QueryOptions{Token: token.SecretID},
1518		}
1519		var resp structs.IntentionQueryCheckResponse
1520		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1521		require.False(t, resp.Allowed)
1522	}
1523}
1524