Add wildcard namespace subscriptions
All checks were successful
CI / build (pull_request) Successful in 18s
CI / build (push) Successful in 16s

Support NATS-style wildcard patterns ("*" and ">") for subscribing
to events across multiple namespaces. This enables cross-cutting
concerns like logging, monitoring, and auditing without requiring
separate subscriptions for each namespace.

- Add pattern.go with MatchNamespacePattern and IsWildcardPattern
- Update EventBus to track wildcard subscribers separately
- Update NATSEventBus to use NATS native wildcard support
- Add comprehensive tests for pattern matching and EventBus wildcards
- Document security implications in all relevant code comments

Closes #20

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit was merged in pull request #52.
This commit is contained in:
2026-01-10 19:24:26 +01:00
parent f0f8978079
commit adead7e980
5 changed files with 807 additions and 62 deletions

83
pattern.go Normal file
View File

@@ -0,0 +1,83 @@
package aether
import "strings"
// MatchNamespacePattern checks if a namespace matches a pattern.
// Patterns follow NATS subject matching conventions where tokens are separated by dots:
// - "*" matches exactly one token (any sequence without ".")
// - ">" matches one or more tokens (only valid at the end of a pattern)
// - Exact strings match exactly
//
// Examples:
// - "tenant-a" matches "tenant-a" (exact match)
// - "*" matches any single-token namespace like "tenant-a" or "production"
// - ">" matches any namespace with one or more tokens
// - "prod.*" matches "prod.tenant", "prod.orders" (but not "prod.tenant.orders")
// - "prod.>" matches "prod.tenant", "prod.tenant.orders", "prod.a.b.c"
// - "*.tenant.*" matches "prod.tenant.orders", "staging.tenant.events"
//
// Security Considerations:
// Wildcard subscriptions provide cross-namespace visibility. Use with caution:
// - "*" or ">" patterns receive events from ALL matching namespaces
// - This bypasses namespace isolation for the subscriber
// - Only grant wildcard subscription access to trusted system components
// - Consider auditing wildcard subscription usage
// - For multi-tenant systems, wildcard access should be restricted to admin/ops
// - Use the most specific pattern possible to minimize exposure
func MatchNamespacePattern(pattern, namespace string) bool {
// Empty pattern matches nothing
if pattern == "" {
return false
}
// ">" matches everything when used alone
if pattern == ">" {
return namespace != ""
}
patternTokens := strings.Split(pattern, ".")
namespaceTokens := strings.Split(namespace, ".")
return matchTokens(patternTokens, namespaceTokens)
}
// matchTokens recursively matches pattern tokens against namespace tokens
func matchTokens(patternTokens, namespaceTokens []string) bool {
// If pattern is exhausted, namespace must also be exhausted
if len(patternTokens) == 0 {
return len(namespaceTokens) == 0
}
patternToken := patternTokens[0]
// ">" matches one or more remaining tokens (must be last pattern token)
if patternToken == ">" {
// ">" requires at least one token to match
return len(namespaceTokens) >= 1
}
// If namespace is exhausted but pattern has more tokens, no match
if len(namespaceTokens) == 0 {
return false
}
namespaceToken := namespaceTokens[0]
// "*" matches exactly one token
if patternToken == "*" {
return matchTokens(patternTokens[1:], namespaceTokens[1:])
}
// Exact match required
if patternToken == namespaceToken {
return matchTokens(patternTokens[1:], namespaceTokens[1:])
}
return false
}
// IsWildcardPattern returns true if the pattern contains wildcards (* or >).
// Wildcard patterns can match multiple namespaces and bypass namespace isolation.
func IsWildcardPattern(pattern string) bool {
return strings.Contains(pattern, "*") || strings.Contains(pattern, ">")
}