mcpField
P
prowler-api

prowler-api

github.com/prowler-cloud开发15.1k73 次安装今天更新
代码仓库

关于 prowler-api

prowler-api 是面向 Prowler 后端开发的技能,涵盖租户隔离、RBAC、Provider 生命周期、Celery 任务及多数据库架构,侧重安全实现模式。

主要功能

  1. 01RLS 租户隔离模式
  2. 02RBAC 权限检查规则
  3. 03Provider 校验与生命周期
  4. 04Celery 租户上下文任务模式
  5. 05四数据库架构指南

适用场景

  1. 01实现租户范围内的 API 查询
  2. 02开发带权限控制的后端功能
  3. 03编写安全的异步任务
  4. 04在多个数据库间选择查询目标

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/prowler-cloud/prowler --skill prowler-api

安装后在 Claude Code 中输入/prowler-api确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
security_patterns.py
celery_patterns.py
references/
modeling-decisions.md
configuration.md
file-locations.md
production-settings.md

详细介绍

适用场景

在以下 Prowler 专属模式中使用此技能:

  • 行级安全(RLS)/ 租户隔离
  • RBAC 权限和角色检查
  • Provider 生命周期和验证
  • 携带租户上下文的 Celery 任务
  • 多数据库架构(4 数据库设置)

对于通用 DRF 模式(ViewSet、Serializer、Filter、JSON:API),请使用 django-drf 技能。


关键规则

  • 在 ViewSet 上下文之外查询时,始终使用 rls_transaction(tenant_id)
  • 检查权限前,始终使用 get_role()(只返回第一个角色)
  • 始终按 @set_tenant 再 @handle_provider_deletion 的顺序使用装饰器
  • 对 M2M 关系,始终使用显式 through 模型(RLS 要求如此)
  • Celery 任务中绝不在没有 RLS 上下文的情况下访问 Provider.objects
  • 绝不通过原始 SQL 或 connection.cursor() 绕过 RLS
  • 绝不使用 Django 默认的 M2M;RLS 要求使用带有 tenant_id 的 through 模型

注意:rls_transaction() 接受 UUID 对象和字符串,并通过 str(value) 在内部进行转换。


架构概览

4 数据库架构
数据库别名用途RLS
defaultprowler_user标准 API 查询是
adminadmin迁移、绕过身份验证否
replicaprowler_user只读查询是
admin_replicaadmin管理员只读副本否
# 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 事务流程
Request → Authentication → BaseRLSViewSet.initial()
                                    │
                                    ├─ Extract tenant_id from JWT
                                    ├─ SET api.tenant_id = 'uuid' (PostgreSQL)
                                    └─ All queries now tenant-scoped

实现清单

实现 Prowler 专属 API 功能时:

#模式参考要点
1RLS 模型api/rls.py继承 RowLevelSecurityProtectedModel,添加约束
2RLS 事务api/db_utils.py使用 rls_transaction(tenant_id) 上下文管理器
3RBAC 权限api/rbac/permissions.pyget_role()、get_providers()、Permissions 枚举
4Provider 验证api/models.py在 Provider 模型上定义 validate_<provider>_uid() 方法
5Celery 任务tasks/tasks.py、api/decorators.py、config/celery.py任务定义、装饰器(@set_tenant、@handle_provider_deletion)、RLSTask 基类
6RLS Serializerapi/v1/serializers.py继承 RLSSerializer,自动注入 tenant_id
7Through 模型api/models.py所有 M2M 都必须使用带有 tenant_id 的显式 through 模型

完整文件路径:请参阅 references/file-locations.md


决策树

使用哪个基类模型?
Tenant-scoped data       → RowLevelSecurityProtectedModel
Global/shared data       → models.Model + BaseSecurityConstraint (rare)
Partitioned time-series  → PostgresPartitionedModel + RowLevelSecurityProtectedModel
Soft-deletable           → Add is_deleted + ActiveProviderManager
使用哪个 Manager?
Normal queries           → Model.objects (excludes deleted)
Include deleted records  → Model.all_objects
Celery task context      → Must use rls_transaction() first
使用哪个数据库?
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 任务装饰器顺序?
@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 模型模式

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 关系(必须使用 through 模型)
class Resource(RowLevelSecurityProtectedModel):
    tags = models.ManyToManyField(
        ResourceTag,
        through="ResourceTagMapping",  # REQUIRED for RLS
    )

class ResourceTagMapping(RowLevelSecurityProtectedModel):
    # Through model MUST have tenant_id for RLS
    resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
    tag = models.ForeignKey(ResourceTag, on_delete=models.CASCADE)

    class Meta:
        constraints = [
            RowLevelSecurityConstraint(
                field="tenant_id",
                name="rls_on_%(class)s",
                statements=["SELECT", "INSERT", "UPDATE", "DELETE"],
            ),
        ]

异步任务响应模式(202 Accepted)

对于长时间运行的操作,返回 202 并附带任务引用:

@action(detail=True, methods=["post"], url_name="connection")
def connection(self, request, pk=None):
    with transaction.atomic():
        task = check_provider_connection_task.delay(
            provider_id=pk, tenant_id=self.request.tenant_id
        )
    prowler_task = Task.objects.get(id=task.id)
    serializer = TaskSerializer(prowler_task)
    return Response(
        data=serializer.data,
        status=status.HTTP_202_ACCEPTED,
        headers={"Content-Location": reverse("task-detail", kwargs={"pk": prowler_task.id})}
    )

Provider(支持 11 种)

ProviderUID 格式示例
AWS12 位数字123456789012
AzureUUID v4a1b2c3d4-e5f6-...
GCP6–30 个字符,小写,以字母开头my-gcp-project
M365有效域名contoso.onmicrosoft.com
Kubernetes2–251 个字符arn:aws:eks:...
GitHub1–39 个字符my-org
IaCGit URLhttps://github.com/user/repo.git
Oracle CloudOCID 格式ocid1.tenancy.oc1..
MongoDB Atlas24 位十六进制507f1f77bcf86cd799439011
Alibaba Cloud16 位数字1234567890123456

添加新 Provider:添加到 ProviderChoices 枚举,并创建 validate_<provider>_uid() 静态方法。


RBAC 权限

权限控制内容
MANAGE_USERS用户 CRUD、角色分配
MANAGE_ACCOUNT租户设置
MANAGE_BILLING账单/订阅
MANAGE_PROVIDERSProvider CRUD
MANAGE_INTEGRATIONS集成配置
MANAGE_SCANS扫描执行
UNLIMITED_VISIBILITY查看所有 Provider(绕过 provider_groups)
RBAC 可见性模式
def get_queryset(self):
    user_role = get_role(self.request.user)
    if user_role.unlimited_visibility:
        return Model.objects.filter(tenant_id=self.request.tenant_id)
    else:
        # Filter by provider_groups assigned to role
        return Model.objects.filter(provider__in=get_providers(user_role))

Celery 队列

队列用途
scansProwler 扫描执行
overview仪表板聚合(严重性、攻击面)
compliance合规报告生成
integrations外部集成(Jira、S3、Security Hub)
deletion提供商/租户删除(异步)
backfill历史数据回填操作
scan-reports输出生成(CSV、JSON、HTML、PDF)

任务组合(Canvas)

使用 Celery 的 Canvas 原语构建复杂工作流:

原语用途
chain()顺序执行:A → B → C
group()并行执行:同时执行 A、B、C
组合使用嵌套 group 的 chain 构建复杂工作流

注意: 使用 .si()(签名不可变)可阻止传递结果。需要传递结果时使用 .s()。

示例: 查看 assets/celery_patterns.py,了解 chain、group 和组合模式。


Beat 调度(周期性任务)

操作要点
创建计划IntervalSchedule.objects.get_or_create(every=24, period=HOURS)
创建周期性任务使用任务名称(而非函数),kwargs=json.dumps(...)
删除计划任务PeriodicTask.objects.filter(name=...).delete()
避免竞态条件使用 countdown=5 等待数据库提交

示例: 查看 assets/celery_patterns.py,了解 schedule_provider_scan 模式。


高级任务模式

@set_tenant 行为
模式kwargs 中的 tenant_id传递给函数的 tenant_id
@set_tenant(默认)弹出(移除)否——函数不会收到它
@set_tenant(keep_tenant=True)读取但保留是——函数会收到它
关键模式
模式描述
bind=True访问 self.request.id、self.request.retries
get_task_logger(__name__)在 Celery 任务中正确记录日志
SoftTimeLimitExceeded捕获该异常,以便在强制终止前保存进度
countdown=30延迟 N 秒执行
eta=datetime(...)在指定时间执行

示例: 查看 assets/celery_patterns.py,了解所有高级模式。


Celery 配置

设置值用途
BROKER_VISIBILITY_TIMEOUT86400(24 小时)防止长时间任务重新入队
CELERY_RESULT_BACKENDdjango-db将结果存储在 PostgreSQL 中
CELERY_TASK_TRACK_STARTEDTrue跟踪任务的启动时间
soft_time_limit任务专用引发 SoftTimeLimitExceeded
time_limit任务专用强制终止(SIGKILL)

完整配置: 查看 assets/celery_patterns.py 以及 config/celery.py、config/settings/celery.py 中的实际文件。


分区表中的 UUIDv7

Finding 和 ResourceFindingMapping 使用 UUIDv7 进行基于时间的分区:

from uuid6 import uuid7
from api.uuid_utils import uuid7_start, uuid7_end, datetime_to_uuid7

# Partition-aware filtering
start = uuid7_start(datetime_to_uuid7(date_from))
end = uuid7_end(datetime_to_uuid7(date_to), settings.FINDINGS_TABLE_PARTITION_MONTHS)
queryset.filter(id__gte=start, id__lt=end)

为什么使用 UUIDv7? 按时间排序的 UUID 可使 PostgreSQL 在范围查询期间裁剪分区。


使用 RLS 的批量操作

from api.db_utils import batch_delete, create_objects_in_batches, update_objects_in_batches

# Delete in batches (RLS-aware)
batch_delete(tenant_id, queryset, batch_size=1000)

# Bulk create with RLS
create_objects_in_batches(tenant_id, Finding, objects, batch_size=500)

# Bulk update with RLS
update_objects_in_batches(tenant_id, Finding, objects, fields=["status"], batch_size=500)

安全模式

完整示例:查看 assets/security_patterns.py

租户隔离摘要
模式规则
ViewSet 中的 RLS通过 BaseRLSViewSet 自动实现——从 JWT 获取 tenant_id
Celery 中的 RLS必须使用 @set_tenant + rls_transaction(tenant_id)
跨租户验证深度防御:验证 obj.tenant_id == request.tenant_id
绝不信任用户输入使用 JWT 中的 request.tenant_id,绝不使用 request.data.get("tenant_id")
管理员 DB 绕过仅用于跨租户管理员操作——会暴露所有租户的数据
Celery 任务安全摘要
模式规则
仅使用命名任务切勿使用来自用户输入的动态任务名称
验证参数在执行数据库查询前检查 UUID 格式
安全排队使用 transaction.on_commit() 在提交后入队
现代重试机制使用 autoretry_for、retry_backoff、retry_jitter
时间限制设置 soft_time_limit 和 time_limit,防止任务挂起
幂等性使用 update_or_create 或幂等键
快速参考
# Safe task queuing - task only enqueued after transaction commits
with transaction.atomic():
    provider = Provider.objects.create(**data)
    transaction.on_commit(
        lambda: verify_provider_connection.delay(
            tenant_id=str(request.tenant_id),
            provider_id=str(provider.id)
        )
    )

# Modern retry pattern
@shared_task(
    base=RLSTask,
    bind=True,
    autoretry_for=(ConnectionError, TimeoutError, OperationalError),
    retry_backoff=True,
    retry_backoff_max=600,
    retry_jitter=True,
    max_retries=5,
    soft_time_limit=300,
    time_limit=360,
)
@set_tenant
def sync_provider_data(self, tenant_id, provider_id):
    with rls_transaction(tenant_id):
        # ... task logic
        pass

# Idempotent task - safe to retry
@shared_task(base=RLSTask, acks_late=True)
@set_tenant
def process_finding(tenant_id, finding_uid, data):
    with rls_transaction(tenant_id):
        Finding.objects.update_or_create(uid=finding_uid, defaults=data)

生产环境部署检查清单

完整设置:请参阅 references/production-settings.md

每次部署到生产环境前运行:

cd api && uv run python src/backend/manage.py check --deploy
关键设置
设置生产环境值配置错误时的风险
DEBUGFalse暴露堆栈跟踪、设置和 SQL 查询
SECRET_KEY环境变量,定期轮换会话劫持、绕过 CSRF
ALLOWED_HOSTS明确的列表Host 标头攻击
SECURE_SSL_REDIRECTTrue凭据通过 HTTP 发送
SESSION_COOKIE_SECURETrue会话 Cookie 通过 HTTP 发送
CSRF_COOKIE_SECURETrueCSRF 令牌通过 HTTP 发送
SECURE_HSTS_SECONDS31536000(1 年)降级攻击
CONN_MAX_AGE60 或更高连接池耗尽

命令

# Development
cd api && uv run python src/backend/manage.py runserver
cd api && uv run python src/backend/manage.py shell

# Celery
cd api && uv run celery -A config.celery worker -l info -Q scans,overview
cd api && uv run celery -A config.celery beat -l info

# Testing
cd api && uv run pytest -x --tb=short

# Production checks
cd api && uv run python src/backend/manage.py check --deploy

资源

本地参考资料
相关技能
  • 通用 DRF 模式:使用 django-drf 技能
  • API 测试:使用 prowler-test-api 技能
Context7 MCP(推荐)

**前提条件:**安装 Context7 MCP 服务,以便查询最新文档。

实现或调试 Prowler 专属模式时,通过 mcp_context7_query-docs 查询以下库:

库Context7 ID用途
Celery/websites/celeryq_dev_en_stable任务模式、队列、错误处理
django-celery-beat/celery/django-celery-beat定期任务调度
Django/websites/djangoproject_en_5_2模型、ORM、约束、索引

查询示例:

mcp_context7_query-docs(libraryId="/websites/celeryq_dev_en_stable", query="shared_task decorator retry patterns")
mcp_context7_query-docs(libraryId="/celery/django-celery-beat", query="periodic task database scheduler")
mcp_context7_query-docs(libraryId="/websites/djangoproject_en_5_2", query="model constraints CheckConstraint UniqueConstraint")

**注意:**如果需要查找正确的库 ID,请先使用 mcp_context7_resolve-library-id。

评论

支持 Markdown