1package agent
2
3import (
4	"fmt"
5	"io"
6	"testing"
7	"time"
8
9	"github.com/armon/go-metrics"
10	"github.com/hashicorp/go-hclog"
11	"github.com/hashicorp/serf/serf"
12
13	"github.com/hashicorp/consul/acl"
14	"github.com/hashicorp/consul/agent/config"
15	"github.com/hashicorp/consul/agent/consul"
16	"github.com/hashicorp/consul/agent/local"
17	"github.com/hashicorp/consul/agent/structs"
18	"github.com/hashicorp/consul/lib"
19	"github.com/hashicorp/consul/sdk/testutil"
20	"github.com/hashicorp/consul/types"
21
22	"github.com/stretchr/testify/require"
23)
24
25type authzResolver func(string) (structs.ACLIdentity, acl.Authorizer, error)
26type identResolver func(string) (structs.ACLIdentity, error)
27
28type TestACLAgent struct {
29	resolveAuthzFn authzResolver
30	resolveIdentFn identResolver
31
32	*Agent
33}
34
35// NewTestACLAgent does just enough so that all the code within agent/acl.go can work
36// Basically it needs a local state for some of the vet* functions, a logger and a delegate.
37// The key is that we are the delegate so we can control the ResolveToken responses
38func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzResolver, resolveIdent identResolver) *TestACLAgent {
39	t.Helper()
40
41	a := &TestACLAgent{resolveAuthzFn: resolveAuthz, resolveIdentFn: resolveIdent}
42
43	dataDir := testutil.TempDir(t, "acl-agent")
44
45	logBuffer := testutil.NewLogBuffer(t)
46	loader := func(source config.Source) (config.LoadResult, error) {
47		dataDir := fmt.Sprintf(`data_dir = "%s"`, dataDir)
48		opts := config.LoadOpts{
49			HCL:           []string{TestConfigHCL(NodeID()), hcl, dataDir},
50			DefaultConfig: source,
51		}
52		result, err := config.Load(opts)
53		if result.RuntimeConfig != nil {
54			result.RuntimeConfig.Telemetry.Disable = true
55		}
56		return result, err
57	}
58	bd, err := NewBaseDeps(loader, logBuffer)
59	require.NoError(t, err)
60
61	bd.Logger = hclog.NewInterceptLogger(&hclog.LoggerOptions{
62		Name:       name,
63		Level:      hclog.Debug,
64		Output:     logBuffer,
65		TimeFormat: "04:05.000",
66	})
67	bd.MetricsHandler = metrics.NewInmemSink(1*time.Second, time.Minute)
68
69	agent, err := New(bd)
70	require.NoError(t, err)
71
72	agent.delegate = a
73	agent.State = local.NewState(LocalConfig(bd.RuntimeConfig), bd.Logger, bd.Tokens)
74	agent.State.TriggerSyncChanges = func() {}
75	a.Agent = agent
76	return a
77}
78
79func (a *TestACLAgent) UseLegacyACLs() bool {
80	return false
81}
82
83func (a *TestACLAgent) ResolveToken(secretID string) (acl.Authorizer, error) {
84	if a.resolveAuthzFn == nil {
85		return nil, fmt.Errorf("ResolveToken call is unexpected - no authz resolver callback set")
86	}
87
88	_, authz, err := a.resolveAuthzFn(secretID)
89	return authz, err
90}
91
92func (a *TestACLAgent) ResolveTokenToIdentityAndAuthorizer(secretID string) (structs.ACLIdentity, acl.Authorizer, error) {
93	if a.resolveAuthzFn == nil {
94		return nil, nil, fmt.Errorf("ResolveTokenToIdentityAndAuthorizer call is unexpected - no authz resolver callback set")
95	}
96
97	return a.resolveAuthzFn(secretID)
98}
99
100func (a *TestACLAgent) ResolveTokenToIdentity(secretID string) (structs.ACLIdentity, error) {
101	if a.resolveIdentFn == nil {
102		return nil, fmt.Errorf("ResolveTokenToIdentity call is unexpected - no ident resolver callback set")
103	}
104
105	return a.resolveIdentFn(secretID)
106}
107
108func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (acl.Authorizer, error) {
109	identity, authz, err := a.ResolveTokenToIdentityAndAuthorizer(secretID)
110	if err != nil {
111		return nil, err
112	}
113
114	// Default the EnterpriseMeta based on the Tokens meta or actual defaults
115	// in the case of unknown identity
116	if identity != nil {
117		entMeta.Merge(identity.EnterpriseMetadata())
118	} else {
119		entMeta.Merge(structs.DefaultEnterpriseMeta())
120	}
121
122	// Use the meta to fill in the ACL authorization context
123	entMeta.FillAuthzContext(authzContext)
124
125	return authz, err
126}
127
128// All of these are stubs to satisfy the interface
129func (a *TestACLAgent) GetLANCoordinate() (lib.CoordinateSet, error) {
130	return nil, fmt.Errorf("Unimplemented")
131}
132func (a *TestACLAgent) Leave() error {
133	return fmt.Errorf("Unimplemented")
134}
135func (a *TestACLAgent) LANMembers() []serf.Member {
136	return nil
137}
138func (a *TestACLAgent) LANMembersAllSegments() ([]serf.Member, error) {
139	return nil, fmt.Errorf("Unimplemented")
140}
141func (a *TestACLAgent) LANSegmentMembers(segment string) ([]serf.Member, error) {
142	return nil, fmt.Errorf("Unimplemented")
143}
144func (a *TestACLAgent) LocalMember() serf.Member {
145	return serf.Member{}
146}
147func (a *TestACLAgent) JoinLAN(addrs []string) (n int, err error) {
148	return 0, fmt.Errorf("Unimplemented")
149}
150func (a *TestACLAgent) RemoveFailedNode(node string, prune bool) error {
151	return fmt.Errorf("Unimplemented")
152}
153
154func (a *TestACLAgent) RPC(method string, args interface{}, reply interface{}) error {
155	return fmt.Errorf("Unimplemented")
156}
157func (a *TestACLAgent) SnapshotRPC(args *structs.SnapshotRequest, in io.Reader, out io.Writer, replyFn structs.SnapshotReplyFn) error {
158	return fmt.Errorf("Unimplemented")
159}
160func (a *TestACLAgent) Shutdown() error {
161	return fmt.Errorf("Unimplemented")
162}
163func (a *TestACLAgent) Stats() map[string]map[string]string {
164	return nil
165}
166func (a *TestACLAgent) ReloadConfig(_ consul.ReloadableConfig) error {
167	return fmt.Errorf("Unimplemented")
168}
169
170func TestACL_Version8EnabledByDefault(t *testing.T) {
171	t.Parallel()
172
173	called := false
174	resolveFn := func(string) (structs.ACLIdentity, acl.Authorizer, error) {
175		called = true
176		return nil, nil, acl.ErrNotFound
177	}
178	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), resolveFn, nil)
179
180	_, err := a.delegate.ResolveTokenAndDefaultMeta("nope", nil, nil)
181	require.Error(t, err)
182	require.True(t, called)
183}
184
185func authzFromPolicy(policy *acl.Policy, cfg *acl.Config) (acl.Authorizer, error) {
186	return acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, cfg)
187}
188
189type testToken struct {
190	token structs.ACLToken
191	// yes the rules can exist on the token itself but that is legacy behavior
192	// that I would prefer these tests not rely on
193	rules string
194}
195
196var (
197	nodeROSecret    = "7e80d017-bccc-492f-8dec-65f03aeaebf3"
198	nodeRWSecret    = "e3586ee5-02a2-4bf4-9ec3-9c4be7606e8c"
199	serviceROSecret = "3d2c8552-df3b-4da7-9890-36885cbf56ac"
200	serviceRWSecret = "4a1017a2-f788-4be3-93f2-90566f1340bb"
201	otherRWSecret   = "a38e8016-91b6-4876-b3e7-a307abbb2002"
202
203	testTokens = map[string]testToken{
204		nodeROSecret: {
205			token: structs.ACLToken{
206				AccessorID: "9df2d1a4-2d07-414e-8ead-6053f56ed2eb",
207				SecretID:   nodeROSecret,
208			},
209			rules: `node_prefix "Node" { policy = "read" }`,
210		},
211		nodeRWSecret: {
212			token: structs.ACLToken{
213				AccessorID: "efb6b7d5-d343-47c1-b4cb-aa6b94d2f490",
214				SecretID:   nodeRWSecret,
215			},
216			rules: `node_prefix "Node" { policy = "write" }`,
217		},
218		serviceROSecret: {
219			token: structs.ACLToken{
220				AccessorID: "0da53edb-36e5-4603-9c31-79965bad45f5",
221				SecretID:   serviceROSecret,
222			},
223			rules: `service_prefix "service" { policy = "read" }`,
224		},
225		serviceRWSecret: {
226			token: structs.ACLToken{
227				AccessorID: "52504258-137a-41e6-9326-01f40e80872e",
228				SecretID:   serviceRWSecret,
229			},
230			rules: `service_prefix "service" { policy = "write" }`,
231		},
232		otherRWSecret: {
233			token: structs.ACLToken{
234				AccessorID: "5e032c5b-c39e-4552-b5ad-8a9365b099c4",
235				SecretID:   otherRWSecret,
236			},
237			rules: `service_prefix "other" { policy = "write" }`,
238		},
239	}
240)
241
242func catalogPolicy(token string) (structs.ACLIdentity, acl.Authorizer, error) {
243	tok, ok := testTokens[token]
244	if !ok {
245		return nil, nil, acl.ErrNotFound
246	}
247
248	policy, err := acl.NewPolicyFromSource("", 0, tok.rules, acl.SyntaxCurrent, nil, nil)
249	if err != nil {
250		return nil, nil, err
251	}
252
253	authz, err := authzFromPolicy(policy, nil)
254	return &tok.token, authz, err
255}
256
257func catalogIdent(token string) (structs.ACLIdentity, error) {
258	tok, ok := testTokens[token]
259	if !ok {
260		return nil, acl.ErrNotFound
261	}
262
263	return &tok.token, nil
264}
265
266func TestACL_vetServiceRegister(t *testing.T) {
267	t.Parallel()
268	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent)
269
270	// Register a new service, with permission.
271	err := a.vetServiceRegister(serviceRWSecret, &structs.NodeService{
272		ID:      "my-service",
273		Service: "service",
274	})
275	require.NoError(t, err)
276
277	// Register a new service without write privs.
278	err = a.vetServiceRegister(serviceROSecret, &structs.NodeService{
279		ID:      "my-service",
280		Service: "service",
281	})
282	require.True(t, acl.IsErrPermissionDenied(err))
283
284	// Try to register over a service without write privs to the existing
285	// service.
286	a.State.AddService(&structs.NodeService{
287		ID:      "my-service",
288		Service: "other",
289	}, "")
290	err = a.vetServiceRegister(serviceRWSecret, &structs.NodeService{
291		ID:      "my-service",
292		Service: "service",
293	})
294	require.True(t, acl.IsErrPermissionDenied(err))
295}
296
297func TestACL_vetServiceUpdate(t *testing.T) {
298	t.Parallel()
299	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent)
300
301	// Update a service that doesn't exist.
302	err := a.vetServiceUpdate(serviceRWSecret, structs.NewServiceID("my-service", nil))
303	require.Error(t, err)
304	require.Contains(t, err.Error(), "Unknown service")
305
306	// Update with write privs.
307	a.State.AddService(&structs.NodeService{
308		ID:      "my-service",
309		Service: "service",
310	}, "")
311	err = a.vetServiceUpdate(serviceRWSecret, structs.NewServiceID("my-service", nil))
312	require.NoError(t, err)
313
314	// Update without write privs.
315	err = a.vetServiceUpdate(serviceROSecret, structs.NewServiceID("my-service", nil))
316	require.Error(t, err)
317	require.True(t, acl.IsErrPermissionDenied(err))
318}
319
320func TestACL_vetCheckRegister(t *testing.T) {
321	t.Parallel()
322	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent)
323
324	// Register a new service check with write privs.
325	err := a.vetCheckRegister(serviceRWSecret, &structs.HealthCheck{
326		CheckID:     types.CheckID("my-check"),
327		ServiceID:   "my-service",
328		ServiceName: "service",
329	})
330	require.NoError(t, err)
331
332	// Register a new service check without write privs.
333	err = a.vetCheckRegister(serviceROSecret, &structs.HealthCheck{
334		CheckID:     types.CheckID("my-check"),
335		ServiceID:   "my-service",
336		ServiceName: "service",
337	})
338	require.Error(t, err)
339	require.True(t, acl.IsErrPermissionDenied(err))
340
341	// Register a new node check with write privs.
342	err = a.vetCheckRegister(nodeRWSecret, &structs.HealthCheck{
343		CheckID: types.CheckID("my-check"),
344	})
345	require.NoError(t, err)
346
347	// Register a new node check without write privs.
348	err = a.vetCheckRegister(nodeROSecret, &structs.HealthCheck{
349		CheckID: types.CheckID("my-check"),
350	})
351	require.Error(t, err)
352	require.True(t, acl.IsErrPermissionDenied(err))
353
354	// Try to register over a service check without write privs to the
355	// existing service.
356	a.State.AddService(&structs.NodeService{
357		ID:      "my-service",
358		Service: "service",
359	}, "")
360	a.State.AddCheck(&structs.HealthCheck{
361		CheckID:     types.CheckID("my-check"),
362		ServiceID:   "my-service",
363		ServiceName: "other",
364	}, "")
365	err = a.vetCheckRegister(serviceRWSecret, &structs.HealthCheck{
366		CheckID:     types.CheckID("my-check"),
367		ServiceID:   "my-service",
368		ServiceName: "service",
369	})
370	require.Error(t, err)
371	require.True(t, acl.IsErrPermissionDenied(err))
372
373	// Try to register over a node check without write privs to the node.
374	a.State.AddCheck(&structs.HealthCheck{
375		CheckID: types.CheckID("my-node-check"),
376	}, "")
377	err = a.vetCheckRegister(serviceRWSecret, &structs.HealthCheck{
378		CheckID:     types.CheckID("my-node-check"),
379		ServiceID:   "my-service",
380		ServiceName: "service",
381	})
382	require.Error(t, err)
383	require.True(t, acl.IsErrPermissionDenied(err))
384}
385
386func TestACL_vetCheckUpdate(t *testing.T) {
387	t.Parallel()
388	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent)
389
390	// Update a check that doesn't exist.
391	err := a.vetCheckUpdate(nodeRWSecret, structs.NewCheckID("my-check", nil))
392	require.Error(t, err)
393	require.Contains(t, err.Error(), "Unknown check")
394
395	// Update service check with write privs.
396	a.State.AddService(&structs.NodeService{
397		ID:      "my-service",
398		Service: "service",
399	}, "")
400	a.State.AddCheck(&structs.HealthCheck{
401		CheckID:     types.CheckID("my-service-check"),
402		ServiceID:   "my-service",
403		ServiceName: "service",
404	}, "")
405	err = a.vetCheckUpdate(serviceRWSecret, structs.NewCheckID("my-service-check", nil))
406	require.NoError(t, err)
407
408	// Update service check without write privs.
409	err = a.vetCheckUpdate(serviceROSecret, structs.NewCheckID("my-service-check", nil))
410	require.Error(t, err)
411	require.True(t, acl.IsErrPermissionDenied(err), "not permission denied: %s", err.Error())
412
413	// Update node check with write privs.
414	a.State.AddCheck(&structs.HealthCheck{
415		CheckID: types.CheckID("my-node-check"),
416	}, "")
417	err = a.vetCheckUpdate(nodeRWSecret, structs.NewCheckID("my-node-check", nil))
418	require.NoError(t, err)
419
420	// Update without write privs.
421	err = a.vetCheckUpdate(nodeROSecret, structs.NewCheckID("my-node-check", nil))
422	require.Error(t, err)
423	require.True(t, acl.IsErrPermissionDenied(err))
424}
425
426func TestACL_filterMembers(t *testing.T) {
427	t.Parallel()
428	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent)
429
430	var members []serf.Member
431	require.NoError(t, a.filterMembers(nodeROSecret, &members))
432	require.Len(t, members, 0)
433
434	members = []serf.Member{
435		{Name: "Node 1"},
436		{Name: "Nope"},
437		{Name: "Node 2"},
438	}
439	require.NoError(t, a.filterMembers(nodeROSecret, &members))
440	require.Len(t, members, 2)
441	require.Equal(t, members[0].Name, "Node 1")
442	require.Equal(t, members[1].Name, "Node 2")
443}
444
445func TestACL_filterServices(t *testing.T) {
446	t.Parallel()
447	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent)
448
449	services := make(map[structs.ServiceID]*structs.NodeService)
450	require.NoError(t, a.filterServices(nodeROSecret, &services))
451
452	services[structs.NewServiceID("my-service", nil)] = &structs.NodeService{ID: "my-service", Service: "service"}
453	services[structs.NewServiceID("my-other", nil)] = &structs.NodeService{ID: "my-other", Service: "other"}
454	require.NoError(t, a.filterServices(serviceROSecret, &services))
455	require.Contains(t, services, structs.NewServiceID("my-service", nil))
456	require.NotContains(t, services, structs.NewServiceID("my-other", nil))
457}
458
459func TestACL_filterChecks(t *testing.T) {
460	t.Parallel()
461	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent)
462
463	checks := make(map[structs.CheckID]*structs.HealthCheck)
464	require.NoError(t, a.filterChecks(nodeROSecret, &checks))
465
466	checks[structs.NewCheckID("my-node", nil)] = &structs.HealthCheck{}
467	checks[structs.NewCheckID("my-service", nil)] = &structs.HealthCheck{ServiceName: "service"}
468	checks[structs.NewCheckID("my-other", nil)] = &structs.HealthCheck{ServiceName: "other"}
469	require.NoError(t, a.filterChecks(serviceROSecret, &checks))
470	_, ok := checks[structs.NewCheckID("my-node", nil)]
471	require.False(t, ok)
472	_, ok = checks[structs.NewCheckID("my-service", nil)]
473	require.True(t, ok)
474	_, ok = checks[structs.NewCheckID("my-other", nil)]
475	require.False(t, ok)
476
477	checks[structs.NewCheckID("my-node", nil)] = &structs.HealthCheck{}
478	checks[structs.NewCheckID("my-service", nil)] = &structs.HealthCheck{ServiceName: "service"}
479	checks[structs.NewCheckID("my-other", nil)] = &structs.HealthCheck{ServiceName: "other"}
480	require.NoError(t, a.filterChecks(nodeROSecret, &checks))
481	_, ok = checks[structs.NewCheckID("my-node", nil)]
482	require.True(t, ok)
483	_, ok = checks[structs.NewCheckID("my-service", nil)]
484	require.False(t, ok)
485	_, ok = checks[structs.NewCheckID("my-other", nil)]
486	require.False(t, ok)
487}
488
489// TODO: remove?
490func TestACL_ResolveIdentity(t *testing.T) {
491	t.Parallel()
492	a := NewTestACLAgent(t, t.Name(), TestACLConfig(), nil, catalogIdent)
493
494	// this test is meant to ensure we are calling the correct function
495	// which is ResolveTokenToIdentity on the Agent delegate. Our
496	// nil authz resolver will cause it to emit an error if used
497	ident, err := a.delegate.ResolveTokenToIdentity(nodeROSecret)
498	require.NoError(t, err)
499	require.NotNil(t, ident)
500
501	// just double checkingto ensure if we had used the wrong function
502	// that an error would be produced
503	_, err = a.delegate.ResolveTokenAndDefaultMeta(nodeROSecret, nil, nil)
504	require.Error(t, err)
505
506}
507