The legacy regex-based tenant filtering strategy creates a cartesian product security vulnerability when users have access to multiple tenants with mixed project permissions.
User has OAuth scopes granting access to:
- Account
1000with all projects (.*) - Account
2000with project20only
# Generated filter
{vm_account_id=~"(1000|2000)",vm_project_id=~"(.*|20)"}
# This creates 4 combinations:
# ✅ account=1000, project=.* (authorized)
# ✅ account=1000, project=20 (authorized)
# ❌ account=2000, project=.* (SECURITY BREACH!)
# ✅ account=2000, project=20 (authorized)
Result: User gains unauthorized access to ALL projects in account 2000!
# Generated filter
{vm_account_id="1000"} or {vm_account_id="2000",vm_project_id="20"}
# This creates exactly 2 conditions:
# ✅ account=1000 (any project) (authorized)
# ✅ account=2000, project=20 (authorized)
Result: Perfect tenant isolation with no cross-tenant leakage.
upstream:
tenant_filter:
strategy: "or_conditions" # Secure strategy - this is now the only supported strategy| Strategy | Security | Performance | Use Case |
|---|---|---|---|
or_conditions |
✅ Secure | 📊 Moderate | Multi-tenant production environments |
- Audit Current Tenants: Review users with multiple tenant access
- Validate Queries: Ensure PromQL queries work correctly with OR-based filtering
- Monitor Performance: Watch for query complexity increases
- Optimize Tenant Mappings: Reduce complex tenant combinations where possible
# Original
up
# Regex Strategy (unsafe)
up{vm_account_id=~"(1000|2000)",vm_project_id=~"(.*|20)"}
# OR Strategy (secure)
up{vm_account_id="1000"} or up{vm_account_id="2000",vm_project_id="20"}
# Original
rate(http_requests_total[5m]) / rate(http_requests_duration_seconds_count[5m])
# OR Strategy (secure)
rate(http_requests_total{vm_account_id="1000"}[5m]) / rate(http_requests_duration_seconds_count{vm_account_id="1000"}[5m])
or
rate(http_requests_total{vm_account_id="2000",vm_project_id="20"}[5m]) / rate(http_requests_duration_seconds_count{vm_account_id="2000",vm_project_id="20"}[5m])
- Single Tenant: No performance impact
- Multiple Tenants: Linear increase with tenant count
- Complex Queries: Each metric duplicated per tenant
- Wildcard Grouping:
project_id: ".*"optimized to no project filter - Tenant Deduplication: Duplicate tenants automatically merged
- Smart Fallback: Single tenant uses simple filtering
tenant_mappings:
- groups: ["dev-team"]
vm_tenants:
- account_id: "1000"
project_id: "frontend" # Specific project only
read_only: falsetenant_mappings:
- groups: ["security-audit"]
vm_tenants:
- account_id: "1000"
project_id: ".*"
- account_id: "2000"
project_id: "20"
read_only: true # Read-only for auditing# Check for users with wildcard + specific project combinations
grep -E "(.*|[0-9]+)" config.yaml# Solution: Use more specific tenant mappings
tenant_mappings:
- groups: ["team-a"]
vm_tenants:
- account_id: "1000" # Remove project_id for account-level access# Solution: Optimize tenant mappings to reduce complex tenant combinations
tenant_mappings:
- groups: ["team-a"]
vm_tenants:
- account_id: "1000" # Use more specific tenant grants- OR strategy may change query semantics
- Test dashboards with new filtering before production
- Consider tenant-specific dashboards
tenant_filter_duration_seconds: Query filtering performancetenant_filter_applied_total: Number of filtered queriestenant_access_denied_total: Access violations
groups:
- name: tenant-security
rules:
- alert: CrossTenantAccessAttempt
expr: increase(tenant_access_denied_total[5m]) > 0
annotations:
summary: "Potential cross-tenant access attempt detected"The OR strategy is now the only supported strategy for tenant filtering:
upstream:
tenant_filter:
strategy: "or_conditions" # Only supported strategyLegacy configurations without the tenant_filter section will default to the secure OR-based strategy.