mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	events: WebSocket subscriptions support go-bexpr expressions (#22835)
Subscribing to events through a WebSocket now support boolean expressions to filter only the events wanted based on the fields * `event_type` * `operation` * `source_plugin_mount` * `data_path` * `namespace` Example expressions: These can be passed to `vault events subscribe`, e.g.,: * `event_type == abc` * `source_plugin_mount == secret/` * `event_type != def and operation != write` ```sh vault events subscribe -filter='source_plugin_mount == secret/' 'kv*' ``` The docs for the `vault events subscribe` command and API endpoint will be coming shortly in a different PR, and will include a better specification for these expressions, similar to (or linking to) https://developer.hashicorp.com/boundary/docs/concepts/filtering
This commit is contained in:
		 Christopher Swenson
					Christopher Swenson
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							3130e8ba94
						
					
				
				
					commit
					022469da45
				
			
							
								
								
									
										3
									
								
								changelog/22835.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/22835.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:improvement | ||||
| events: WebSocket subscriptions add support for boolean filter expressions | ||||
| ``` | ||||
| @@ -25,7 +25,8 @@ var ( | ||||
| type EventsSubscribeCommands struct { | ||||
| 	*BaseCommand | ||||
|  | ||||
| 	namespaces []string | ||||
| 	namespaces  []string | ||||
| 	bexprFilter string | ||||
| } | ||||
|  | ||||
| func (c *EventsSubscribeCommands) Synopsis() string { | ||||
| @@ -34,7 +35,7 @@ func (c *EventsSubscribeCommands) Synopsis() string { | ||||
|  | ||||
| func (c *EventsSubscribeCommands) Help() string { | ||||
| 	helpText := ` | ||||
| Usage: vault events subscribe [-namespaces=ns1] [-timeout=XYZs] eventType | ||||
| Usage: vault events subscribe [-namespaces=ns1] [-timeout=XYZs] [-filter=filterExpression] eventType | ||||
|  | ||||
|   Subscribe to events of the given event type (topic), which may be a glob | ||||
|   pattern (with "*"" treated as a wildcard). The events will be sent to | ||||
| @@ -49,6 +50,14 @@ Usage: vault events subscribe [-namespaces=ns1] [-timeout=XYZs] eventType | ||||
| func (c *EventsSubscribeCommands) Flags() *FlagSets { | ||||
| 	set := c.flagSet(FlagSetHTTP) | ||||
| 	f := set.NewFlagSet("Subscribe Options") | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name: "filter", | ||||
| 		Usage: `A boolean expression to use to filter events. Only events matching | ||||
|                 the filter will be subscribed to. This is applied after any filtering | ||||
|                 by event type or namespace.`, | ||||
| 		Default: "", | ||||
| 		Target:  &c.bexprFilter, | ||||
| 	}) | ||||
| 	f.StringSliceVar(&StringSliceVar{ | ||||
| 		Name: "namespaces", | ||||
| 		Usage: `Specifies one or more patterns of additional child namespaces | ||||
| @@ -133,6 +142,10 @@ func (c *EventsSubscribeCommands) subscribeRequest(client *api.Client, path stri | ||||
| 	if len(c.namespaces) > 0 { | ||||
| 		q["namespaces"] = cleanNamespaces(c.namespaces) | ||||
| 	} | ||||
| 	bexprFilter := strings.TrimSpace(c.bexprFilter) | ||||
| 	if bexprFilter != "" { | ||||
| 		q.Set("filter", bexprFilter) | ||||
| 	} | ||||
| 	u.RawQuery = q.Encode() | ||||
| 	client.AddHeader("X-Vault-Token", client.Token()) | ||||
| 	client.AddHeader("X-Vault-Namespace", client.Namespace()) | ||||
|   | ||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -79,6 +79,7 @@ require ( | ||||
| 	github.com/hashicorp/consul/api v1.23.0 | ||||
| 	github.com/hashicorp/errwrap v1.1.0 | ||||
| 	github.com/hashicorp/eventlogger v0.2.3 | ||||
| 	github.com/hashicorp/go-bexpr v0.1.12 | ||||
| 	github.com/hashicorp/go-cleanhttp v0.5.2 | ||||
| 	github.com/hashicorp/go-discover v0.0.0-20210818145131-c573d69da192 | ||||
| 	github.com/hashicorp/go-gcp-common v0.8.0 | ||||
|   | ||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1955,6 +1955,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY | ||||
| github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||
| github.com/hashicorp/eventlogger v0.2.3 h1:HhtM4tGEqd5H3bcI4SdppQBHA8Y5QF8Aje7HODp8/TA= | ||||
| github.com/hashicorp/eventlogger v0.2.3/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM= | ||||
| github.com/hashicorp/go-bexpr v0.1.12 h1:XrdVhmwu+9iYxIUWxsGVG7NQwrhzJZ0vR6nbN5bLgrA= | ||||
| github.com/hashicorp/go-bexpr v0.1.12/go.mod h1:ACktpcSySkFNpcxWSClFrut7wicd9WzisnvHuw+g9K8= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= | ||||
|   | ||||
| @@ -37,6 +37,7 @@ type eventSubscriber struct { | ||||
| 	events            *eventbus.EventBus | ||||
| 	namespacePatterns []string | ||||
| 	pattern           string | ||||
| 	bexprFilter       string | ||||
| 	conn              *websocket.Conn | ||||
| 	json              bool | ||||
| 	checkCache        *cache.Cache | ||||
| @@ -48,7 +49,7 @@ type eventSubscriber struct { | ||||
| func (sub *eventSubscriber) handleEventsSubscribeWebsocket() (websocket.StatusCode, string, error) { | ||||
| 	ctx := sub.ctx | ||||
| 	logger := sub.logger | ||||
| 	ch, cancel, err := sub.events.SubscribeMultipleNamespaces(ctx, sub.namespacePatterns, sub.pattern) | ||||
| 	ch, cancel, err := sub.events.SubscribeMultipleNamespaces(ctx, sub.namespacePatterns, sub.pattern, sub.bexprFilter) | ||||
| 	if err != nil { | ||||
| 		logger.Info("Error subscribing", "error", err) | ||||
| 		return websocket.StatusUnsupportedData, "Error subscribing", nil | ||||
| @@ -217,6 +218,7 @@ func handleEventsSubscribe(core *vault.Core, req *logical.Request) http.Handler | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		bexprFilter := strings.TrimSpace(r.URL.Query().Get("filter")) | ||||
| 		namespacePatterns := r.URL.Query()["namespaces"] | ||||
| 		namespacePatterns = prependNamespacePatterns(namespacePatterns, ns) | ||||
| 		conn, err := websocket.Accept(w, r, nil) | ||||
| @@ -251,6 +253,7 @@ func handleEventsSubscribe(core *vault.Core, req *logical.Request) http.Handler | ||||
| 			events:            core.Events(), | ||||
| 			namespacePatterns: namespacePatterns, | ||||
| 			pattern:           pattern, | ||||
| 			bexprFilter:       bexprFilter, | ||||
| 			conn:              conn, | ||||
| 			json:              json, | ||||
| 			checkCache:        cache.New(webSocketRevalidationTime, webSocketRevalidationTime), | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| @@ -84,8 +85,8 @@ func TestEventsSubscribe(t *testing.T) { | ||||
| 	}{{true}, {false}} | ||||
|  | ||||
| 	for _, testCase := range testCases { | ||||
| 		url := fmt.Sprintf("%s/v1/sys/events/subscribe/%s?namespaces=ns1&namespaces=ns*&json=%v", wsAddr, eventType, testCase.json) | ||||
| 		conn, _, err := websocket.Dial(ctx, url, &websocket.DialOptions{ | ||||
| 		location := fmt.Sprintf("%s/v1/sys/events/subscribe/%s?namespaces=ns1&namespaces=ns*&json=%v", wsAddr, eventType, testCase.json) | ||||
| 		conn, _, err := websocket.Dial(ctx, location, &websocket.DialOptions{ | ||||
| 			HTTPHeader: http.Header{"x-vault-token": []string{token}}, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| @@ -131,6 +132,90 @@ func TestEventsSubscribe(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestBexprFilters tests that go-bexpr filters are used to filter events. | ||||
| func TestBexprFilters(t *testing.T) { | ||||
| 	core := vault.TestCoreWithConfig(t, &vault.CoreConfig{ | ||||
| 		Experiments: []string{experiments.VaultExperimentEventsAlpha1}, | ||||
| 	}) | ||||
|  | ||||
| 	ln, addr := TestServer(t, core) | ||||
| 	defer ln.Close() | ||||
|  | ||||
| 	// unseal the core | ||||
| 	keys, token := vault.TestCoreInit(t, core) | ||||
| 	for _, key := range keys { | ||||
| 		_, err := core.Unseal(key) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	sendEvent := func(eventType string) error { | ||||
| 		pluginInfo := &logical.EventPluginInfo{ | ||||
| 			MountPath: "secret", | ||||
| 		} | ||||
| 		ns := namespace.RootNamespace | ||||
| 		id, err := uuid.GenerateUUID() | ||||
| 		if err != nil { | ||||
| 			core.Logger().Info("Error generating UUID, exiting sender", "error", err) | ||||
| 			return err | ||||
| 		} | ||||
| 		err = core.Events().SendEventInternal(namespace.RootContext(context.Background()), ns, pluginInfo, logical.EventType(eventType), &logical.EventData{ | ||||
| 			Id:        id, | ||||
| 			Metadata:  nil, | ||||
| 			EntityIds: nil, | ||||
| 			Note:      "testing", | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			core.Logger().Info("Error sending event, exiting sender", "error", err) | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	ctx := context.Background() | ||||
| 	wsAddr := strings.Replace(addr, "http", "ws", 1) | ||||
| 	bexprFilter := url.QueryEscape("event_type == abc") | ||||
|  | ||||
| 	location := fmt.Sprintf("%s/v1/sys/events/subscribe/*?json=true&filter=%s", wsAddr, bexprFilter) | ||||
| 	conn, _, err := websocket.Dial(ctx, location, &websocket.DialOptions{ | ||||
| 		HTTPHeader: http.Header{"x-vault-token": []string{token}}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer conn.Close(websocket.StatusNormalClosure, "") | ||||
| 	err = sendEvent("def") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	err = sendEvent("xyz") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	err = sendEvent("abc") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// we should get the abc message | ||||
| 	_, msg, err := conn.Read(context.Background()) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	event := map[string]interface{}{} | ||||
| 	err = json.Unmarshal(msg, &event) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	assert.Equal(t, "abc", event["data"].(map[string]interface{})["event_type"].(string)) | ||||
|  | ||||
| 	// and no other messages | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) | ||||
| 	defer cancel() | ||||
| 	_, _, err = conn.Read(ctx) | ||||
| 	assert.ErrorContains(t, err, "context deadline exceeded") | ||||
| } | ||||
|  | ||||
| func TestNamespacePrepend(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		requestNs string | ||||
|   | ||||
| @@ -67,3 +67,39 @@ func SendEvent(ctx context.Context, sender EventSender, eventType string, metada | ||||
| 	} | ||||
| 	return sender.SendEvent(ctx, EventType(eventType), ev) | ||||
| } | ||||
|  | ||||
| // EventReceivedBexpr is used for evaluating boolean expressions with go-bexpr. | ||||
| type EventReceivedBexpr struct { | ||||
| 	EventType         string `bexpr:"event_type"` | ||||
| 	Operation         string `bexpr:"operation"` | ||||
| 	SourcePluginMount string `bexpr:"source_plugin_mount"` | ||||
| 	DataPath          string `bexpr:"data_path"` | ||||
| 	Namespace         string `bexpr:"namespace"` | ||||
| } | ||||
|  | ||||
| // BexprDatum returns a copy of EventReceived formatted for use in evaluating go-bexpr boolean expressions. | ||||
| func (x *EventReceived) BexprDatum() any { | ||||
| 	operation := "" | ||||
| 	dataPath := "" | ||||
|  | ||||
| 	if x.Event != nil { | ||||
| 		if x.Event.Metadata != nil { | ||||
| 			operationValue := x.Event.Metadata.Fields[EventMetadataOperation] | ||||
| 			if operationValue != nil { | ||||
| 				operation = operationValue.GetStringValue() | ||||
| 			} | ||||
| 			dataPathValue := x.Event.Metadata.Fields[EventMetadataDataPath] | ||||
| 			if dataPathValue != nil { | ||||
| 				dataPath = dataPathValue.GetStringValue() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &EventReceivedBexpr{ | ||||
| 		EventType:         x.EventType, | ||||
| 		Operation:         operation, | ||||
| 		SourcePluginMount: x.PluginInfo.MountPath, | ||||
| 		DataPath:          dataPath, | ||||
| 		Namespace:         x.Namespace, | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import ( | ||||
| 	"github.com/armon/go-metrics" | ||||
| 	"github.com/hashicorp/eventlogger" | ||||
| 	"github.com/hashicorp/eventlogger/formatter_filters/cloudevents" | ||||
| 	"github.com/hashicorp/go-bexpr" | ||||
| 	"github.com/hashicorp/go-hclog" | ||||
| 	"github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/helper/namespace" | ||||
| @@ -201,11 +202,15 @@ func NewEventBus(logger hclog.Logger) (*EventBus, error) { | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (bus *EventBus) Subscribe(ctx context.Context, ns *namespace.Namespace, pattern string) (<-chan *eventlogger.Event, context.CancelFunc, error) { | ||||
| 	return bus.SubscribeMultipleNamespaces(ctx, []string{strings.Trim(ns.Path, "/")}, pattern) | ||||
| // Subscribe subscribes to events in the given namespace matching the event type pattern and after | ||||
| // applying the optional go-bexpr filter. | ||||
| func (bus *EventBus) Subscribe(ctx context.Context, ns *namespace.Namespace, pattern string, bexprFilter string) (<-chan *eventlogger.Event, context.CancelFunc, error) { | ||||
| 	return bus.SubscribeMultipleNamespaces(ctx, []string{strings.Trim(ns.Path, "/")}, pattern, bexprFilter) | ||||
| } | ||||
|  | ||||
| func (bus *EventBus) SubscribeMultipleNamespaces(ctx context.Context, namespacePathPatterns []string, pattern string) (<-chan *eventlogger.Event, context.CancelFunc, error) { | ||||
| // SubscribeMultipleNamespaces subscribes to events in the given namespace matching the event type | ||||
| // pattern and after applying the optional go-bexpr filter. | ||||
| func (bus *EventBus) SubscribeMultipleNamespaces(ctx context.Context, namespacePathPatterns []string, pattern string, bexprFilter string) (<-chan *eventlogger.Event, context.CancelFunc, error) { | ||||
| 	// subscriptions are still stored even if the bus has not been started | ||||
| 	pipelineID, err := uuid.GenerateUUID() | ||||
| 	if err != nil { | ||||
| @@ -217,7 +222,10 @@ func (bus *EventBus) SubscribeMultipleNamespaces(ctx context.Context, namespaceP | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	filterNode := newFilterNode(namespacePathPatterns, pattern) | ||||
| 	filterNode, err := newFilterNode(namespacePathPatterns, pattern, bexprFilter) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	err = bus.broker.RegisterNode(eventlogger.NodeID(filterNodeID), filterNode) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| @@ -263,7 +271,15 @@ func (bus *EventBus) SetSendTimeout(timeout time.Duration) { | ||||
| 	bus.timeout = timeout | ||||
| } | ||||
|  | ||||
| func newFilterNode(namespacePatterns []string, pattern string) *eventlogger.Filter { | ||||
| func newFilterNode(namespacePatterns []string, pattern string, bexprFilter string) (*eventlogger.Filter, error) { | ||||
| 	var evaluator *bexpr.Evaluator | ||||
| 	if bexprFilter != "" { | ||||
| 		var err error | ||||
| 		evaluator, err = bexpr.CreateEvaluator(bexprFilter) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	return &eventlogger.Filter{ | ||||
| 		Predicate: func(e *eventlogger.Event) (bool, error) { | ||||
| 			eventRecv := e.Payload.(*logical.EventReceived) | ||||
| @@ -287,9 +303,13 @@ func newFilterNode(namespacePatterns []string, pattern string) *eventlogger.Filt | ||||
| 				return false, nil | ||||
| 			} | ||||
|  | ||||
| 			// apply go-bexpr filter | ||||
| 			if evaluator != nil { | ||||
| 				return evaluator.Evaluate(eventRecv.BexprDatum()) | ||||
| 			} | ||||
| 			return true, nil | ||||
| 		}, | ||||
| 	} | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func newAsyncNode(ctx context.Context, logger hclog.Logger) *asyncChanNode { | ||||
|   | ||||
| @@ -6,6 +6,7 @@ package eventbus | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| @@ -13,6 +14,7 @@ import ( | ||||
|  | ||||
| 	"github.com/hashicorp/eventlogger" | ||||
| 	"github.com/hashicorp/go-secure-stdlib/strutil" | ||||
| 	"github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/helper/namespace" | ||||
| 	"github.com/hashicorp/vault/sdk/logical" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| @@ -35,7 +37,7 @@ func TestBusBasics(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	err = bus.SendEventInternal(ctx, namespace.RootNamespace, nil, eventType, event) | ||||
| 	if err != ErrNotStarted { | ||||
| 	if !errors.Is(err, ErrNotStarted) { | ||||
| 		t.Errorf("Expected not started error but got: %v", err) | ||||
| 	} | ||||
|  | ||||
| @@ -46,7 +48,7 @@ func TestBusBasics(t *testing.T) { | ||||
| 		t.Errorf("Expected no error sending: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType)) | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType), "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @@ -90,7 +92,7 @@ func TestSubscribeNonRootNamespace(t *testing.T) { | ||||
| 		Path: "abc/", | ||||
| 	} | ||||
|  | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, ns, string(eventType)) | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, ns, string(eventType), "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @@ -133,7 +135,7 @@ func TestNamespaceFiltering(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType)) | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType), "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @@ -189,13 +191,13 @@ func TestBus2Subscriptions(t *testing.T) { | ||||
| 	eventType2 := logical.EventType("someType2") | ||||
| 	bus.Start() | ||||
|  | ||||
| 	ch1, cancel1, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType1)) | ||||
| 	ch1, cancel1, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType1), "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer cancel1() | ||||
|  | ||||
| 	ch2, cancel2, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType2)) | ||||
| 	ch2, cancel2, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType2), "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @@ -274,7 +276,7 @@ func TestBusSubscriptionsCancel(t *testing.T) { | ||||
| 			received := atomic.Int32{} | ||||
|  | ||||
| 			for i := 0; i < create; i++ { | ||||
| 				ch, cancelFunc, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType)) | ||||
| 				ch, cancelFunc, err := bus.Subscribe(ctx, namespace.RootNamespace, string(eventType), "") | ||||
| 				if err != nil { | ||||
| 					t.Fatal(err) | ||||
| 				} | ||||
| @@ -363,13 +365,13 @@ func TestBusWildcardSubscriptions(t *testing.T) { | ||||
| 	barEventType := logical.EventType("kv/bar") | ||||
| 	bus.Start() | ||||
|  | ||||
| 	ch1, cancel1, err := bus.Subscribe(ctx, namespace.RootNamespace, "kv/*") | ||||
| 	ch1, cancel1, err := bus.Subscribe(ctx, namespace.RootNamespace, "kv/*", "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer cancel1() | ||||
|  | ||||
| 	ch2, cancel2, err := bus.Subscribe(ctx, namespace.RootNamespace, "*/bar") | ||||
| 	ch2, cancel2, err := bus.Subscribe(ctx, namespace.RootNamespace, "*/bar", "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @@ -437,7 +439,7 @@ func TestDataPathIsPrependedWithMount(t *testing.T) { | ||||
| 	fooEventType := logical.EventType("kv/foo") | ||||
| 	bus.Start() | ||||
|  | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, "kv/*") | ||||
| 	ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, "kv/*", "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @@ -545,3 +547,82 @@ func TestDataPathIsPrependedWithMount(t *testing.T) { | ||||
| 		t.Error("Timeout waiting for event") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestBexpr tests go-bexpr filters are evaluated on an event. | ||||
| func TestBexpr(t *testing.T) { | ||||
| 	bus, err := NewEventBus(nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	bus.Start() | ||||
|  | ||||
| 	sendEvent := func(eventType string) error { | ||||
| 		event, err := logical.NewEvent() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		metadata := map[string]string{ | ||||
| 			logical.EventMetadataDataPath:  "my/secret/path", | ||||
| 			logical.EventMetadataOperation: "write", | ||||
| 		} | ||||
| 		metadataBytes, err := json.Marshal(metadata) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		event.Metadata = &structpb.Struct{} | ||||
| 		if err := event.Metadata.UnmarshalJSON(metadataBytes); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// send with a secrets plugin mounted | ||||
| 		pluginInfo := logical.EventPluginInfo{ | ||||
| 			MountClass:    "secrets", | ||||
| 			MountAccessor: "kv_abc", | ||||
| 			MountPath:     "secret/", | ||||
| 			Plugin:        "kv", | ||||
| 			PluginVersion: "v1.13.1+builtin", | ||||
| 			Version:       "2", | ||||
| 		} | ||||
| 		return bus.SendEventInternal(ctx, namespace.RootNamespace, &pluginInfo, logical.EventType(eventType), event) | ||||
| 	} | ||||
|  | ||||
| 	testCases := []struct { | ||||
| 		name             string | ||||
| 		filter           string | ||||
| 		shouldPassFilter bool | ||||
| 	}{ | ||||
| 		{"empty expression", "", true}, | ||||
| 		{"non-matching expression", "data_path == nothing", false}, | ||||
| 		{"matching expression", "data_path == secret/my/secret/path", true}, | ||||
| 		{"full matching expression", "data_path == secret/my/secret/path and operation != read and source_plugin_mount == secret/ and source_plugin_mount != somethingelse", true}, | ||||
| 	} | ||||
|  | ||||
| 	for _, testCase := range testCases { | ||||
| 		t.Run(testCase.name, func(t *testing.T) { | ||||
| 			eventType, err := uuid.GenerateUUID() | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 			ch, cancel, err := bus.Subscribe(ctx, namespace.RootNamespace, eventType, testCase.filter) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 			defer cancel() | ||||
| 			err = sendEvent(eventType) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			timer := time.NewTimer(5 * time.Second) | ||||
| 			defer timer.Stop() | ||||
| 			got := false | ||||
| 			select { | ||||
| 			case <-ch: | ||||
| 				got = true | ||||
| 			case <-timer.C: | ||||
| 			} | ||||
| 			assert.Equal(t, testCase.shouldPassFilter, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ func TestCanSendEventsFromBuiltinPlugin(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	ch, cancel, err := c.events.Subscribe(ctx, namespace.RootNamespace, eventType) | ||||
| 	ch, cancel, err := c.events.Subscribe(ctx, namespace.RootNamespace, eventType, "") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user