github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/stream/event_broker_test.go (about)

     1  package stream
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/go-memdb"
    10  	"github.com/hashicorp/nomad/acl"
    11  	"github.com/hashicorp/nomad/ci"
    12  	"github.com/hashicorp/nomad/helper/pointer"
    13  	"github.com/hashicorp/nomad/helper/uuid"
    14  	"github.com/hashicorp/nomad/nomad/mock"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestEventBroker_PublishChangesAndSubscribe(t *testing.T) {
    21  	ci.Parallel(t)
    22  
    23  	subscription := &SubscribeRequest{
    24  		Topics: map[structs.Topic][]string{
    25  			"Test": {"sub-key"},
    26  		},
    27  	}
    28  	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    29  	defer cancel()
    30  
    31  	publisher, err := NewEventBroker(ctx, nil, EventBrokerCfg{EventBufferSize: 100})
    32  	require.NoError(t, err)
    33  
    34  	sub, err := publisher.Subscribe(subscription)
    35  	require.NoError(t, err)
    36  	eventCh := consumeSubscription(ctx, sub)
    37  
    38  	// Now subscriber should block waiting for updates
    39  	assertNoResult(t, eventCh)
    40  
    41  	events := []structs.Event{{
    42  		Index:   1,
    43  		Topic:   "Test",
    44  		Key:     "sub-key",
    45  		Payload: "sample payload",
    46  	}}
    47  	publisher.Publish(&structs.Events{Index: 1, Events: events})
    48  
    49  	// Subscriber should see the published event
    50  	result := nextResult(t, eventCh)
    51  	require.NoError(t, result.Err)
    52  	expected := []structs.Event{{Payload: "sample payload", Key: "sub-key", Topic: "Test", Index: 1}}
    53  	require.Equal(t, expected, result.Events)
    54  
    55  	// Now subscriber should block waiting for updates
    56  	assertNoResult(t, eventCh)
    57  
    58  	// Publish a second event
    59  	events = []structs.Event{{
    60  		Index:   2,
    61  		Topic:   "Test",
    62  		Key:     "sub-key",
    63  		Payload: "sample payload 2",
    64  	}}
    65  	publisher.Publish(&structs.Events{Index: 2, Events: events})
    66  
    67  	result = nextResult(t, eventCh)
    68  	require.NoError(t, result.Err)
    69  	expected = []structs.Event{{Payload: "sample payload 2", Key: "sub-key", Topic: "Test", Index: 2}}
    70  	require.Equal(t, expected, result.Events)
    71  }
    72  
    73  func TestEventBroker_ShutdownClosesSubscriptions(t *testing.T) {
    74  	ci.Parallel(t)
    75  
    76  	ctx, cancel := context.WithCancel(context.Background())
    77  	t.Cleanup(cancel)
    78  
    79  	publisher, err := NewEventBroker(ctx, nil, EventBrokerCfg{})
    80  	require.NoError(t, err)
    81  
    82  	sub1, err := publisher.Subscribe(&SubscribeRequest{})
    83  	require.NoError(t, err)
    84  	defer sub1.Unsubscribe()
    85  
    86  	sub2, err := publisher.Subscribe(&SubscribeRequest{})
    87  	require.NoError(t, err)
    88  	defer sub2.Unsubscribe()
    89  
    90  	cancel() // Shutdown
    91  
    92  	err = consumeSub(context.Background(), sub1)
    93  	require.Equal(t, err, ErrSubscriptionClosed)
    94  
    95  	_, err = sub2.Next(context.Background())
    96  	require.Equal(t, err, ErrSubscriptionClosed)
    97  }
    98  
    99  // TestEventBroker_EmptyReqToken_DistinctSubscriptions tests subscription
   100  // hanlding behavior when ACLs are disabled (request Token is empty).
   101  // Subscriptions are mapped by their request token.  when that token is empty,
   102  // the subscriptions should still be handled indeppendtly of each other when
   103  // unssubscribing.
   104  func TestEventBroker_EmptyReqToken_DistinctSubscriptions(t *testing.T) {
   105  	ci.Parallel(t)
   106  
   107  	ctx, cancel := context.WithCancel(context.Background())
   108  	t.Cleanup(cancel)
   109  
   110  	publisher, err := NewEventBroker(ctx, nil, EventBrokerCfg{})
   111  	require.NoError(t, err)
   112  
   113  	// first subscription, empty token
   114  	sub1, err := publisher.Subscribe(&SubscribeRequest{})
   115  	require.NoError(t, err)
   116  	defer sub1.Unsubscribe()
   117  
   118  	// second subscription, empty token
   119  	sub2, err := publisher.Subscribe(&SubscribeRequest{})
   120  	require.NoError(t, err)
   121  	require.NotNil(t, sub2)
   122  
   123  	sub1.Unsubscribe()
   124  
   125  	require.Equal(t, subscriptionStateOpen, atomic.LoadUint32(&sub2.state))
   126  }
   127  
   128  func TestEventBroker_handleACLUpdates_TokenDeleted(t *testing.T) {
   129  	ci.Parallel(t)
   130  
   131  	ctx, cancel := context.WithCancel(context.Background())
   132  	t.Cleanup(cancel)
   133  
   134  	publisher, err := NewEventBroker(ctx, nil, EventBrokerCfg{})
   135  	require.NoError(t, err)
   136  
   137  	sub1, err := publisher.Subscribe(&SubscribeRequest{
   138  		Topics: map[structs.Topic][]string{
   139  			"*": {"*"},
   140  		},
   141  		Token: "foo",
   142  	})
   143  	require.NoError(t, err)
   144  	defer sub1.Unsubscribe()
   145  
   146  	aclEvent := structs.Event{
   147  		Topic:   structs.TopicACLToken,
   148  		Type:    structs.TypeACLTokenDeleted,
   149  		Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: "foo"}),
   150  	}
   151  
   152  	publisher.Publish(&structs.Events{Index: 100, Events: []structs.Event{aclEvent}})
   153  	for {
   154  		_, err := sub1.Next(ctx)
   155  		if err == ErrSubscriptionClosed {
   156  			break
   157  		}
   158  	}
   159  
   160  	out, err := sub1.Next(ctx)
   161  	require.Error(t, err)
   162  	require.Equal(t, ErrSubscriptionClosed, err)
   163  	require.Equal(t, structs.Events{}, out)
   164  }
   165  
   166  type fakeACLDelegate struct {
   167  	tokenProvider ACLTokenProvider
   168  }
   169  
   170  func (d *fakeACLDelegate) TokenProvider() ACLTokenProvider {
   171  	return d.tokenProvider
   172  }
   173  
   174  type fakeACLTokenProvider struct {
   175  	policy    *structs.ACLPolicy
   176  	policyErr error
   177  	token     *structs.ACLToken
   178  	tokenErr  error
   179  	role      *structs.ACLRole
   180  	roleErr   error
   181  }
   182  
   183  func (p *fakeACLTokenProvider) ACLTokenBySecretID(_ memdb.WatchSet, _ string) (*structs.ACLToken, error) {
   184  	return p.token, p.tokenErr
   185  }
   186  
   187  func (p *fakeACLTokenProvider) ACLPolicyByName(_ memdb.WatchSet, _ string) (*structs.ACLPolicy, error) {
   188  	return p.policy, p.policyErr
   189  }
   190  
   191  func (p *fakeACLTokenProvider) GetACLRoleByID(_ memdb.WatchSet, _ string) (*structs.ACLRole, error) {
   192  	return p.role, p.roleErr
   193  }
   194  
   195  func TestEventBroker_handleACLUpdates_policyUpdated(t *testing.T) {
   196  	ci.Parallel(t)
   197  
   198  	ctx, cancel := context.WithCancel(context.Background())
   199  	t.Cleanup(cancel)
   200  
   201  	secretID := "some-secret-id"
   202  	cases := []struct {
   203  		policyBeforeRules string
   204  		policyAfterRules  string
   205  		topics            map[structs.Topic][]string
   206  		desc              string
   207  		event             structs.Event
   208  		policyEvent       structs.Event
   209  		shouldUnsubscribe bool
   210  		initialSubErr     bool
   211  	}{
   212  		{
   213  			desc:              "subscribed to deployments and removed access",
   214  			policyBeforeRules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   215  			policyAfterRules:  mock.NamespacePolicy(structs.DefaultNamespace, "", []string{}),
   216  			shouldUnsubscribe: true,
   217  			event: structs.Event{
   218  				Topic: structs.TopicDeployment,
   219  				Type:  structs.TypeDeploymentUpdate,
   220  				Payload: structs.DeploymentEvent{
   221  					Deployment: &structs.Deployment{
   222  						ID: "some-id",
   223  					},
   224  				},
   225  			},
   226  			policyEvent: structs.Event{
   227  				Topic:   structs.TopicACLToken,
   228  				Type:    structs.TypeACLTokenUpserted,
   229  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   230  			},
   231  		},
   232  		{
   233  			desc:              "subscribed to evals and removed access",
   234  			policyBeforeRules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   235  			policyAfterRules:  mock.NamespacePolicy(structs.DefaultNamespace, "", []string{}),
   236  			shouldUnsubscribe: true,
   237  			event: structs.Event{
   238  				Topic: structs.TopicEvaluation,
   239  				Type:  structs.TypeEvalUpdated,
   240  				Payload: structs.EvaluationEvent{
   241  					Evaluation: &structs.Evaluation{
   242  						ID: "some-id",
   243  					},
   244  				},
   245  			},
   246  			policyEvent: structs.Event{
   247  				Topic:   structs.TopicACLToken,
   248  				Type:    structs.TypeACLTokenUpserted,
   249  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   250  			},
   251  		},
   252  		{
   253  			desc:              "subscribed to allocs and removed access",
   254  			policyBeforeRules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   255  			policyAfterRules:  mock.NamespacePolicy(structs.DefaultNamespace, "", []string{}),
   256  			shouldUnsubscribe: true,
   257  			event: structs.Event{
   258  				Topic: structs.TopicAllocation,
   259  				Type:  structs.TypeAllocationUpdated,
   260  				Payload: structs.AllocationEvent{
   261  					Allocation: &structs.Allocation{
   262  						ID: "some-id",
   263  					},
   264  				},
   265  			},
   266  			policyEvent: structs.Event{
   267  				Topic:   structs.TopicACLToken,
   268  				Type:    structs.TypeACLTokenUpserted,
   269  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   270  			},
   271  		},
   272  		{
   273  			desc:              "subscribed to nodes and removed access",
   274  			policyBeforeRules: mock.NodePolicy(acl.PolicyRead),
   275  			policyAfterRules:  mock.NodePolicy(acl.PolicyDeny),
   276  			shouldUnsubscribe: true,
   277  			event: structs.Event{
   278  				Topic: structs.TopicNode,
   279  				Type:  structs.TypeNodeRegistration,
   280  				Payload: structs.NodeStreamEvent{
   281  					Node: &structs.Node{
   282  						ID: "some-id",
   283  					},
   284  				},
   285  			},
   286  			policyEvent: structs.Event{
   287  				Topic:   structs.TopicACLToken,
   288  				Type:    structs.TypeACLTokenUpserted,
   289  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   290  			},
   291  		},
   292  		{
   293  			desc:              "subscribed to evals in all namespaces and removed access",
   294  			policyBeforeRules: mock.NamespacePolicy("*", "", []string{acl.NamespaceCapabilityReadJob}),
   295  			policyAfterRules:  mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   296  			shouldUnsubscribe: true,
   297  			event: structs.Event{
   298  				Topic:     structs.TopicEvaluation,
   299  				Type:      structs.TypeEvalUpdated,
   300  				Namespace: "foo",
   301  				Payload: structs.EvaluationEvent{
   302  					Evaluation: &structs.Evaluation{
   303  						ID: "some-id",
   304  					},
   305  				},
   306  			},
   307  			policyEvent: structs.Event{
   308  				Topic:   structs.TopicACLToken,
   309  				Type:    structs.TypeACLTokenUpserted,
   310  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   311  			},
   312  		},
   313  		{
   314  			desc:              "subscribed to deployments and no access change",
   315  			policyBeforeRules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   316  			policyAfterRules:  mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   317  			shouldUnsubscribe: false,
   318  			event: structs.Event{
   319  				Topic: structs.TopicDeployment,
   320  				Type:  structs.TypeDeploymentUpdate,
   321  				Payload: structs.DeploymentEvent{
   322  					Deployment: &structs.Deployment{
   323  						ID: "some-id",
   324  					},
   325  				},
   326  			},
   327  			policyEvent: structs.Event{
   328  				Topic:   structs.TopicACLToken,
   329  				Type:    structs.TypeACLTokenUpserted,
   330  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   331  			},
   332  		},
   333  		{
   334  			desc:              "subscribed to evals and no access change",
   335  			policyBeforeRules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   336  			policyAfterRules:  mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   337  			shouldUnsubscribe: false,
   338  			event: structs.Event{
   339  				Topic: structs.TopicEvaluation,
   340  				Type:  structs.TypeEvalUpdated,
   341  				Payload: structs.EvaluationEvent{
   342  					Evaluation: &structs.Evaluation{
   343  						ID: "some-id",
   344  					},
   345  				},
   346  			},
   347  			policyEvent: structs.Event{
   348  				Topic:   structs.TopicACLToken,
   349  				Type:    structs.TypeACLTokenUpserted,
   350  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   351  			},
   352  		},
   353  		{
   354  			desc:              "subscribed to allocs and no access change",
   355  			policyBeforeRules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   356  			policyAfterRules:  mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}),
   357  			shouldUnsubscribe: false,
   358  			event: structs.Event{
   359  				Topic: structs.TopicAllocation,
   360  				Type:  structs.TypeAllocationUpdated,
   361  				Payload: structs.AllocationEvent{
   362  					Allocation: &structs.Allocation{
   363  						ID: "some-id",
   364  					},
   365  				},
   366  			},
   367  			policyEvent: structs.Event{
   368  				Topic:   structs.TopicACLToken,
   369  				Type:    structs.TypeACLTokenUpserted,
   370  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   371  			},
   372  		},
   373  		{
   374  			desc:              "subscribed to nodes and no access change",
   375  			policyBeforeRules: mock.NodePolicy(acl.PolicyRead),
   376  			policyAfterRules:  mock.NodePolicy(acl.PolicyRead),
   377  			shouldUnsubscribe: false,
   378  			event: structs.Event{
   379  				Topic: structs.TopicNode,
   380  				Type:  structs.TypeNodeRegistration,
   381  				Payload: structs.NodeStreamEvent{
   382  					Node: &structs.Node{
   383  						ID: "some-id",
   384  					},
   385  				},
   386  			},
   387  			policyEvent: structs.Event{
   388  				Topic:   structs.TopicACLToken,
   389  				Type:    structs.TypeACLTokenUpserted,
   390  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   391  			},
   392  		},
   393  		{
   394  			desc:              "initial token insufficient privileges",
   395  			initialSubErr:     true,
   396  			policyBeforeRules: mock.NodePolicy(acl.PolicyDeny),
   397  			event: structs.Event{
   398  				Topic: structs.TopicNode,
   399  				Type:  structs.TypeNodeRegistration,
   400  				Payload: structs.NodeStreamEvent{
   401  					Node: &structs.Node{
   402  						ID: "some-id",
   403  					},
   404  				},
   405  			},
   406  			policyEvent: structs.Event{
   407  				Topic:   structs.TopicACLToken,
   408  				Type:    structs.TypeACLTokenUpserted,
   409  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: secretID}),
   410  			},
   411  		},
   412  		{
   413  			desc:              "subscribed to nodes and policy change no change",
   414  			policyBeforeRules: mock.NodePolicy(acl.PolicyRead),
   415  			policyAfterRules:  mock.NodePolicy(acl.PolicyWrite),
   416  			shouldUnsubscribe: false,
   417  			event: structs.Event{
   418  				Topic: structs.TopicNode,
   419  				Type:  structs.TypeNodeRegistration,
   420  				Payload: structs.NodeStreamEvent{
   421  					Node: &structs.Node{
   422  						ID: "some-id",
   423  					},
   424  				},
   425  			},
   426  			policyEvent: structs.Event{
   427  				Topic: structs.TopicACLPolicy,
   428  				Type:  structs.TypeACLPolicyUpserted,
   429  				Payload: &structs.ACLPolicyEvent{
   430  					ACLPolicy: &structs.ACLPolicy{
   431  						Name: "some-policy",
   432  					},
   433  				},
   434  			},
   435  		},
   436  		{
   437  			desc:              "subscribed to nodes and policy change no access",
   438  			policyBeforeRules: mock.NodePolicy(acl.PolicyRead),
   439  			policyAfterRules:  mock.NodePolicy(acl.PolicyDeny),
   440  			shouldUnsubscribe: true,
   441  			event: structs.Event{
   442  				Topic: structs.TopicNode,
   443  				Type:  structs.TypeNodeRegistration,
   444  				Payload: structs.NodeStreamEvent{
   445  					Node: &structs.Node{
   446  						ID: "some-id",
   447  					},
   448  				},
   449  			},
   450  			policyEvent: structs.Event{
   451  				Topic: structs.TopicACLPolicy,
   452  				Type:  structs.TypeACLPolicyUpserted,
   453  				Payload: &structs.ACLPolicyEvent{
   454  					ACLPolicy: &structs.ACLPolicy{
   455  						Name: "some-policy",
   456  					},
   457  				},
   458  			},
   459  		},
   460  		{
   461  			desc:              "subscribed to nodes policy deleted",
   462  			policyBeforeRules: mock.NodePolicy(acl.PolicyRead),
   463  			policyAfterRules:  "",
   464  			shouldUnsubscribe: true,
   465  			event: structs.Event{
   466  				Topic: structs.TopicNode,
   467  				Type:  structs.TypeNodeRegistration,
   468  				Payload: structs.NodeStreamEvent{
   469  					Node: &structs.Node{
   470  						ID: "some-id",
   471  					},
   472  				},
   473  			},
   474  			policyEvent: structs.Event{
   475  				Topic: structs.TopicACLPolicy,
   476  				Type:  structs.TypeACLPolicyDeleted,
   477  				Payload: &structs.ACLPolicyEvent{
   478  					ACLPolicy: &structs.ACLPolicy{
   479  						Name: "some-policy",
   480  					},
   481  				},
   482  			},
   483  		},
   484  	}
   485  
   486  	for _, tc := range cases {
   487  		t.Run(tc.desc, func(t *testing.T) {
   488  
   489  			policy := &structs.ACLPolicy{
   490  				Name:  "some-policy",
   491  				Rules: tc.policyBeforeRules,
   492  			}
   493  			policy.SetHash()
   494  
   495  			tokenProvider := &fakeACLTokenProvider{
   496  				policy: policy,
   497  				token: &structs.ACLToken{
   498  					SecretID: secretID,
   499  					Policies: []string{policy.Name},
   500  				},
   501  			}
   502  
   503  			aclDelegate := &fakeACLDelegate{
   504  				tokenProvider: tokenProvider,
   505  			}
   506  
   507  			publisher, err := NewEventBroker(ctx, aclDelegate, EventBrokerCfg{})
   508  			require.NoError(t, err)
   509  
   510  			var ns string
   511  			if tc.event.Namespace != "" {
   512  				ns = tc.event.Namespace
   513  			} else {
   514  				ns = structs.DefaultNamespace
   515  			}
   516  
   517  			sub, expiryTime, err := publisher.SubscribeWithACLCheck(&SubscribeRequest{
   518  				Topics: map[structs.Topic][]string{
   519  					tc.event.Topic: {"*"},
   520  				},
   521  				Namespace: ns,
   522  				Token:     secretID,
   523  			})
   524  			require.Nil(t, expiryTime)
   525  
   526  			if tc.initialSubErr {
   527  				require.Error(t, err)
   528  				require.Nil(t, sub)
   529  				return
   530  			} else {
   531  				require.NoError(t, err)
   532  			}
   533  			publisher.Publish(&structs.Events{Index: 100, Events: []structs.Event{tc.event}})
   534  
   535  			ctx, cancel := context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   536  			defer cancel()
   537  			_, err = sub.Next(ctx)
   538  			require.NoError(t, err)
   539  
   540  			// Update the mock provider to use the after rules
   541  			policyAfter := &structs.ACLPolicy{
   542  				Name:        "some-new-policy",
   543  				Rules:       tc.policyAfterRules,
   544  				ModifyIndex: 101, // The ModifyIndex is used to caclulate the acl cache key
   545  			}
   546  			policyAfter.SetHash()
   547  
   548  			tokenProvider.policy = policyAfter
   549  
   550  			// Publish ACL event triggering subscription re-evaluation
   551  			publisher.Publish(&structs.Events{Index: 101, Events: []structs.Event{tc.policyEvent}})
   552  			// Publish another event
   553  			publisher.Publish(&structs.Events{Index: 102, Events: []structs.Event{tc.event}})
   554  
   555  			// If we are expecting to unsubscribe consume the subscription
   556  			// until the expected error occurs.
   557  			ctx, cancel = context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   558  			defer cancel()
   559  			if tc.shouldUnsubscribe {
   560  				for {
   561  					_, err = sub.Next(ctx)
   562  					if err != nil {
   563  						if err == context.DeadlineExceeded {
   564  							require.Fail(t, err.Error())
   565  						}
   566  						if err == ErrSubscriptionClosed {
   567  							break
   568  						}
   569  					}
   570  				}
   571  			} else {
   572  				_, err = sub.Next(ctx)
   573  				require.NoError(t, err)
   574  			}
   575  
   576  			publisher.Publish(&structs.Events{Index: 103, Events: []structs.Event{tc.event}})
   577  
   578  			ctx, cancel = context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   579  			defer cancel()
   580  			_, err = sub.Next(ctx)
   581  			if tc.shouldUnsubscribe {
   582  				require.Equal(t, ErrSubscriptionClosed, err)
   583  			} else {
   584  				require.NoError(t, err)
   585  			}
   586  		})
   587  	}
   588  }
   589  
   590  func TestEventBroker_handleACLUpdates_roleUpdated(t *testing.T) {
   591  	ci.Parallel(t)
   592  
   593  	ctx, cancel := context.WithCancel(context.Background())
   594  	t.Cleanup(cancel)
   595  
   596  	// Generate a UUID to use in all tests for the token secret ID and the role
   597  	// ID.
   598  	tokenSecretID := uuid.Generate()
   599  	roleID := uuid.Generate()
   600  
   601  	cases := []struct {
   602  		name                  string
   603  		aclPolicy             *structs.ACLPolicy
   604  		roleBeforePolicyLinks []*structs.ACLRolePolicyLink
   605  		roleAfterPolicyLinks  []*structs.ACLRolePolicyLink
   606  		topics                map[structs.Topic][]string
   607  		event                 structs.Event
   608  		policyEvent           structs.Event
   609  		shouldUnsubscribe     bool
   610  		initialSubErr         bool
   611  	}{
   612  		{
   613  			name: "deployments access policy link removed",
   614  			aclPolicy: &structs.ACLPolicy{
   615  				Name: "test-event-broker-acl-policy",
   616  				Rules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{
   617  					acl.NamespaceCapabilityReadJob},
   618  				),
   619  			},
   620  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   621  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{},
   622  			shouldUnsubscribe:     true,
   623  			event: structs.Event{
   624  				Topic:   structs.TopicDeployment,
   625  				Type:    structs.TypeDeploymentUpdate,
   626  				Payload: structs.DeploymentEvent{Deployment: &structs.Deployment{}},
   627  			},
   628  			policyEvent: structs.Event{
   629  				Topic:   structs.TopicACLToken,
   630  				Type:    structs.TypeACLTokenUpserted,
   631  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   632  			},
   633  		},
   634  		{
   635  			name: "evaluations access policy link removed",
   636  			aclPolicy: &structs.ACLPolicy{
   637  				Name: "test-event-broker-acl-policy",
   638  				Rules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{
   639  					acl.NamespaceCapabilityReadJob},
   640  				),
   641  			},
   642  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   643  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{},
   644  			shouldUnsubscribe:     true,
   645  			event: structs.Event{
   646  				Topic:   structs.TopicEvaluation,
   647  				Type:    structs.TypeEvalUpdated,
   648  				Payload: structs.EvaluationEvent{Evaluation: &structs.Evaluation{}},
   649  			},
   650  			policyEvent: structs.Event{
   651  				Topic:   structs.TopicACLToken,
   652  				Type:    structs.TypeACLTokenUpserted,
   653  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   654  			},
   655  		},
   656  		{
   657  			name: "allocations access policy link removed",
   658  			aclPolicy: &structs.ACLPolicy{
   659  				Name: "test-event-broker-acl-policy",
   660  				Rules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{
   661  					acl.NamespaceCapabilityReadJob},
   662  				),
   663  			},
   664  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   665  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{},
   666  			shouldUnsubscribe:     true,
   667  			event: structs.Event{
   668  				Topic:   structs.TopicAllocation,
   669  				Type:    structs.TypeAllocationUpdated,
   670  				Payload: structs.AllocationEvent{Allocation: &structs.Allocation{}},
   671  			},
   672  			policyEvent: structs.Event{
   673  				Topic:   structs.TopicACLToken,
   674  				Type:    structs.TypeACLTokenUpserted,
   675  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   676  			},
   677  		},
   678  		{
   679  			name: "nodes access policy link removed",
   680  			aclPolicy: &structs.ACLPolicy{
   681  				Name:  "test-event-broker-acl-policy",
   682  				Rules: mock.NodePolicy(acl.PolicyRead),
   683  			},
   684  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   685  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{},
   686  			shouldUnsubscribe:     true,
   687  			event: structs.Event{
   688  				Topic:   structs.TopicNode,
   689  				Type:    structs.TypeNodeRegistration,
   690  				Payload: structs.NodeStreamEvent{Node: &structs.Node{}},
   691  			},
   692  			policyEvent: structs.Event{
   693  				Topic:   structs.TopicACLToken,
   694  				Type:    structs.TypeACLTokenUpserted,
   695  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   696  			},
   697  		},
   698  		{
   699  			name: "deployment access no change",
   700  			aclPolicy: &structs.ACLPolicy{
   701  				Name: "test-event-broker-acl-policy",
   702  				Rules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{
   703  					acl.NamespaceCapabilityReadJob},
   704  				),
   705  			},
   706  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   707  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   708  			shouldUnsubscribe:     false,
   709  			event: structs.Event{
   710  				Topic:   structs.TopicDeployment,
   711  				Type:    structs.TypeDeploymentUpdate,
   712  				Payload: structs.DeploymentEvent{Deployment: &structs.Deployment{}},
   713  			},
   714  			policyEvent: structs.Event{
   715  				Topic:   structs.TopicACLToken,
   716  				Type:    structs.TypeACLTokenUpserted,
   717  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   718  			},
   719  		},
   720  		{
   721  			name: "evaluations access no change",
   722  			aclPolicy: &structs.ACLPolicy{
   723  				Name: "test-event-broker-acl-policy",
   724  				Rules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{
   725  					acl.NamespaceCapabilityReadJob},
   726  				),
   727  			},
   728  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   729  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   730  			shouldUnsubscribe:     false,
   731  			event: structs.Event{
   732  				Topic:   structs.TopicEvaluation,
   733  				Type:    structs.TypeEvalUpdated,
   734  				Payload: structs.EvaluationEvent{Evaluation: &structs.Evaluation{}},
   735  			},
   736  			policyEvent: structs.Event{
   737  				Topic:   structs.TopicACLToken,
   738  				Type:    structs.TypeACLTokenUpserted,
   739  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   740  			},
   741  		},
   742  		{
   743  			name: "allocations access no change",
   744  			aclPolicy: &structs.ACLPolicy{
   745  				Name: "test-event-broker-acl-policy",
   746  				Rules: mock.NamespacePolicy(structs.DefaultNamespace, "", []string{
   747  					acl.NamespaceCapabilityReadJob},
   748  				),
   749  			},
   750  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   751  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   752  			shouldUnsubscribe:     false,
   753  			event: structs.Event{
   754  				Topic:   structs.TopicAllocation,
   755  				Type:    structs.TypeAllocationUpdated,
   756  				Payload: structs.AllocationEvent{Allocation: &structs.Allocation{}},
   757  			},
   758  			policyEvent: structs.Event{
   759  				Topic:   structs.TopicACLToken,
   760  				Type:    structs.TypeACLTokenUpserted,
   761  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   762  			},
   763  		},
   764  		{
   765  			name: "nodes access no change",
   766  			aclPolicy: &structs.ACLPolicy{
   767  				Name:  "test-event-broker-acl-policy",
   768  				Rules: mock.NodePolicy(acl.PolicyRead),
   769  			},
   770  			roleBeforePolicyLinks: []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   771  			roleAfterPolicyLinks:  []*structs.ACLRolePolicyLink{{Name: "test-event-broker-acl-policy"}},
   772  			shouldUnsubscribe:     false,
   773  			event: structs.Event{
   774  				Topic:   structs.TopicNode,
   775  				Type:    structs.TypeNodeRegistration,
   776  				Payload: structs.NodeStreamEvent{Node: &structs.Node{}},
   777  			},
   778  			policyEvent: structs.Event{
   779  				Topic:   structs.TopicACLToken,
   780  				Type:    structs.TypeACLTokenUpserted,
   781  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tokenSecretID}),
   782  			},
   783  		},
   784  	}
   785  
   786  	for _, tc := range cases {
   787  		t.Run(tc.name, func(t *testing.T) {
   788  
   789  			// Build our fake token provider containing the relevant state
   790  			// objects and add this to our new delegate. Keeping the token
   791  			// provider setup separate means we can easily update its state.
   792  			tokenProvider := &fakeACLTokenProvider{
   793  				policy: tc.aclPolicy,
   794  				token: &structs.ACLToken{
   795  					SecretID: tokenSecretID,
   796  					Roles:    []*structs.ACLTokenRoleLink{{ID: roleID}},
   797  				},
   798  				role: &structs.ACLRole{
   799  					ID: uuid.Short(),
   800  					Policies: []*structs.ACLRolePolicyLink{
   801  						{Name: tc.aclPolicy.Name},
   802  					},
   803  				},
   804  			}
   805  			aclDelegate := &fakeACLDelegate{tokenProvider: tokenProvider}
   806  
   807  			publisher, err := NewEventBroker(ctx, aclDelegate, EventBrokerCfg{})
   808  			require.NoError(t, err)
   809  
   810  			ns := structs.DefaultNamespace
   811  			if tc.event.Namespace != "" {
   812  				ns = tc.event.Namespace
   813  			}
   814  
   815  			sub, expiryTime, err := publisher.SubscribeWithACLCheck(&SubscribeRequest{
   816  				Topics:    map[structs.Topic][]string{tc.event.Topic: {"*"}},
   817  				Namespace: ns,
   818  				Token:     tokenSecretID,
   819  			})
   820  			require.Nil(t, expiryTime)
   821  
   822  			if tc.initialSubErr {
   823  				require.Error(t, err)
   824  				require.Nil(t, sub)
   825  				return
   826  			}
   827  
   828  			require.NoError(t, err)
   829  			publisher.Publish(&structs.Events{Index: 100, Events: []structs.Event{tc.event}})
   830  
   831  			ctx, cancel := context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   832  			defer cancel()
   833  			_, err = sub.Next(ctx)
   834  			require.NoError(t, err)
   835  
   836  			// Overwrite the ACL role policy links with the updated version
   837  			// which is expected to cause a change in the subscription.
   838  			tokenProvider.role.Policies = tc.roleAfterPolicyLinks
   839  
   840  			// Publish ACL event triggering subscription re-evaluation
   841  			publisher.Publish(&structs.Events{Index: 101, Events: []structs.Event{tc.policyEvent}})
   842  			publisher.Publish(&structs.Events{Index: 102, Events: []structs.Event{tc.event}})
   843  
   844  			// If we are expecting to unsubscribe consume the subscription
   845  			// until the expected error occurs.
   846  			ctx, cancel = context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   847  			defer cancel()
   848  			if tc.shouldUnsubscribe {
   849  				for {
   850  					_, err = sub.Next(ctx)
   851  					if err != nil {
   852  						if err == context.DeadlineExceeded {
   853  							require.Fail(t, err.Error())
   854  						}
   855  						if err == ErrSubscriptionClosed {
   856  							break
   857  						}
   858  					}
   859  				}
   860  			} else {
   861  				_, err = sub.Next(ctx)
   862  				require.NoError(t, err)
   863  			}
   864  
   865  			publisher.Publish(&structs.Events{Index: 103, Events: []structs.Event{tc.event}})
   866  
   867  			ctx, cancel = context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   868  			defer cancel()
   869  			_, err = sub.Next(ctx)
   870  			if tc.shouldUnsubscribe {
   871  				require.Equal(t, ErrSubscriptionClosed, err)
   872  			} else {
   873  				require.NoError(t, err)
   874  			}
   875  		})
   876  	}
   877  }
   878  
   879  func TestEventBroker_handleACLUpdates_tokenExpiry(t *testing.T) {
   880  	ci.Parallel(t)
   881  
   882  	ctx, cancel := context.WithCancel(context.Background())
   883  	t.Cleanup(cancel)
   884  
   885  	cases := []struct {
   886  		name         string
   887  		inputToken   *structs.ACLToken
   888  		shouldExpire bool
   889  	}{
   890  		{
   891  			name: "token does not expire",
   892  			inputToken: &structs.ACLToken{
   893  				AccessorID:     uuid.Generate(),
   894  				SecretID:       uuid.Generate(),
   895  				ExpirationTime: pointer.Of(time.Now().Add(100000 * time.Hour).UTC()),
   896  				Type:           structs.ACLManagementToken,
   897  			},
   898  			shouldExpire: false,
   899  		},
   900  		{
   901  			name: "token does expire",
   902  			inputToken: &structs.ACLToken{
   903  				AccessorID:     uuid.Generate(),
   904  				SecretID:       uuid.Generate(),
   905  				ExpirationTime: pointer.Of(time.Now().Add(100000 * time.Hour).UTC()),
   906  				Type:           structs.ACLManagementToken,
   907  			},
   908  			shouldExpire: true,
   909  		},
   910  	}
   911  
   912  	for _, tc := range cases {
   913  		t.Run(tc.name, func(t *testing.T) {
   914  
   915  			// Build our fake token provider containing the relevant state
   916  			// objects and add this to our new delegate. Keeping the token
   917  			// provider setup separate means we can easily update its state.
   918  			tokenProvider := &fakeACLTokenProvider{token: tc.inputToken}
   919  			aclDelegate := &fakeACLDelegate{tokenProvider: tokenProvider}
   920  
   921  			publisher, err := NewEventBroker(ctx, aclDelegate, EventBrokerCfg{})
   922  			require.NoError(t, err)
   923  
   924  			fakeNodeEvent := structs.Event{
   925  				Topic:   structs.TopicNode,
   926  				Type:    structs.TypeNodeRegistration,
   927  				Payload: structs.NodeStreamEvent{Node: &structs.Node{}},
   928  			}
   929  
   930  			fakeTokenEvent := structs.Event{
   931  				Topic:   structs.TopicACLToken,
   932  				Type:    structs.TypeACLTokenUpserted,
   933  				Payload: structs.NewACLTokenEvent(&structs.ACLToken{SecretID: tc.inputToken.SecretID}),
   934  			}
   935  
   936  			sub, expiryTime, err := publisher.SubscribeWithACLCheck(&SubscribeRequest{
   937  				Topics: map[structs.Topic][]string{structs.TopicAll: {"*"}},
   938  				Token:  tc.inputToken.SecretID,
   939  			})
   940  			require.NoError(t, err)
   941  			require.NotNil(t, sub)
   942  			require.NotNil(t, expiryTime)
   943  
   944  			// Publish an event and check that there is a new item in the
   945  			// subscription queue.
   946  			publisher.Publish(&structs.Events{Index: 100, Events: []structs.Event{fakeNodeEvent}})
   947  
   948  			ctx, cancel := context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   949  			defer cancel()
   950  			_, err = sub.Next(ctx)
   951  			require.NoError(t, err)
   952  
   953  			// If the test states the token should expire, set the expiration
   954  			// time to a previous time.
   955  			if tc.shouldExpire {
   956  				tokenProvider.token.ExpirationTime = pointer.Of(
   957  					time.Date(1987, time.April, 13, 8, 3, 0, 0, time.UTC),
   958  				)
   959  			}
   960  
   961  			// Publish some events to trigger re-evaluation of the subscription.
   962  			publisher.Publish(&structs.Events{Index: 101, Events: []structs.Event{fakeTokenEvent}})
   963  			publisher.Publish(&structs.Events{Index: 102, Events: []structs.Event{fakeNodeEvent}})
   964  
   965  			// If we are expecting to unsubscribe consume the subscription
   966  			// until the expected error occurs.
   967  			ctx, cancel = context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
   968  			defer cancel()
   969  
   970  			if tc.shouldExpire {
   971  				for {
   972  					if _, err = sub.Next(ctx); err != nil {
   973  						if err == context.DeadlineExceeded {
   974  							require.Fail(t, err.Error())
   975  						}
   976  						if err == ErrSubscriptionClosed {
   977  							break
   978  						}
   979  					}
   980  				}
   981  			} else {
   982  				_, err = sub.Next(ctx)
   983  				require.NoError(t, err)
   984  			}
   985  		})
   986  	}
   987  }
   988  
   989  func consumeSubscription(ctx context.Context, sub *Subscription) <-chan subNextResult {
   990  	eventCh := make(chan subNextResult, 1)
   991  	go func() {
   992  		for {
   993  			es, err := sub.Next(ctx)
   994  			eventCh <- subNextResult{
   995  				Events: es.Events,
   996  				Err:    err,
   997  			}
   998  			if err != nil {
   999  				return
  1000  			}
  1001  		}
  1002  	}()
  1003  	return eventCh
  1004  }
  1005  
  1006  type subNextResult struct {
  1007  	Events []structs.Event
  1008  	Err    error
  1009  }
  1010  
  1011  func nextResult(t *testing.T, eventCh <-chan subNextResult) subNextResult {
  1012  	t.Helper()
  1013  	select {
  1014  	case next := <-eventCh:
  1015  		return next
  1016  	case <-time.After(100 * time.Millisecond):
  1017  		t.Fatalf("no event after 100ms")
  1018  	}
  1019  	return subNextResult{}
  1020  }
  1021  
  1022  func assertNoResult(t *testing.T, eventCh <-chan subNextResult) {
  1023  	t.Helper()
  1024  	select {
  1025  	case next := <-eventCh:
  1026  		require.NoError(t, next.Err)
  1027  		require.Len(t, next.Events, 1)
  1028  		t.Fatalf("received unexpected event: %#v", next.Events[0].Payload)
  1029  	case <-time.After(100 * time.Millisecond):
  1030  	}
  1031  }
  1032  
  1033  func consumeSub(ctx context.Context, sub *Subscription) error {
  1034  	for {
  1035  		_, err := sub.Next(ctx)
  1036  		if err != nil {
  1037  			return err
  1038  		}
  1039  	}
  1040  }