prowler-api
Prowler is the world’s most widely used open-source cloud security platform that automates security and compliance across any cloud environment.
12.8K stars2K forksUpdated Jan 26, 2026
npx skills add https://github.com/prowler-cloud/prowler --skill prowler-apiSKILL.md
When to Use
Use this skill for Prowler-specific patterns:
- Row-Level Security (RLS) / tenant isolation
- RBAC permissions and role checks
- Provider lifecycle and validation
- Celery tasks with tenant context
- Multi-database architecture (4-database setup)
For generic DRF patterns (ViewSets, Serializers, Filters, JSON:API), use django-drf skill.
Critical Rules
- ALWAYS use
rls_transaction(tenant_id)when querying outside ViewSet context - ALWAYS use
get_role()before checking permissions (returns FIRST role only) - ALWAYS use
@set_tenantthen@handle_provider_deletiondecorator order - ALWAYS use explicit through models for M2M relationships (required for RLS)
- NEVER access
Provider.objectswithout RLS context in Celery tasks - NEVER bypass RLS by using raw SQL or
connection.cursor() - NEVER use Django's default M2M - RLS requires through models with
tenant_id
Note:
rls_transaction()accepts both UUID objects and strings - it converts internally viastr(value).
Architecture Overview
4-Database Architecture
| Database | Alias | Purpose | RLS |
|---|---|---|---|
default | prowler_user | Standard API queries | Yes |
admin | admin | Migrations, auth bypass | No |
replica | prowler_user | Read-only queries | Yes |
admin_replica | admin | Admin read replica | No |
# When to use admin (bypasses RLS)
from api.db_router import MainRouter
User.objects.using(MainRouter.admin_db).get(id=user_id) # Auth lookups
# Standard queries use default (RLS enforced)
Provider.objects.filter(connected=True) # Requires rls_transaction context
RLS Transaction Flow
Request → Authentication → BaseRLSViewSet.initial()
│
├─ Extract tenant_id from JWT
├─ SET api.tenant_id = 'uuid' (PostgreSQL)
└─ All queries now tenant-scoped
Implementation Checklist
When implementing Prowler-specific API features:
| # | Pattern | Reference | Key Points |
|---|---|---|---|
| 1 | RLS Models | api/rls.py | Inherit RowLevelSecurityProtectedModel, add constraint |
| 2 | RLS Transactions | api/db_utils.py | Use rls_transaction(tenant_id) context manager |
| 3 | RBAC Permissions | api/rbac/permissions.py | get_role(), get_providers(), Permissions enum |
| 4 | Provider Validation | api/models.py | validate_<provider>_uid() methods on Provider model |
| 5 | Celery Tasks | tasks/tasks.py, api/decorators.py, config/celery.py | Task definitions, decorators (@set_tenant, @handle_provider_deletion), RLSTask base |
| 6 | RLS Serializers | api/v1/serializers.py | Inherit RLSSerializer to auto-inject tenant_id |
| 7 | Through Models | api/models.py | ALL M2M must use explicit through with tenant_id |
Full file paths: See references/file-locations.md
Decision Trees
Which Base Model?
Tenant-scoped data → RowLevelSecurityProtectedModel
Global/shared data → models.Model + BaseSecurityConstraint (rare)
Partitioned time-series → PostgresPartitionedModel + RowLevelSecurityProtectedModel
Soft-deletable → Add is_deleted + ActiveProviderManager
Which Manager?
Normal queries → Model.objects (excludes deleted)
Include deleted records → Model.all_objects
Celery task context → Must use rls_transaction() first
Which Database?
Standard API queries → default (automatic via ViewSet)
Read-only operations → replica (automatic for GET in BaseRLSViewSet)
Auth/admin operations → MainRouter.admin_db
Cross-tenant lookups → MainRouter.admin_db (use sparingly!)
Celery Task Decorator Order?
@shared_task(base=RLSTask, name="...", queue="...")
@set_tenant # First: sets tenant context
@handle_provider_deletion # Second: handles deleted providers
def my_task(tenant_id, provider_id):
pass
RLS Model Pattern
from api.rls import RowLevelSecurityProtectedModel, RowLevelSecurityConstraint
class MyModel(RowLevelSecurityProtectedModel):
# tenant FK inherited from parent
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
name = models.CharField(max_length=255)
inserted_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
class Meta(RowLevelSecurityProtectedModel.Meta):
db_table = "my_models"
constraints = [
RowLevelSecurityConstraint(
field="tenant_id",
name="rls_on_%(class)s",
statements=["SELECT", "INSERT", "UPDATE", "DELETE"],
),
]
class JSONAPIMeta:
resource_name = "my-models"
M2M Re
...
Repository
prowler-cloud/prowlerParent repository
Repository Stats
Stars12.8K
Forks2K
LicenseApache License 2.0