# Access Control Stream-level permissions and rate limiting for secure event streaming. ## Overview Access control configuration provides fine-grained security per stream: - **Read/Write Permissions** - Control who can read/write events - **Consumer Group Limits** - Prevent resource exhaustion - **Rate Limiting** - Throttle write operations - **Public/Private Streams** - Configure visibility ## Quick Start ```csharp using Svrnty.CQRS.Events.Abstractions; var configStore = serviceProvider.GetRequiredService(); await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "orders", AccessControl = new AccessControlConfiguration { PublicRead = false, PublicWrite = false, AllowedReaders = new List { "admin", "order-service" }, AllowedWriters = new List { "order-service" } } }); ``` ## Access Control Properties ```csharp public class AccessControlConfiguration { public bool PublicRead { get; set; } // Allow anonymous reads public bool PublicWrite { get; set; } // Allow anonymous writes public List AllowedReaders { get; set; } // Authorized readers public List AllowedWriters { get; set; } // Authorized writers public List DeniedReaders { get; set; } // Explicit deny public List DeniedWriters { get; set; } // Explicit deny public int MaxConsumerGroups { get; set; } // Consumer group limit public int MaxEventsPerSecond { get; set; } // Write rate limit public int MaxEventsPerMinute { get; set; } // Read rate limit public bool RequireAuthentication { get; set; } // Require auth } ``` ## Public vs Private Streams ### Public Stream ```csharp // Public read-only stream var accessControl = new AccessControlConfiguration { PublicRead = true, PublicWrite = false, AllowedWriters = new List { "admin" } }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "public-announcements", AccessControl = accessControl, Tags = new List { "public" } }); ``` ### Private Stream ```csharp // Private stream - restricted access var accessControl = new AccessControlConfiguration { PublicRead = false, PublicWrite = false, AllowedReaders = new List { "admin", "finance-service" }, AllowedWriters = new List { "finance-service" }, RequireAuthentication = true }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "financial-transactions", AccessControl = accessControl, Tags = new List { "private", "sensitive" } }); ``` ## Reader Permissions ```csharp // Multiple authorized readers var accessControl = new AccessControlConfiguration { PublicRead = false, AllowedReaders = new List { "admin", "order-service", "analytics-service", "reporting-service" } }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "orders", AccessControl = accessControl }); ``` ## Writer Permissions ```csharp // Single writer (recommended) var accessControl = new AccessControlConfiguration { PublicWrite = false, AllowedWriters = new List { "order-service" } }; // Multiple writers (use with caution) var accessControl = new AccessControlConfiguration { PublicWrite = false, AllowedWriters = new List { "order-service", "admin-service" } }; ``` ## Explicit Deny ```csharp // Allow all except denied var accessControl = new AccessControlConfiguration { PublicRead = true, DeniedReaders = new List { "untrusted-service" }, AllowedWriters = new List { "admin" }, DeniedWriters = new List { "legacy-service" } }; // Deny takes precedence over allow ``` ## Consumer Group Limits ```csharp // Limit consumer groups per stream var accessControl = new AccessControlConfiguration { MaxConsumerGroups = 10 // Max 10 consumer groups }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "orders", AccessControl = accessControl }); // Attempts to create 11th consumer group will fail ``` ## Rate Limiting ### Write Rate Limiting ```csharp // Limit write throughput var accessControl = new AccessControlConfiguration { MaxEventsPerSecond = 1000 // Max 1000 events/sec }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "orders", AccessControl = accessControl }); // Writes exceeding limit will be throttled or rejected ``` ### Read Rate Limiting ```csharp // Limit read throughput per consumer var accessControl = new AccessControlConfiguration { MaxEventsPerMinute = 60000 // Max 60k events/min (1000/sec) }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "analytics", AccessControl = accessControl }); ``` ### Combined Rate Limiting ```csharp // Limit both reads and writes var accessControl = new AccessControlConfiguration { MaxEventsPerSecond = 500, // Write limit MaxEventsPerMinute = 100000 // Read limit }; ``` ## Domain-Specific Examples ### Financial Transactions ```csharp // Strict access control var financialAccessControl = new AccessControlConfiguration { PublicRead = false, PublicWrite = false, AllowedReaders = new List { "admin", "finance-service", "audit-service" }, AllowedWriters = new List { "finance-service" }, RequireAuthentication = true, MaxConsumerGroups = 5, // Limited consumers MaxEventsPerSecond = 100 // Moderate throughput }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "financial-transactions", AccessControl = financialAccessControl, Tags = new List { "financial", "sensitive", "compliance" } }); ``` ### Public Announcements ```csharp // Public read, admin write var announcementAccessControl = new AccessControlConfiguration { PublicRead = true, // Anyone can read PublicWrite = false, AllowedWriters = new List { "admin", "announcement-service" }, MaxConsumerGroups = 100, // Many consumers allowed MaxEventsPerSecond = 10 // Low write volume }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "public-announcements", AccessControl = announcementAccessControl, Tags = new List { "public" } }); ``` ### User Activity Logs ```csharp // Per-user isolation var activityAccessControl = new AccessControlConfiguration { PublicRead = false, PublicWrite = false, // Users can only read their own activity AllowedReaders = new List { "user:{userId}", "admin" }, AllowedWriters = new List { "activity-tracking-service" }, RequireAuthentication = true, MaxEventsPerSecond = 10000 // High volume }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "user-activity", AccessControl = activityAccessControl }); ``` ### Multi-Tenant Events ```csharp // Tenant isolation var tenantAccessControl = new AccessControlConfiguration { PublicRead = false, PublicWrite = false, AllowedReaders = new List { $"tenant:{tenantId}", "admin" }, AllowedWriters = new List { $"tenant:{tenantId}" }, RequireAuthentication = true, MaxConsumerGroups = 20, MaxEventsPerSecond = 1000 }; await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = $"tenant-{tenantId}-events", AccessControl = tenantAccessControl, Tags = new List { "multi-tenant", $"tenant-{tenantId}" } }); ``` ## Authorization Integration ### ASP.NET Core Integration ```csharp // Middleware to enforce access control app.Use(async (context, next) => { var streamName = context.Request.RouteValues["streamName"]?.ToString(); var user = context.User.Identity?.Name; var config = await configStore.GetConfigurationAsync(streamName); var accessControl = config?.AccessControl; if (accessControl != null && !accessControl.PublicRead) { if (!accessControl.AllowedReaders.Contains(user)) { context.Response.StatusCode = 403; await context.Response.WriteAsync("Access denied"); return; } } await next(); }); ``` ### Custom Authorization Service ```csharp public interface IStreamAuthorizationService { Task CanReadAsync(string streamName, string userId); Task CanWriteAsync(string streamName, string userId); } public class StreamAuthorizationService : IStreamAuthorizationService { private readonly IStreamConfigurationStore _configStore; public async Task CanReadAsync(string streamName, string userId) { var config = await _configStore.GetConfigurationAsync(streamName); var accessControl = config?.AccessControl; if (accessControl == null) return true; // No restrictions if (accessControl.PublicRead) return true; if (accessControl.DeniedReaders.Contains(userId)) return false; return accessControl.AllowedReaders.Contains(userId); } public async Task CanWriteAsync(string streamName, string userId) { var config = await _configStore.GetConfigurationAsync(streamName); var accessControl = config?.AccessControl; if (accessControl == null) return true; if (accessControl.PublicWrite) return true; if (accessControl.DeniedWriters.Contains(userId)) return false; return accessControl.AllowedWriters.Contains(userId); } } // Register service builder.Services.AddSingleton(); ``` ## Rate Limiting Implementation ```csharp public class StreamRateLimiter { private readonly Dictionary _buckets = new(); public async Task AllowWriteAsync(string streamName, int eventCount) { var config = await _configStore.GetConfigurationAsync(streamName); var limit = config?.AccessControl?.MaxEventsPerSecond ?? int.MaxValue; var bucket = GetOrCreateBucket(streamName, limit); return bucket.TryConsume(eventCount); } private TokenBucket GetOrCreateBucket(string streamName, int capacity) { if (!_buckets.TryGetValue(streamName, out var bucket)) { bucket = new TokenBucket(capacity, TimeSpan.FromSeconds(1)); _buckets[streamName] = bucket; } return bucket; } } ``` ## Monitoring Access Control ```csharp // Track authorization failures var metrics = new { StreamName = "orders", TotalRequests = 1000, AllowedRequests = 950, DeniedRequests = 50, DenialRate = 5.0 // 5% }; if (metrics.DenialRate > 1.0) { _logger.LogWarning( "High denial rate for {Stream}: {Rate:F1}%", metrics.StreamName, metrics.DenialRate); } // Log authorization failures _logger.LogWarning( "Access denied for user {User} to stream {Stream}", userId, streamName); ``` ## Best Practices ### ✅ DO - Use principle of least privilege - Require authentication for sensitive streams - Limit consumer groups to prevent resource exhaustion - Use rate limiting to prevent abuse - Use explicit deny for untrusted services - Monitor authorization failures - Audit access regularly - Use role-based access (admin, service, user) - Isolate multi-tenant streams - Document access requirements ### ❌ DON'T - Don't make sensitive streams public - Don't allow unlimited consumer groups - Don't skip rate limiting on public streams - Don't forget to log denied access - Don't use same permissions for all streams - Don't hard-code user/service names - Don't ignore authorization failures - Don't forget authentication requirements ## See Also - [Stream Configuration Overview](README.md) - [Retention Configuration](retention-config.md) - [Performance Configuration](performance-config.md) - [Best Practices - Security](../../best-practices/security.md) - [Multi-Tenancy](../../best-practices/multi-tenancy.md)