|
| 1 | +# ACL Configuration Guide |
| 2 | + |
| 3 | +This guide explains how to use the ACL (Access Control List) feature in asynq to enable multi-tenant task queue isolation. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +When ACL is enabled (by setting a tenant name), asynq automatically prefixes queue names with a tenant identifier (username). This ensures that different tenants can only access their own tasks in a shared Redis instance. |
| 8 | + |
| 9 | +**Important**: The `default` queue is intentionally excluded from prefixing and serves as a shared public queue accessible by all tenants. All other queue names will be prefixed. |
| 10 | + |
| 11 | +For example, with tenant `tenant1`: |
| 12 | +- Queue `default` → Redis key: `asynq:{default}:pending` (shared) |
| 13 | +- Queue `critical` → Redis key: `asynq:{tenant1:critical}:pending` (tenant-specific) |
| 14 | + |
| 15 | +## Configuration |
| 16 | + |
| 17 | +### Server Configuration |
| 18 | + |
| 19 | +To enable ACL in the server (consumer): |
| 20 | + |
| 21 | +```rust |
| 22 | +use asynq::config::ServerConfig; |
| 23 | +use asynq::server::ServerBuilder; |
| 24 | + |
| 25 | +let server_config = ServerConfig::new() |
| 26 | + .acl_tenant("tenant1") // Set the tenant name (ACL is automatically enabled) |
| 27 | + .concurrency(4) |
| 28 | + // ... other configurations |
| 29 | + ; |
| 30 | + |
| 31 | +let server = ServerBuilder::new() |
| 32 | + .redis_config(redis_config) |
| 33 | + .server_config(server_config) |
| 34 | + .build() |
| 35 | + .await?; |
| 36 | +``` |
| 37 | + |
| 38 | +### Client Configuration |
| 39 | + |
| 40 | +To enable ACL in the client (producer): |
| 41 | + |
| 42 | +```rust |
| 43 | +use asynq::config::ClientConfig; |
| 44 | +use asynq::client::Client; |
| 45 | + |
| 46 | +let client_config = ClientConfig::new() |
| 47 | + .acl_tenant("tenant1"); // Set the tenant name (ACL is automatically enabled) |
| 48 | + |
| 49 | +let client = Client::with_config(redis_config, client_config).await?; |
| 50 | +``` |
| 51 | + |
| 52 | +## Automatic Configuration from Redis URL |
| 53 | + |
| 54 | +Both the consumer and producer examples include automatic ACL configuration when using Redis URLs with authentication: |
| 55 | + |
| 56 | +```bash |
| 57 | +# Redis URL format: redis://username:password@host:port |
| 58 | +export REDIS_URL="redis://tenant1:secure_pass123@localhost:6379" |
| 59 | + |
| 60 | +# When the acl feature is enabled, the username is automatically extracted |
| 61 | +cargo run --example=consumer --features=acl |
| 62 | +cargo run --example=producer --features=acl |
| 63 | +``` |
| 64 | + |
| 65 | +## Example Usage |
| 66 | + |
| 67 | +### 1. Create Redis ACL User |
| 68 | + |
| 69 | +First, create a Redis user with appropriate permissions: |
| 70 | + |
| 71 | +```bash |
| 72 | +redis-cli |
| 73 | +> ACL SETUSER tenant1 on >secure_pass123 ~asynq:{tenant1}:* +@all |
| 74 | +``` |
| 75 | + |
| 76 | +### 2. Run Consumer with ACL |
| 77 | + |
| 78 | +```bash |
| 79 | +export REDIS_URL="redis://tenant1:secure_pass123@localhost:6379" |
| 80 | +cargo run --manifest-path=asynq/Cargo.toml --example=consumer --features=acl |
| 81 | +``` |
| 82 | + |
| 83 | +Output: |
| 84 | +``` |
| 85 | +🚀 Starting Asynq worker server... |
| 86 | +🔗 Using Redis URL: redis://tenant1:secure_pass123@localhost:6379 |
| 87 | +🔐 ACL enabled with tenant: tenant1 |
| 88 | +🔄 Server is running and waiting for tasks... |
| 89 | +``` |
| 90 | + |
| 91 | +### 3. Run Producer with ACL |
| 92 | + |
| 93 | +```bash |
| 94 | +export REDIS_URL="redis://tenant1:secure_pass123@localhost:6379" |
| 95 | +cargo run --manifest-path=asynq/Cargo.toml --example=producer --features=acl |
| 96 | +``` |
| 97 | + |
| 98 | +Output: |
| 99 | +``` |
| 100 | +🔗 Using Redis URL: redis://tenant1:secure_pass123@localhost:6379 |
| 101 | +🔐 ACL enabled with tenant: tenant1 |
| 102 | +Email task enqueued: ID = ... |
| 103 | +``` |
| 104 | + |
| 105 | +## Queue Name Transformation |
| 106 | + |
| 107 | +When ACL is enabled, queue names are automatically prefixed, **except for the default queue**: |
| 108 | + |
| 109 | +| Original Queue | With ACL (tenant1) | Notes | |
| 110 | +|---------------|--------------------| ------| |
| 111 | +| `default` | `default` (unchanged) | Shared public queue for all tenants | |
| 112 | +| `critical` | `tenant1:critical` | Tenant-specific queue | |
| 113 | +| `low` | `tenant1:low` | Tenant-specific queue | |
| 114 | + |
| 115 | +The Redis keys become: |
| 116 | +- `asynq:{default}:pending` - Shared across all tenants |
| 117 | +- `asynq:{tenant1:critical}:pending` - Tenant-specific |
| 118 | +- etc. |
| 119 | + |
| 120 | +**Important**: The `default` queue is intentionally left unprefixed as it serves as a shared public queue accessible by all tenants. If you need tenant-specific default behavior, create a custom queue (e.g., `myqueue`) instead. |
| 121 | + |
| 122 | +## Multi-Tenant Isolation |
| 123 | + |
| 124 | +Different tenants can run simultaneously without interfering with each other: |
| 125 | + |
| 126 | +**Tenant 1:** |
| 127 | +```bash |
| 128 | +export REDIS_URL="redis://tenant1:pass1@localhost:6379" |
| 129 | +cargo run --example=consumer --features=acl # Processes tenant1:* queues |
| 130 | +``` |
| 131 | + |
| 132 | +**Tenant 2:** |
| 133 | +```bash |
| 134 | +export REDIS_URL="redis://tenant2:pass2@localhost:6379" |
| 135 | +cargo run --example=consumer --features=acl # Processes tenant2:* queues |
| 136 | +``` |
| 137 | + |
| 138 | +Each tenant can only access their own tasks due to Redis ACL rules. |
| 139 | + |
| 140 | +## Backward Compatibility |
| 141 | + |
| 142 | +When ACL is not enabled (no tenant name set), the system works exactly as before: |
| 143 | + |
| 144 | +```rust |
| 145 | +// ACL disabled (default - no tenant name) |
| 146 | +let config = ServerConfig::new(); |
| 147 | +// Queue names remain unchanged: "default", "critical", etc. |
| 148 | +``` |
| 149 | + |
| 150 | +## API Design |
| 151 | + |
| 152 | +Setting the tenant name automatically enables ACL functionality. There is no separate flag to enable/disable ACL: |
| 153 | + |
| 154 | +```rust |
| 155 | +// ACL enabled - tenant name is set |
| 156 | +let config_with_acl = ServerConfig::new() |
| 157 | + .acl_tenant("tenant1"); // ACL is now active |
| 158 | + |
| 159 | +// ACL disabled - no tenant name |
| 160 | +let config_without_acl = ServerConfig::new(); |
| 161 | +``` |
| 162 | + |
| 163 | +## Best Practices |
| 164 | + |
| 165 | +1. **Use meaningful tenant names**: Use usernames or organization identifiers as tenant names |
| 166 | +2. **Set up Redis ACL properly**: Ensure each tenant user has appropriate Redis ACL permissions |
| 167 | +3. **Consistent tenant names**: Both producer and consumer should use the same tenant name |
| 168 | +4. **Use environment variables**: Store Redis URLs with credentials in environment variables, not in code |
| 169 | + |
| 170 | +## Troubleshooting |
| 171 | + |
| 172 | +### Error: "No permissions to access a key" |
| 173 | + |
| 174 | +This error occurs when: |
| 175 | +1. Tenant name is not set but Redis requires authentication |
| 176 | +2. The Redis ACL user doesn't have permission to access the required keys |
| 177 | + |
| 178 | +**Solution**: |
| 179 | +- Set tenant name in both client and server configurations |
| 180 | +- Ensure Redis ACL user has permissions for `~asynq:{tenant}:*` keys |
| 181 | + |
| 182 | +### Tasks not being processed |
| 183 | + |
| 184 | +**Check**: |
| 185 | +1. Both producer and consumer are using the same tenant name |
| 186 | +2. Tenant name is set in both configurations |
| 187 | +3. Queue names match between producer and consumer |
| 188 | + |
| 189 | +## Reference |
| 190 | + |
| 191 | +For more information about the ACL module, see: |
| 192 | +- `asynq/examples/acl_example.rs` - Complete ACL module demonstration |
| 193 | +- `asynq/src/acl/mod.rs` - ACL module implementation |
| 194 | +- Redis ACL documentation: https://redis.io/docs/management/security/acl/ |
0 commit comments