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	"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		ixn.Intention.UpdatePrecedence()
71		assert.Equal(ixn.Intention, actual)
72	}
73}
74
75// Test the source type defaults
76func TestIntentionApply_defaultSourceType(t *testing.T) {
77	t.Parallel()
78
79	assert := assert.New(t)
80	dir1, s1 := testServer(t)
81	defer os.RemoveAll(dir1)
82	defer s1.Shutdown()
83	codec := rpcClient(t, s1)
84	defer codec.Close()
85
86	testrpc.WaitForLeader(t, s1.RPC, "dc1")
87
88	// Setup a basic record to create
89	ixn := structs.IntentionRequest{
90		Datacenter: "dc1",
91		Op:         structs.IntentionOpCreate,
92		Intention: &structs.Intention{
93			SourceNS:        structs.IntentionDefaultNamespace,
94			SourceName:      "test",
95			DestinationNS:   structs.IntentionDefaultNamespace,
96			DestinationName: "test",
97			Action:          structs.IntentionActionAllow,
98		},
99	}
100	var reply string
101
102	// Create
103	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
104	assert.NotEmpty(reply)
105
106	// Read
107	ixn.Intention.ID = reply
108	{
109		req := &structs.IntentionQueryRequest{
110			Datacenter:  "dc1",
111			IntentionID: ixn.Intention.ID,
112		}
113		var resp structs.IndexedIntentions
114		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
115		assert.Len(resp.Intentions, 1)
116		actual := resp.Intentions[0]
117		assert.Equal(structs.IntentionSourceConsul, actual.SourceType)
118	}
119}
120
121// Shouldn't be able to create with an ID set
122func TestIntentionApply_createWithID(t *testing.T) {
123	t.Parallel()
124
125	assert := assert.New(t)
126	dir1, s1 := testServer(t)
127	defer os.RemoveAll(dir1)
128	defer s1.Shutdown()
129	codec := rpcClient(t, s1)
130	defer codec.Close()
131
132	testrpc.WaitForLeader(t, s1.RPC, "dc1")
133
134	// Setup a basic record to create
135	ixn := structs.IntentionRequest{
136		Datacenter: "dc1",
137		Op:         structs.IntentionOpCreate,
138		Intention: &structs.Intention{
139			ID:         generateUUID(),
140			SourceName: "test",
141		},
142	}
143	var reply string
144
145	// Create
146	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
147	assert.NotNil(err)
148	assert.Contains(err, "ID must be empty")
149}
150
151// Test basic updating
152func TestIntentionApply_updateGood(t *testing.T) {
153	t.Parallel()
154
155	assert := assert.New(t)
156	dir1, s1 := testServer(t)
157	defer os.RemoveAll(dir1)
158	defer s1.Shutdown()
159	codec := rpcClient(t, s1)
160	defer codec.Close()
161
162	testrpc.WaitForLeader(t, s1.RPC, "dc1")
163
164	// Setup a basic record to create
165	ixn := structs.IntentionRequest{
166		Datacenter: "dc1",
167		Op:         structs.IntentionOpCreate,
168		Intention: &structs.Intention{
169			SourceNS:        structs.IntentionDefaultNamespace,
170			SourceName:      "test",
171			DestinationNS:   structs.IntentionDefaultNamespace,
172			DestinationName: "test",
173			Action:          structs.IntentionActionAllow,
174			SourceType:      structs.IntentionSourceConsul,
175			Meta:            map[string]string{},
176		},
177	}
178	var reply string
179
180	// Create
181	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
182	assert.NotEmpty(reply)
183
184	// Read CreatedAt
185	var createdAt time.Time
186	ixn.Intention.ID = reply
187	{
188		req := &structs.IntentionQueryRequest{
189			Datacenter:  "dc1",
190			IntentionID: ixn.Intention.ID,
191		}
192		var resp structs.IndexedIntentions
193		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
194		assert.Len(resp.Intentions, 1)
195		actual := resp.Intentions[0]
196		createdAt = actual.CreatedAt
197	}
198
199	// Sleep a bit so that the updated at will definitely be different, not much
200	time.Sleep(1 * time.Millisecond)
201
202	// Update
203	ixn.Op = structs.IntentionOpUpdate
204	ixn.Intention.ID = reply
205	ixn.Intention.SourceName = "*"
206	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
207
208	// Read
209	ixn.Intention.ID = reply
210	{
211		req := &structs.IntentionQueryRequest{
212			Datacenter:  "dc1",
213			IntentionID: ixn.Intention.ID,
214		}
215		var resp structs.IndexedIntentions
216		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
217		assert.Len(resp.Intentions, 1)
218		actual := resp.Intentions[0]
219		assert.Equal(createdAt, actual.CreatedAt)
220		assert.WithinDuration(time.Now(), actual.UpdatedAt, 5*time.Second)
221
222		actual.CreateIndex, actual.ModifyIndex = 0, 0
223		actual.CreatedAt = ixn.Intention.CreatedAt
224		actual.UpdatedAt = ixn.Intention.UpdatedAt
225		ixn.Intention.UpdatePrecedence()
226		assert.Equal(ixn.Intention, actual)
227	}
228}
229
230// Shouldn't be able to update a non-existent intention
231func TestIntentionApply_updateNonExist(t *testing.T) {
232	t.Parallel()
233
234	assert := assert.New(t)
235	dir1, s1 := testServer(t)
236	defer os.RemoveAll(dir1)
237	defer s1.Shutdown()
238	codec := rpcClient(t, s1)
239	defer codec.Close()
240
241	testrpc.WaitForLeader(t, s1.RPC, "dc1")
242
243	// Setup a basic record to create
244	ixn := structs.IntentionRequest{
245		Datacenter: "dc1",
246		Op:         structs.IntentionOpUpdate,
247		Intention: &structs.Intention{
248			ID:         generateUUID(),
249			SourceName: "test",
250		},
251	}
252	var reply string
253
254	// Create
255	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
256	assert.NotNil(err)
257	assert.Contains(err, "Cannot modify non-existent intention")
258}
259
260// Test basic deleting
261func TestIntentionApply_deleteGood(t *testing.T) {
262	t.Parallel()
263
264	assert := assert.New(t)
265	dir1, s1 := testServer(t)
266	defer os.RemoveAll(dir1)
267	defer s1.Shutdown()
268	codec := rpcClient(t, s1)
269	defer codec.Close()
270
271	testrpc.WaitForLeader(t, s1.RPC, "dc1")
272
273	// Setup a basic record to create
274	ixn := structs.IntentionRequest{
275		Datacenter: "dc1",
276		Op:         structs.IntentionOpCreate,
277		Intention: &structs.Intention{
278			SourceNS:        "test",
279			SourceName:      "test",
280			DestinationNS:   "test",
281			DestinationName: "test",
282			Action:          structs.IntentionActionAllow,
283		},
284	}
285	var reply string
286
287	// Create
288	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
289	assert.NotEmpty(reply)
290
291	// Delete
292	ixn.Op = structs.IntentionOpDelete
293	ixn.Intention.ID = reply
294	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
295
296	// Read
297	ixn.Intention.ID = reply
298	{
299		req := &structs.IntentionQueryRequest{
300			Datacenter:  "dc1",
301			IntentionID: ixn.Intention.ID,
302		}
303		var resp structs.IndexedIntentions
304		err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp)
305		assert.NotNil(err)
306		assert.Contains(err, ErrIntentionNotFound.Error())
307	}
308}
309
310// Test apply with a deny ACL
311func TestIntentionApply_aclDeny(t *testing.T) {
312	t.Parallel()
313
314	assert := assert.New(t)
315	dir1, s1 := testServerWithConfig(t, func(c *Config) {
316		c.ACLDatacenter = "dc1"
317		c.ACLMasterToken = "root"
318		c.ACLDefaultPolicy = "deny"
319	})
320	defer os.RemoveAll(dir1)
321	defer s1.Shutdown()
322	codec := rpcClient(t, s1)
323	defer codec.Close()
324
325	testrpc.WaitForLeader(t, s1.RPC, "dc1")
326
327	// Create an ACL with write permissions
328	var token string
329	{
330		var rules = `
331service "foo" {
332	policy = "deny"
333	intentions = "write"
334}`
335
336		req := structs.ACLRequest{
337			Datacenter: "dc1",
338			Op:         structs.ACLSet,
339			ACL: structs.ACL{
340				Name:  "User token",
341				Type:  structs.ACLTypeClient,
342				Rules: rules,
343			},
344			WriteRequest: structs.WriteRequest{Token: "root"},
345		}
346		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
347	}
348
349	// Setup a basic record to create
350	ixn := structs.IntentionRequest{
351		Datacenter: "dc1",
352		Op:         structs.IntentionOpCreate,
353		Intention:  structs.TestIntention(t),
354	}
355	ixn.Intention.DestinationName = "foobar"
356
357	// Create without a token should error since default deny
358	var reply string
359	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
360	assert.True(acl.IsErrPermissionDenied(err))
361
362	// Now add the token and try again.
363	ixn.WriteRequest.Token = token
364	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
365
366	// Read
367	ixn.Intention.ID = reply
368	{
369		req := &structs.IntentionQueryRequest{
370			Datacenter:   "dc1",
371			IntentionID:  ixn.Intention.ID,
372			QueryOptions: structs.QueryOptions{Token: "root"},
373		}
374		var resp structs.IndexedIntentions
375		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
376		assert.Len(resp.Intentions, 1)
377		actual := resp.Intentions[0]
378		assert.Equal(resp.Index, actual.ModifyIndex)
379
380		actual.CreateIndex, actual.ModifyIndex = 0, 0
381		actual.CreatedAt = ixn.Intention.CreatedAt
382		actual.UpdatedAt = ixn.Intention.UpdatedAt
383		ixn.Intention.UpdatePrecedence()
384		assert.Equal(ixn.Intention, actual)
385	}
386}
387
388// Test apply with delete and a default deny ACL
389func TestIntentionApply_aclDelete(t *testing.T) {
390	t.Parallel()
391
392	assert := assert.New(t)
393	dir1, s1 := testServerWithConfig(t, func(c *Config) {
394		c.ACLDatacenter = "dc1"
395		c.ACLMasterToken = "root"
396		c.ACLDefaultPolicy = "deny"
397	})
398	defer os.RemoveAll(dir1)
399	defer s1.Shutdown()
400	codec := rpcClient(t, s1)
401	defer codec.Close()
402
403	testrpc.WaitForLeader(t, s1.RPC, "dc1")
404
405	// Create an ACL with write permissions
406	var token string
407	{
408		var rules = `
409service "foo" {
410	policy = "deny"
411	intentions = "write"
412}`
413
414		req := structs.ACLRequest{
415			Datacenter: "dc1",
416			Op:         structs.ACLSet,
417			ACL: structs.ACL{
418				Name:  "User token",
419				Type:  structs.ACLTypeClient,
420				Rules: rules,
421			},
422			WriteRequest: structs.WriteRequest{Token: "root"},
423		}
424		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
425	}
426
427	// Setup a basic record to create
428	ixn := structs.IntentionRequest{
429		Datacenter: "dc1",
430		Op:         structs.IntentionOpCreate,
431		Intention:  structs.TestIntention(t),
432	}
433	ixn.Intention.DestinationName = "foobar"
434	ixn.WriteRequest.Token = token
435
436	// Create
437	var reply string
438	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
439
440	// Try to do a delete with no token; this should get rejected.
441	ixn.Op = structs.IntentionOpDelete
442	ixn.Intention.ID = reply
443	ixn.WriteRequest.Token = ""
444	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
445	assert.True(acl.IsErrPermissionDenied(err))
446
447	// Try again with the original token. This should go through.
448	ixn.WriteRequest.Token = token
449	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
450
451	// Verify it is gone
452	{
453		req := &structs.IntentionQueryRequest{
454			Datacenter:  "dc1",
455			IntentionID: ixn.Intention.ID,
456		}
457		var resp structs.IndexedIntentions
458		err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp)
459		assert.NotNil(err)
460		assert.Contains(err.Error(), ErrIntentionNotFound.Error())
461	}
462}
463
464// Test apply with update and a default deny ACL
465func TestIntentionApply_aclUpdate(t *testing.T) {
466	t.Parallel()
467
468	assert := assert.New(t)
469	dir1, s1 := testServerWithConfig(t, func(c *Config) {
470		c.ACLDatacenter = "dc1"
471		c.ACLMasterToken = "root"
472		c.ACLDefaultPolicy = "deny"
473	})
474	defer os.RemoveAll(dir1)
475	defer s1.Shutdown()
476	codec := rpcClient(t, s1)
477	defer codec.Close()
478
479	testrpc.WaitForLeader(t, s1.RPC, "dc1")
480
481	// Create an ACL with write permissions
482	var token string
483	{
484		var rules = `
485service "foo" {
486	policy = "deny"
487	intentions = "write"
488}`
489
490		req := structs.ACLRequest{
491			Datacenter: "dc1",
492			Op:         structs.ACLSet,
493			ACL: structs.ACL{
494				Name:  "User token",
495				Type:  structs.ACLTypeClient,
496				Rules: rules,
497			},
498			WriteRequest: structs.WriteRequest{Token: "root"},
499		}
500		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
501	}
502
503	// Setup a basic record to create
504	ixn := structs.IntentionRequest{
505		Datacenter: "dc1",
506		Op:         structs.IntentionOpCreate,
507		Intention:  structs.TestIntention(t),
508	}
509	ixn.Intention.DestinationName = "foobar"
510	ixn.WriteRequest.Token = token
511
512	// Create
513	var reply string
514	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
515
516	// Try to do an update without a token; this should get rejected.
517	ixn.Op = structs.IntentionOpUpdate
518	ixn.Intention.ID = reply
519	ixn.WriteRequest.Token = ""
520	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
521	assert.True(acl.IsErrPermissionDenied(err))
522
523	// Try again with the original token; this should go through.
524	ixn.WriteRequest.Token = token
525	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
526}
527
528// Test apply with a management token
529func TestIntentionApply_aclManagement(t *testing.T) {
530	t.Parallel()
531
532	assert := assert.New(t)
533	dir1, s1 := testServerWithConfig(t, func(c *Config) {
534		c.ACLDatacenter = "dc1"
535		c.ACLMasterToken = "root"
536		c.ACLDefaultPolicy = "deny"
537	})
538	defer os.RemoveAll(dir1)
539	defer s1.Shutdown()
540	codec := rpcClient(t, s1)
541	defer codec.Close()
542
543	testrpc.WaitForLeader(t, s1.RPC, "dc1")
544
545	// Setup a basic record to create
546	ixn := structs.IntentionRequest{
547		Datacenter: "dc1",
548		Op:         structs.IntentionOpCreate,
549		Intention:  structs.TestIntention(t),
550	}
551	ixn.Intention.DestinationName = "foobar"
552	ixn.WriteRequest.Token = "root"
553
554	// Create
555	var reply string
556	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
557	ixn.Intention.ID = reply
558
559	// Update
560	ixn.Op = structs.IntentionOpUpdate
561	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
562
563	// Delete
564	ixn.Op = structs.IntentionOpDelete
565	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
566}
567
568// Test update changing the name where an ACL won't allow it
569func TestIntentionApply_aclUpdateChange(t *testing.T) {
570	t.Parallel()
571
572	assert := assert.New(t)
573	dir1, s1 := testServerWithConfig(t, func(c *Config) {
574		c.ACLDatacenter = "dc1"
575		c.ACLMasterToken = "root"
576		c.ACLDefaultPolicy = "deny"
577	})
578	defer os.RemoveAll(dir1)
579	defer s1.Shutdown()
580	codec := rpcClient(t, s1)
581	defer codec.Close()
582
583	testrpc.WaitForLeader(t, s1.RPC, "dc1")
584
585	// Create an ACL with write permissions
586	var token string
587	{
588		var rules = `
589service "foo" {
590	policy = "deny"
591	intentions = "write"
592}`
593
594		req := structs.ACLRequest{
595			Datacenter: "dc1",
596			Op:         structs.ACLSet,
597			ACL: structs.ACL{
598				Name:  "User token",
599				Type:  structs.ACLTypeClient,
600				Rules: rules,
601			},
602			WriteRequest: structs.WriteRequest{Token: "root"},
603		}
604		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
605	}
606
607	// Setup a basic record to create
608	ixn := structs.IntentionRequest{
609		Datacenter: "dc1",
610		Op:         structs.IntentionOpCreate,
611		Intention:  structs.TestIntention(t),
612	}
613	ixn.Intention.DestinationName = "bar"
614	ixn.WriteRequest.Token = "root"
615
616	// Create
617	var reply string
618	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
619
620	// Try to do an update without a token; this should get rejected.
621	ixn.Op = structs.IntentionOpUpdate
622	ixn.Intention.ID = reply
623	ixn.Intention.DestinationName = "foo"
624	ixn.WriteRequest.Token = token
625	err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)
626	assert.True(acl.IsErrPermissionDenied(err))
627}
628
629// Test reading with ACLs
630func TestIntentionGet_acl(t *testing.T) {
631	t.Parallel()
632
633	assert := assert.New(t)
634	dir1, s1 := testServerWithConfig(t, func(c *Config) {
635		c.ACLDatacenter = "dc1"
636		c.ACLMasterToken = "root"
637		c.ACLDefaultPolicy = "deny"
638	})
639	defer os.RemoveAll(dir1)
640	defer s1.Shutdown()
641	codec := rpcClient(t, s1)
642	defer codec.Close()
643
644	testrpc.WaitForLeader(t, s1.RPC, "dc1")
645
646	// Create an ACL with service write permissions. This will grant
647	// intentions read.
648	var token string
649	{
650		var rules = `
651service "foo" {
652	policy = "write"
653}`
654
655		req := structs.ACLRequest{
656			Datacenter: "dc1",
657			Op:         structs.ACLSet,
658			ACL: structs.ACL{
659				Name:  "User token",
660				Type:  structs.ACLTypeClient,
661				Rules: rules,
662			},
663			WriteRequest: structs.WriteRequest{Token: "root"},
664		}
665		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
666	}
667
668	// Setup a basic record to create
669	ixn := structs.IntentionRequest{
670		Datacenter: "dc1",
671		Op:         structs.IntentionOpCreate,
672		Intention:  structs.TestIntention(t),
673	}
674	ixn.Intention.DestinationName = "foobar"
675	ixn.WriteRequest.Token = "root"
676
677	// Create
678	var reply string
679	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
680	ixn.Intention.ID = reply
681
682	// Read without token should be error
683	{
684		req := &structs.IntentionQueryRequest{
685			Datacenter:  "dc1",
686			IntentionID: ixn.Intention.ID,
687		}
688
689		var resp structs.IndexedIntentions
690		err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp)
691		assert.True(acl.IsErrPermissionDenied(err))
692		assert.Len(resp.Intentions, 0)
693	}
694
695	// Read with token should work
696	{
697		req := &structs.IntentionQueryRequest{
698			Datacenter:   "dc1",
699			IntentionID:  ixn.Intention.ID,
700			QueryOptions: structs.QueryOptions{Token: token},
701		}
702
703		var resp structs.IndexedIntentions
704		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp))
705		assert.Len(resp.Intentions, 1)
706	}
707}
708
709func TestIntentionList(t *testing.T) {
710	t.Parallel()
711
712	assert := assert.New(t)
713	dir1, s1 := testServer(t)
714	defer os.RemoveAll(dir1)
715	defer s1.Shutdown()
716
717	codec := rpcClient(t, s1)
718	defer codec.Close()
719	testrpc.WaitForLeader(t, s1.RPC, "dc1")
720
721	// Test with no intentions inserted yet
722	{
723		req := &structs.DCSpecificRequest{
724			Datacenter: "dc1",
725		}
726		var resp structs.IndexedIntentions
727		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
728		assert.NotNil(resp.Intentions)
729		assert.Len(resp.Intentions, 0)
730	}
731}
732
733// Test listing with ACLs
734func TestIntentionList_acl(t *testing.T) {
735	t.Parallel()
736
737	assert := assert.New(t)
738	dir1, s1 := testServerWithConfig(t, func(c *Config) {
739		c.ACLDatacenter = "dc1"
740		c.ACLMasterToken = "root"
741		c.ACLDefaultPolicy = "deny"
742	})
743	defer os.RemoveAll(dir1)
744	defer s1.Shutdown()
745	codec := rpcClient(t, s1)
746	defer codec.Close()
747
748	testrpc.WaitForLeader(t, s1.RPC, "dc1")
749
750	// Create an ACL with service write permissions. This will grant
751	// intentions read.
752	var token string
753	{
754		var rules = `
755service "foo" {
756	policy = "write"
757}`
758
759		req := structs.ACLRequest{
760			Datacenter: "dc1",
761			Op:         structs.ACLSet,
762			ACL: structs.ACL{
763				Name:  "User token",
764				Type:  structs.ACLTypeClient,
765				Rules: rules,
766			},
767			WriteRequest: structs.WriteRequest{Token: "root"},
768		}
769		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
770	}
771
772	// Create a few records
773	for _, name := range []string{"foobar", "bar", "baz"} {
774		ixn := structs.IntentionRequest{
775			Datacenter: "dc1",
776			Op:         structs.IntentionOpCreate,
777			Intention:  structs.TestIntention(t),
778		}
779		ixn.Intention.DestinationName = name
780		ixn.WriteRequest.Token = "root"
781
782		// Create
783		var reply string
784		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
785	}
786
787	// Test with no token
788	{
789		req := &structs.DCSpecificRequest{
790			Datacenter: "dc1",
791		}
792		var resp structs.IndexedIntentions
793		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
794		assert.Len(resp.Intentions, 0)
795	}
796
797	// Test with management token
798	{
799		req := &structs.DCSpecificRequest{
800			Datacenter:   "dc1",
801			QueryOptions: structs.QueryOptions{Token: "root"},
802		}
803		var resp structs.IndexedIntentions
804		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
805		assert.Len(resp.Intentions, 3)
806	}
807
808	// Test with user token
809	{
810		req := &structs.DCSpecificRequest{
811			Datacenter:   "dc1",
812			QueryOptions: structs.QueryOptions{Token: token},
813		}
814		var resp structs.IndexedIntentions
815		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
816		assert.Len(resp.Intentions, 1)
817	}
818}
819
820// Test basic matching. We don't need to exhaustively test inputs since this
821// is tested in the agent/consul/state package.
822func TestIntentionMatch_good(t *testing.T) {
823	t.Parallel()
824
825	assert := assert.New(t)
826	dir1, s1 := testServer(t)
827	defer os.RemoveAll(dir1)
828	defer s1.Shutdown()
829	codec := rpcClient(t, s1)
830	defer codec.Close()
831
832	testrpc.WaitForLeader(t, s1.RPC, "dc1")
833
834	// Create some records
835	{
836		insert := [][]string{
837			{"foo", "*", "foo", "*"},
838			{"foo", "*", "foo", "bar"},
839			{"foo", "*", "foo", "baz"}, // shouldn't match
840			{"foo", "*", "bar", "bar"}, // shouldn't match
841			{"foo", "*", "bar", "*"},   // shouldn't match
842			{"foo", "*", "*", "*"},
843			{"bar", "*", "foo", "bar"}, // duplicate destination different source
844		}
845
846		for _, v := range insert {
847			ixn := structs.IntentionRequest{
848				Datacenter: "dc1",
849				Op:         structs.IntentionOpCreate,
850				Intention: &structs.Intention{
851					SourceNS:        v[0],
852					SourceName:      v[1],
853					DestinationNS:   v[2],
854					DestinationName: v[3],
855					Action:          structs.IntentionActionAllow,
856				},
857			}
858
859			// Create
860			var reply string
861			assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
862		}
863	}
864
865	// Match
866	req := &structs.IntentionQueryRequest{
867		Datacenter: "dc1",
868		Match: &structs.IntentionQueryMatch{
869			Type: structs.IntentionMatchDestination,
870			Entries: []structs.IntentionMatchEntry{
871				{
872					Namespace: "foo",
873					Name:      "bar",
874				},
875			},
876		},
877	}
878	var resp structs.IndexedIntentionMatches
879	assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp))
880	assert.Len(resp.Matches, 1)
881
882	expected := [][]string{
883		{"bar", "*", "foo", "bar"},
884		{"foo", "*", "foo", "bar"},
885		{"foo", "*", "foo", "*"},
886		{"foo", "*", "*", "*"},
887	}
888	var actual [][]string
889	for _, ixn := range resp.Matches[0] {
890		actual = append(actual, []string{
891			ixn.SourceNS,
892			ixn.SourceName,
893			ixn.DestinationNS,
894			ixn.DestinationName,
895		})
896	}
897	assert.Equal(expected, actual)
898}
899
900// Test matching with ACLs
901func TestIntentionMatch_acl(t *testing.T) {
902	t.Parallel()
903
904	assert := assert.New(t)
905	dir1, s1 := testServerWithConfig(t, func(c *Config) {
906		c.ACLDatacenter = "dc1"
907		c.ACLMasterToken = "root"
908		c.ACLDefaultPolicy = "deny"
909	})
910	defer os.RemoveAll(dir1)
911	defer s1.Shutdown()
912	codec := rpcClient(t, s1)
913	defer codec.Close()
914
915	testrpc.WaitForLeader(t, s1.RPC, "dc1")
916
917	// Create an ACL with service write permissions. This will grant
918	// intentions read.
919	var token string
920	{
921		var rules = `
922service "bar" {
923	policy = "write"
924}`
925
926		req := structs.ACLRequest{
927			Datacenter: "dc1",
928			Op:         structs.ACLSet,
929			ACL: structs.ACL{
930				Name:  "User token",
931				Type:  structs.ACLTypeClient,
932				Rules: rules,
933			},
934			WriteRequest: structs.WriteRequest{Token: "root"},
935		}
936		assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
937	}
938
939	// Create some records
940	{
941		insert := [][]string{
942			{"foo", "*"},
943			{"foo", "bar"},
944			{"foo", "baz"}, // shouldn't match
945			{"bar", "bar"}, // shouldn't match
946			{"bar", "*"},   // shouldn't match
947			{"*", "*"},
948		}
949
950		for _, v := range insert {
951			ixn := structs.IntentionRequest{
952				Datacenter: "dc1",
953				Op:         structs.IntentionOpCreate,
954				Intention:  structs.TestIntention(t),
955			}
956			ixn.Intention.DestinationNS = v[0]
957			ixn.Intention.DestinationName = v[1]
958			ixn.WriteRequest.Token = "root"
959
960			// Create
961			var reply string
962			assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
963		}
964	}
965
966	// Test with no token
967	{
968		req := &structs.IntentionQueryRequest{
969			Datacenter: "dc1",
970			Match: &structs.IntentionQueryMatch{
971				Type: structs.IntentionMatchDestination,
972				Entries: []structs.IntentionMatchEntry{
973					{
974						Namespace: "foo",
975						Name:      "bar",
976					},
977				},
978			},
979		}
980		var resp structs.IndexedIntentionMatches
981		err := msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp)
982		assert.True(acl.IsErrPermissionDenied(err))
983		assert.Len(resp.Matches, 0)
984	}
985
986	// Test with proper token
987	{
988		req := &structs.IntentionQueryRequest{
989			Datacenter: "dc1",
990			Match: &structs.IntentionQueryMatch{
991				Type: structs.IntentionMatchDestination,
992				Entries: []structs.IntentionMatchEntry{
993					{
994						Namespace: "foo",
995						Name:      "bar",
996					},
997				},
998			},
999			QueryOptions: structs.QueryOptions{Token: token},
1000		}
1001		var resp structs.IndexedIntentionMatches
1002		assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp))
1003		assert.Len(resp.Matches, 1)
1004
1005		expected := [][]string{{"foo", "bar"}, {"foo", "*"}, {"*", "*"}}
1006		var actual [][]string
1007		for _, ixn := range resp.Matches[0] {
1008			actual = append(actual, []string{ixn.DestinationNS, ixn.DestinationName})
1009		}
1010
1011		assert.Equal(expected, actual)
1012	}
1013}
1014
1015// Test the Check method defaults to allow with no ACL set.
1016func TestIntentionCheck_defaultNoACL(t *testing.T) {
1017	t.Parallel()
1018
1019	require := require.New(t)
1020	dir1, s1 := testServer(t)
1021	defer os.RemoveAll(dir1)
1022	defer s1.Shutdown()
1023	codec := rpcClient(t, s1)
1024	defer codec.Close()
1025
1026	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1027
1028	// Test
1029	req := &structs.IntentionQueryRequest{
1030		Datacenter: "dc1",
1031		Check: &structs.IntentionQueryCheck{
1032			SourceNS:        "foo",
1033			SourceName:      "bar",
1034			DestinationNS:   "foo",
1035			DestinationName: "qux",
1036			SourceType:      structs.IntentionSourceConsul,
1037		},
1038	}
1039	var resp structs.IntentionQueryCheckResponse
1040	require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1041	require.True(resp.Allowed)
1042}
1043
1044// Test the Check method defaults to deny with whitelist ACLs.
1045func TestIntentionCheck_defaultACLDeny(t *testing.T) {
1046	t.Parallel()
1047
1048	require := require.New(t)
1049	dir1, s1 := testServerWithConfig(t, func(c *Config) {
1050		c.ACLDatacenter = "dc1"
1051		c.ACLMasterToken = "root"
1052		c.ACLDefaultPolicy = "deny"
1053	})
1054	defer os.RemoveAll(dir1)
1055	defer s1.Shutdown()
1056	codec := rpcClient(t, s1)
1057	defer codec.Close()
1058
1059	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1060
1061	// Check
1062	req := &structs.IntentionQueryRequest{
1063		Datacenter: "dc1",
1064		Check: &structs.IntentionQueryCheck{
1065			SourceNS:        "foo",
1066			SourceName:      "bar",
1067			DestinationNS:   "foo",
1068			DestinationName: "qux",
1069			SourceType:      structs.IntentionSourceConsul,
1070		},
1071	}
1072	req.Token = "root"
1073	var resp structs.IntentionQueryCheckResponse
1074	require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1075	require.False(resp.Allowed)
1076}
1077
1078// Test the Check method defaults to deny with blacklist ACLs.
1079func TestIntentionCheck_defaultACLAllow(t *testing.T) {
1080	t.Parallel()
1081
1082	require := require.New(t)
1083	dir1, s1 := testServerWithConfig(t, func(c *Config) {
1084		c.ACLDatacenter = "dc1"
1085		c.ACLMasterToken = "root"
1086		c.ACLDefaultPolicy = "allow"
1087	})
1088	defer os.RemoveAll(dir1)
1089	defer s1.Shutdown()
1090	codec := rpcClient(t, s1)
1091	defer codec.Close()
1092
1093	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1094
1095	// Check
1096	req := &structs.IntentionQueryRequest{
1097		Datacenter: "dc1",
1098		Check: &structs.IntentionQueryCheck{
1099			SourceNS:        "foo",
1100			SourceName:      "bar",
1101			DestinationNS:   "foo",
1102			DestinationName: "qux",
1103			SourceType:      structs.IntentionSourceConsul,
1104		},
1105	}
1106	req.Token = "root"
1107	var resp structs.IntentionQueryCheckResponse
1108	require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1109	require.True(resp.Allowed)
1110}
1111
1112// Test the Check method requires service:read permission.
1113func TestIntentionCheck_aclDeny(t *testing.T) {
1114	t.Parallel()
1115
1116	require := require.New(t)
1117	dir1, s1 := testServerWithConfig(t, func(c *Config) {
1118		c.ACLDatacenter = "dc1"
1119		c.ACLMasterToken = "root"
1120		c.ACLDefaultPolicy = "deny"
1121	})
1122	defer os.RemoveAll(dir1)
1123	defer s1.Shutdown()
1124	codec := rpcClient(t, s1)
1125	defer codec.Close()
1126
1127	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1128
1129	// Create an ACL with service read permissions. This will grant permission.
1130	var token string
1131	{
1132		var rules = `
1133service "bar" {
1134	policy = "read"
1135}`
1136
1137		req := structs.ACLRequest{
1138			Datacenter: "dc1",
1139			Op:         structs.ACLSet,
1140			ACL: structs.ACL{
1141				Name:  "User token",
1142				Type:  structs.ACLTypeClient,
1143				Rules: rules,
1144			},
1145			WriteRequest: structs.WriteRequest{Token: "root"},
1146		}
1147		require.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
1148	}
1149
1150	// Check
1151	req := &structs.IntentionQueryRequest{
1152		Datacenter: "dc1",
1153		Check: &structs.IntentionQueryCheck{
1154			SourceNS:        "foo",
1155			SourceName:      "qux",
1156			DestinationNS:   "foo",
1157			DestinationName: "baz",
1158			SourceType:      structs.IntentionSourceConsul,
1159		},
1160	}
1161	req.Token = token
1162	var resp structs.IntentionQueryCheckResponse
1163	err := msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp)
1164	require.True(acl.IsErrPermissionDenied(err))
1165}
1166
1167// Test the Check method returns allow/deny properly.
1168func TestIntentionCheck_match(t *testing.T) {
1169	t.Parallel()
1170
1171	require := require.New(t)
1172	dir1, s1 := testServerWithConfig(t, func(c *Config) {
1173		c.ACLDatacenter = "dc1"
1174		c.ACLMasterToken = "root"
1175		c.ACLDefaultPolicy = "deny"
1176	})
1177	defer os.RemoveAll(dir1)
1178	defer s1.Shutdown()
1179	codec := rpcClient(t, s1)
1180	defer codec.Close()
1181
1182	testrpc.WaitForLeader(t, s1.RPC, "dc1")
1183
1184	// Create an ACL with service read permissions. This will grant permission.
1185	var token string
1186	{
1187		var rules = `
1188service "bar" {
1189	policy = "read"
1190}`
1191
1192		req := structs.ACLRequest{
1193			Datacenter: "dc1",
1194			Op:         structs.ACLSet,
1195			ACL: structs.ACL{
1196				Name:  "User token",
1197				Type:  structs.ACLTypeClient,
1198				Rules: rules,
1199			},
1200			WriteRequest: structs.WriteRequest{Token: "root"},
1201		}
1202		require.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
1203	}
1204
1205	// Create some intentions
1206	{
1207		insert := [][]string{
1208			{"foo", "*", "foo", "*"},
1209			{"foo", "*", "foo", "bar"},
1210			{"bar", "*", "foo", "bar"}, // duplicate destination different source
1211		}
1212
1213		for _, v := range insert {
1214			ixn := structs.IntentionRequest{
1215				Datacenter: "dc1",
1216				Op:         structs.IntentionOpCreate,
1217				Intention: &structs.Intention{
1218					SourceNS:        v[0],
1219					SourceName:      v[1],
1220					DestinationNS:   v[2],
1221					DestinationName: v[3],
1222					Action:          structs.IntentionActionAllow,
1223				},
1224			}
1225			ixn.WriteRequest.Token = "root"
1226
1227			// Create
1228			var reply string
1229			require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
1230		}
1231	}
1232
1233	// Check
1234	req := &structs.IntentionQueryRequest{
1235		Datacenter: "dc1",
1236		Check: &structs.IntentionQueryCheck{
1237			SourceNS:        "foo",
1238			SourceName:      "qux",
1239			DestinationNS:   "foo",
1240			DestinationName: "bar",
1241			SourceType:      structs.IntentionSourceConsul,
1242		},
1243	}
1244	req.Token = token
1245	var resp structs.IntentionQueryCheckResponse
1246	require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1247	require.True(resp.Allowed)
1248
1249	// Test no match for sanity
1250	{
1251		req := &structs.IntentionQueryRequest{
1252			Datacenter: "dc1",
1253			Check: &structs.IntentionQueryCheck{
1254				SourceNS:        "baz",
1255				SourceName:      "qux",
1256				DestinationNS:   "foo",
1257				DestinationName: "bar",
1258				SourceType:      structs.IntentionSourceConsul,
1259			},
1260		}
1261		req.Token = token
1262		var resp structs.IntentionQueryCheckResponse
1263		require.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
1264		require.False(resp.Allowed)
1265	}
1266}
1267