diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 34a38ada6f..be9b353ce7 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler API** are documented in this file. ### 🚀 Added +- Cloudflare provider support [(#9907)](https://github.com/prowler-cloud/prowler/pull/9907) - Attack Paths: Bedrock Code Interpreter and AttachRolePolicy privilege escalation queries [(#9885)](https://github.com/prowler-cloud/prowler/pull/9885) - `provider_id` and `provider_id__in` filters for resources endpoints (`GET /resources` and `GET /resources/metadata/latest`) [(#9864)](https://github.com/prowler-cloud/prowler/pull/9864) - Added memory optimizations for large compliance report generation [(#9444)](https://github.com/prowler-cloud/prowler/pull/9444) diff --git a/api/src/backend/api/migrations/0075_cloudflare_provider.py b/api/src/backend/api/migrations/0075_cloudflare_provider.py new file mode 100644 index 0000000000..28fdbdb2a9 --- /dev/null +++ b/api/src/backend/api/migrations/0075_cloudflare_provider.py @@ -0,0 +1,38 @@ +# Generated by Django migration for Cloudflare provider support + +from django.db import migrations + +import api.db_utils + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0074_findings_fail_new_index_parent"), + ] + + operations = [ + migrations.AlterField( + model_name="provider", + name="provider", + field=api.db_utils.ProviderEnumField( + choices=[ + ("aws", "AWS"), + ("azure", "Azure"), + ("gcp", "GCP"), + ("kubernetes", "Kubernetes"), + ("m365", "M365"), + ("github", "GitHub"), + ("mongodbatlas", "MongoDB Atlas"), + ("iac", "IaC"), + ("oraclecloud", "Oracle Cloud Infrastructure"), + ("alibabacloud", "Alibaba Cloud"), + ("cloudflare", "Cloudflare"), + ], + default="aws", + ), + ), + migrations.RunSQL( + "ALTER TYPE provider ADD VALUE IF NOT EXISTS 'cloudflare';", + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index fdd52cb8d1..4bda51761c 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -287,6 +287,7 @@ class Provider(RowLevelSecurityProtectedModel): IAC = "iac", _("IaC") ORACLECLOUD = "oraclecloud", _("Oracle Cloud Infrastructure") ALIBABACLOUD = "alibabacloud", _("Alibaba Cloud") + CLOUDFLARE = "cloudflare", _("Cloudflare") @staticmethod def validate_aws_uid(value): @@ -400,6 +401,15 @@ class Provider(RowLevelSecurityProtectedModel): pointer="/data/attributes/uid", ) + @staticmethod + def validate_cloudflare_uid(value): + if not re.match(r"^[a-f0-9]{32}$", value): + raise ModelValidationError( + detail="Cloudflare Account ID must be a 32-character hexadecimal string.", + code="cloudflare-uid", + pointer="/data/attributes/uid", + ) + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) inserted_at = models.DateTimeField(auto_now_add=True, editable=False) updated_at = models.DateTimeField(auto_now=True, editable=False) diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index 5acfad545c..778233ddab 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -355,11 +355,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -378,17 +379,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -409,6 +412,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -1332,11 +1336,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -1355,17 +1360,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -1386,6 +1393,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -1918,11 +1926,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -1941,17 +1950,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -1972,6 +1983,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -2412,11 +2424,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -2435,17 +2448,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -2466,6 +2481,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -2904,11 +2920,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -2927,17 +2944,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -2958,6 +2977,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -3384,11 +3404,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -3407,17 +3428,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -3438,6 +3461,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -5205,11 +5229,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5228,17 +5253,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5259,6 +5286,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - name: filter[search] @@ -5364,11 +5392,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5387,17 +5416,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5418,6 +5449,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - name: filter[search] @@ -5517,6 +5549,7 @@ paths: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5535,6 +5568,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: @@ -5545,6 +5579,7 @@ paths: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5565,6 +5600,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - name: filter[search] @@ -5691,11 +5727,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5714,17 +5751,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5745,6 +5784,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -5884,11 +5924,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5907,17 +5948,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -5938,6 +5981,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -6076,6 +6120,7 @@ paths: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6094,6 +6139,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: @@ -6104,6 +6150,7 @@ paths: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6124,6 +6171,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - name: filter[search] @@ -6299,11 +6347,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6322,17 +6371,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6353,6 +6404,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -6457,11 +6509,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6480,17 +6533,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6511,6 +6566,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -6639,11 +6695,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6662,17 +6719,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -6693,6 +6752,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -7462,11 +7522,12 @@ paths: name: filter[provider] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -7485,17 +7546,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -7516,17 +7579,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -7545,17 +7610,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -7576,6 +7643,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - name: filter[search] @@ -8205,11 +8273,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -8228,17 +8297,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -8259,6 +8330,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -8526,6 +8598,29 @@ paths: **Note:** Some events may not appear due to CloudTrail indexing limitations. Not all AWS API calls record the resource identifier in a searchable format. summary: Get events for a resource parameters: + - in: query + name: fields[resource-events] + schema: + type: array + items: + type: string + enum: + - id + - event_time + - event_name + - event_source + - actor + - actor_uid + - actor_type + - source_ip_address + - user_agent + - request_data + - response_data + - error_code + - error_message + description: endpoint return only specific fields in the response on a per-type + basis by including a fields[TYPE] query parameter. + explode: false - in: path name: id schema: @@ -8680,11 +8775,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -8703,17 +8799,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -8734,6 +8832,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -8991,11 +9090,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -9014,17 +9114,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -9045,6 +9147,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -9308,11 +9411,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -9331,17 +9435,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -9362,6 +9468,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -10159,11 +10266,12 @@ paths: name: filter[provider_type] schema: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -10182,17 +10290,19 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare - in: query name: filter[provider_type__in] schema: type: array items: type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b enum: - alibabacloud - aws - azure + - cloudflare - gcp - github - iac @@ -10213,6 +10323,7 @@ paths: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare explode: false style: form - in: query @@ -17182,6 +17293,29 @@ components: - role_arn - access_key_id - access_key_secret + - type: object + title: Cloudflare API Token + properties: + api_token: + type: string + description: Cloudflare API Token for authentication (recommended). + required: + - api_token + - type: object + title: Cloudflare API Key + Email + properties: + api_key: + type: string + description: Cloudflare Global API Key for authentication + (legacy). + api_email: + type: string + format: email + description: Email address associated with the Cloudflare + account. + required: + - api_key + - api_email writeOnly: true required: - secret @@ -18180,6 +18314,7 @@ components: - iac - oraclecloud - alibabacloud + - cloudflare type: string description: |- * `aws` - AWS @@ -18192,7 +18327,8 @@ components: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud - x-spec-enum-id: 684bf4173d2b754f + * `cloudflare` - Cloudflare + x-spec-enum-id: 2d8d323e9cc0044b uid: type: string title: Unique identifier for the provider, set by the provider @@ -18308,8 +18444,9 @@ components: - iac - oraclecloud - alibabacloud + - cloudflare type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b description: |- Type of provider to create. @@ -18323,6 +18460,7 @@ components: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare uid: type: string title: Unique identifier for the provider, set by the provider @@ -18370,8 +18508,9 @@ components: - iac - oraclecloud - alibabacloud + - cloudflare type: string - x-spec-enum-id: 684bf4173d2b754f + x-spec-enum-id: 2d8d323e9cc0044b description: |- Type of provider to create. @@ -18385,6 +18524,7 @@ components: * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure * `alibabacloud` - Alibaba Cloud + * `cloudflare` - Cloudflare uid: type: string minLength: 3 @@ -19185,6 +19325,27 @@ components: - role_arn - access_key_id - access_key_secret + - type: object + title: Cloudflare API Token + properties: + api_token: + type: string + description: Cloudflare API Token for authentication (recommended). + required: + - api_token + - type: object + title: Cloudflare API Key + Email + properties: + api_key: + type: string + description: Cloudflare Global API Key for authentication (legacy). + api_email: + type: string + format: email + description: Email address associated with the Cloudflare account. + required: + - api_key + - api_email writeOnly: true required: - secret_type @@ -19548,6 +19709,29 @@ components: - role_arn - access_key_id - access_key_secret + - type: object + title: Cloudflare API Token + properties: + api_token: + type: string + description: Cloudflare API Token for authentication (recommended). + required: + - api_token + - type: object + title: Cloudflare API Key + Email + properties: + api_key: + type: string + description: Cloudflare Global API Key for authentication + (legacy). + api_email: + type: string + format: email + description: Email address associated with the Cloudflare + account. + required: + - api_key + - api_email writeOnly: true required: - secret_type @@ -19925,6 +20109,27 @@ components: - role_arn - access_key_id - access_key_secret + - type: object + title: Cloudflare API Token + properties: + api_token: + type: string + description: Cloudflare API Token for authentication (recommended). + required: + - api_token + - type: object + title: Cloudflare API Key + Email + properties: + api_key: + type: string + description: Cloudflare Global API Key for authentication (legacy). + api_email: + type: string + format: email + description: Email address associated with the Cloudflare account. + required: + - api_key + - api_email writeOnly: true required: - secret diff --git a/api/src/backend/api/tests/test_utils.py b/api/src/backend/api/tests/test_utils.py index 6f9146cd5c..56f98a5401 100644 --- a/api/src/backend/api/tests/test_utils.py +++ b/api/src/backend/api/tests/test_utils.py @@ -20,6 +20,7 @@ from prowler.providers.alibabacloud.alibabacloud_provider import AlibabacloudPro from prowler.providers.aws.aws_provider import AwsProvider from prowler.providers.aws.lib.security_hub.security_hub import SecurityHubConnection from prowler.providers.azure.azure_provider import AzureProvider +from prowler.providers.cloudflare.cloudflare_provider import CloudflareProvider from prowler.providers.gcp.gcp_provider import GcpProvider from prowler.providers.github.github_provider import GithubProvider from prowler.providers.iac.iac_provider import IacProvider @@ -118,6 +119,7 @@ class TestReturnProwlerProvider: (Provider.ProviderChoices.ORACLECLOUD.value, OraclecloudProvider), (Provider.ProviderChoices.IAC.value, IacProvider), (Provider.ProviderChoices.ALIBABACLOUD.value, AlibabacloudProvider), + (Provider.ProviderChoices.CLOUDFLARE.value, CloudflareProvider), ], ) def test_return_prowler_provider(self, provider_type, expected_provider): @@ -221,6 +223,10 @@ class TestGetProwlerProviderKwargs: Provider.ProviderChoices.MONGODBATLAS.value, {"atlas_organization_id": "provider_uid"}, ), + ( + Provider.ProviderChoices.CLOUDFLARE.value, + {"filter_accounts": ["provider_uid"]}, + ), ], ) def test_get_prowler_provider_kwargs(self, provider_type, expected_extra_kwargs): diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 8d4fdabcbe..4bb7509bc7 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -1174,6 +1174,11 @@ class TestProviderViewSet: "uid": "1234567890123456", "alias": "Alibaba Cloud Account", }, + { + "provider": "cloudflare", + "uid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "alias": "Cloudflare Account", + }, ] ), ) @@ -1553,6 +1558,46 @@ class TestProviderViewSet: "alibabacloud-uid", "uid", ), + # Cloudflare UID validation - too short (not 32 hex chars) + ( + { + "provider": "cloudflare", + "uid": "abc123", + "alias": "test", + }, + "cloudflare-uid", + "uid", + ), + # Cloudflare UID validation - uppercase hex (must be lowercase) + ( + { + "provider": "cloudflare", + "uid": "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4", + "alias": "test", + }, + "cloudflare-uid", + "uid", + ), + # Cloudflare UID validation - non-hex characters + ( + { + "provider": "cloudflare", + "uid": "g1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "alias": "test", + }, + "cloudflare-uid", + "uid", + ), + # Cloudflare UID validation - too long (33 chars) + ( + { + "provider": "cloudflare", + "uid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e", + "alias": "test", + }, + "cloudflare-uid", + "uid", + ), ] ), ) @@ -1726,21 +1771,21 @@ class TestProviderViewSet: ( "uid.icontains", "1", - 8, + 9, ), ("alias", "aws_testing_1", 1), ("alias.icontains", "aws", 2), - ("inserted_at", TODAY, 9), + ("inserted_at", TODAY, 10), ( "inserted_at.gte", "2024-01-01", - 9, + 10, ), ("inserted_at.lte", "2024-01-01", 0), ( "updated_at.gte", "2024-01-01", - 9, + 10, ), ("updated_at.lte", "2024-01-01", 0), ] @@ -2330,6 +2375,23 @@ class TestProviderSecretViewSet: "role_session_name": "ProwlerAuditSession", }, ), + # Cloudflare with API Token + ( + Provider.ProviderChoices.CLOUDFLARE.value, + ProviderSecret.TypeChoices.STATIC, + { + "api_token": "fake-cloudflare-api-token-for-testing", + }, + ), + # Cloudflare with API Key + Email + ( + Provider.ProviderChoices.CLOUDFLARE.value, + ProviderSecret.TypeChoices.STATIC, + { + "api_key": "fake-cloudflare-api-key-for-testing", + "api_email": "user@example.com", + }, + ), ], ) def test_provider_secrets_create_valid( diff --git a/api/src/backend/api/utils.py b/api/src/backend/api/utils.py index ca3e4db86b..343537efa9 100644 --- a/api/src/backend/api/utils.py +++ b/api/src/backend/api/utils.py @@ -24,6 +24,7 @@ if TYPE_CHECKING: ) from prowler.providers.aws.aws_provider import AwsProvider from prowler.providers.azure.azure_provider import AzureProvider + from prowler.providers.cloudflare.cloudflare_provider import CloudflareProvider from prowler.providers.gcp.gcp_provider import GcpProvider from prowler.providers.github.github_provider import GithubProvider from prowler.providers.iac.iac_provider import IacProvider @@ -91,7 +92,7 @@ def return_prowler_provider( provider (Provider): The provider object containing the provider type and associated secrets. Returns: - AlibabacloudProvider | AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: The corresponding provider class. + AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: The corresponding provider class. Raises: ValueError: If the provider type specified in `provider.provider` is not supported. @@ -145,6 +146,12 @@ def return_prowler_provider( ) prowler_provider = AlibabacloudProvider + case Provider.ProviderChoices.CLOUDFLARE.value: + from prowler.providers.cloudflare.cloudflare_provider import ( + CloudflareProvider, + ) + + prowler_provider = CloudflareProvider case _: raise ValueError(f"Provider type {provider.provider} not supported") return prowler_provider @@ -196,6 +203,11 @@ def get_prowler_provider_kwargs( **prowler_provider_kwargs, "atlas_organization_id": provider.uid, } + elif provider.provider == Provider.ProviderChoices.CLOUDFLARE.value: + prowler_provider_kwargs = { + **prowler_provider_kwargs, + "filter_accounts": [provider.uid], + } if mutelist_processor: mutelist_content = mutelist_processor.configuration.get("Mutelist", {}) @@ -213,6 +225,7 @@ def initialize_prowler_provider( AlibabacloudProvider | AwsProvider | AzureProvider + | CloudflareProvider | GcpProvider | GithubProvider | IacProvider @@ -228,7 +241,7 @@ def initialize_prowler_provider( mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration. Returns: - AlibabacloudProvider | AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: An instance of the corresponding provider class + AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: An instance of the corresponding provider class initialized with the provider's secrets. """ prowler_provider = return_prowler_provider(provider) diff --git a/api/src/backend/api/v1/serializer_utils/providers.py b/api/src/backend/api/v1/serializer_utils/providers.py index 3ed2a58ef3..7b34b06d2b 100644 --- a/api/src/backend/api/v1/serializer_utils/providers.py +++ b/api/src/backend/api/v1/serializer_utils/providers.py @@ -346,6 +346,33 @@ from rest_framework_json_api import serializers }, "required": ["role_arn", "access_key_id", "access_key_secret"], }, + { + "type": "object", + "title": "Cloudflare API Token", + "properties": { + "api_token": { + "type": "string", + "description": "Cloudflare API Token for authentication (recommended).", + }, + }, + "required": ["api_token"], + }, + { + "type": "object", + "title": "Cloudflare API Key + Email", + "properties": { + "api_key": { + "type": "string", + "description": "Cloudflare Global API Key for authentication (legacy).", + }, + "api_email": { + "type": "string", + "format": "email", + "description": "Email address associated with the Cloudflare account.", + }, + }, + "required": ["api_key", "api_email"], + }, ] } ) diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index fac8d1cd57..e2ae7fc8d2 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -1503,6 +1503,18 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer): serializer = MongoDBAtlasProviderSecret(data=secret) elif provider_type == Provider.ProviderChoices.ALIBABACLOUD.value: serializer = AlibabaCloudProviderSecret(data=secret) + elif provider_type == Provider.ProviderChoices.CLOUDFLARE.value: + if "api_token" in secret: + serializer = CloudflareTokenProviderSecret(data=secret) + elif "api_key" in secret and "api_email" in secret: + serializer = CloudflareApiKeyProviderSecret(data=secret) + else: + raise serializers.ValidationError( + { + "secret": "Cloudflare credentials must include either 'api_token' " + "or both 'api_key' and 'api_email'." + } + ) else: raise serializers.ValidationError( {"provider": f"Provider type not supported {provider_type}"} @@ -1654,6 +1666,21 @@ class OracleCloudProviderSecret(serializers.Serializer): resource_name = "provider-secrets" +class CloudflareTokenProviderSecret(serializers.Serializer): + api_token = serializers.CharField() + + class Meta: + resource_name = "provider-secrets" + + +class CloudflareApiKeyProviderSecret(serializers.Serializer): + api_key = serializers.CharField() + api_email = serializers.EmailField() + + class Meta: + resource_name = "provider-secrets" + + class AlibabaCloudProviderSecret(serializers.Serializer): access_key_id = serializers.CharField() access_key_secret = serializers.CharField() diff --git a/api/src/backend/conftest.py b/api/src/backend/conftest.py index a3b683ffea..e50f92e1ab 100644 --- a/api/src/backend/conftest.py +++ b/api/src/backend/conftest.py @@ -1,11 +1,9 @@ import logging -from types import SimpleNamespace - from datetime import datetime, timedelta, timezone +from types import SimpleNamespace from unittest.mock import MagicMock, patch import pytest - from allauth.socialaccount.models import SocialLogin from django.conf import settings from django.db import connection as django_connection @@ -14,6 +12,11 @@ from django.urls import reverse from django_celery_results.models import TaskResult from rest_framework import status from rest_framework.test import APIClient +from tasks.jobs.backfill import ( + backfill_resource_scan_summaries, + backfill_scan_category_summaries, + backfill_scan_resource_group_summaries, +) from api.attack_paths import ( AttackPathsQueryDefinition, @@ -59,11 +62,6 @@ from api.rls import Tenant from api.v1.serializers import TokenSerializer from prowler.lib.check.models import Severity from prowler.lib.outputs.finding import Status -from tasks.jobs.backfill import ( - backfill_resource_scan_summaries, - backfill_scan_category_summaries, - backfill_scan_resource_group_summaries, -) TODAY = str(datetime.today().date()) API_JSON_CONTENT_TYPE = "application/vnd.api+json" @@ -533,6 +531,12 @@ def providers_fixture(tenants_fixture): alias="alibabacloud_testing", tenant_id=tenant.id, ) + provider10 = Provider.objects.create( + provider="cloudflare", + uid="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + alias="cloudflare_testing", + tenant_id=tenant.id, + ) return ( provider1, @@ -544,6 +548,7 @@ def providers_fixture(tenants_fixture): provider7, provider8, provider9, + provider10, ) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 3ee77fff9e..a28dc328b1 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `rds_instance_extended_support` check for AWS provider [(#9865)](https://github.com/prowler-cloud/prowler/pull/9865) - `OpenStack` provider support with Compute service including 1 security check [(#9811)](https://github.com/prowler-cloud/prowler/pull/9811) - `OpenStack` documentation for the support in the CLI [(#9848)](https://github.com/prowler-cloud/prowler/pull/9848) +- Cloudflare provider credentials as constructor parameters (`api_token`, `api_key`, `api_email`) [(#9907)](https://github.com/prowler-cloud/prowler/pull/9907) ### Changed diff --git a/prowler/providers/cloudflare/cloudflare_provider.py b/prowler/providers/cloudflare/cloudflare_provider.py index ea0a620087..689d2da2b1 100644 --- a/prowler/providers/cloudflare/cloudflare_provider.py +++ b/prowler/providers/cloudflare/cloudflare_provider.py @@ -49,6 +49,9 @@ class CloudflareProvider(Provider): fixer_config: dict = {}, mutelist_path: str = None, mutelist_content: dict = None, + api_token: str = None, + api_key: str = None, + api_email: str = None, ): logger.info("Instantiating Cloudflare provider...") @@ -61,7 +64,12 @@ class CloudflareProvider(Provider): max_retries = self._audit_config.get("max_retries", 2) - self._session = CloudflareProvider.setup_session(max_retries=max_retries) + self._session = CloudflareProvider.setup_session( + max_retries=max_retries, + api_token=api_token, + api_key=api_key, + api_email=api_email, + ) self._identity = CloudflareProvider.setup_identity(self._session) @@ -135,19 +143,28 @@ class CloudflareProvider(Provider): return self._identity.accounts @staticmethod - def setup_session(max_retries: int = 2) -> CloudflareSession: + def setup_session( + max_retries: int = 2, + api_token: str = None, + api_key: str = None, + api_email: str = None, + ) -> CloudflareSession: """Initialize Cloudflare SDK client. - Credentials are read from environment variables: + Credentials can be provided as arguments or read from environment variables: - CLOUDFLARE_API_TOKEN (recommended) - CLOUDFLARE_API_KEY and CLOUDFLARE_API_EMAIL (legacy) Args: max_retries: Maximum number of retries for API requests (default is 2). + api_token: Cloudflare API token (optional, falls back to env var). + api_key: Cloudflare API key (optional, falls back to env var). + api_email: Cloudflare API email (optional, falls back to env var). """ - token = os.environ.get("CLOUDFLARE_API_TOKEN", "") - key = os.environ.get("CLOUDFLARE_API_KEY", "") - email = os.environ.get("CLOUDFLARE_API_EMAIL", "") + # Use provided credentials or fall back to environment variables + token = api_token or os.environ.get("CLOUDFLARE_API_TOKEN", "") + key = api_key or os.environ.get("CLOUDFLARE_API_KEY", "") + email = api_email or os.environ.get("CLOUDFLARE_API_EMAIL", "") # Warn if both auth methods are set, use API Token (recommended) if token and key and email: @@ -293,14 +310,42 @@ class CloudflareProvider(Provider): print_boxes(report_lines, report_title) - def test_connection(self) -> Connection: + @staticmethod + def test_connection( + api_token: str = None, + api_key: str = None, + api_email: str = None, + raise_on_exception: bool = True, + provider_id: str = None, + ) -> Connection: + """Test connection to Cloudflare. + + Test the connection to Cloudflare using the provided credentials. + + Args: + api_token: Cloudflare API token (optional, falls back to env var). + api_key: Cloudflare API key (optional, falls back to env var). + api_email: Cloudflare API email (optional, falls back to env var). + raise_on_exception: Flag indicating whether to raise an exception if the connection fails. + provider_id: The provider ID (Cloudflare account ID). + + Returns: + Connection: Connection object with is_connected status. + """ try: - _ = self._session.client.user.get() + session = CloudflareProvider.setup_session( + api_token=api_token, + api_key=api_key, + api_email=api_email, + ) + _ = session.client.user.get() return Connection(is_connected=True) except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + if raise_on_exception: + raise error return Connection(is_connected=False, error=error) def validate_arguments(self) -> None: diff --git a/prowler/providers/cloudflare/services/zone/zone_service.py b/prowler/providers/cloudflare/services/zone/zone_service.py index 7a718cd3fb..1fdb133e68 100644 --- a/prowler/providers/cloudflare/services/zone/zone_service.py +++ b/prowler/providers/cloudflare/services/zone/zone_service.py @@ -149,7 +149,7 @@ class Zone(CloudflareService): def _get_zones_universal_ssl(self) -> None: """Get Universal SSL settings for all zones.""" - logger.info("Zones - Getting Universal SSL settings...") + logger.info("Zone - Getting Universal SSL settings...") for zone in self.zones.values(): try: universal_ssl = self.client.ssl.universal.settings.get(zone_id=zone.id) @@ -220,7 +220,7 @@ class Zone(CloudflareService): def _get_zones_firewall_rules(self) -> None: """Get firewall rules for all zones.""" - logger.info("Zones - Getting firewall rules...") + logger.info("Zone - Getting firewall rules...") for zone in self.zones.values(): try: self._get_zone_firewall_rules(zone) @@ -284,7 +284,7 @@ class Zone(CloudflareService): def _get_zones_waf_rulesets(self) -> None: """Get WAF rulesets for all zones.""" - logger.info("Zones - Getting WAF rulesets...") + logger.info("Zone - Getting WAF rulesets...") for zone in self.zones.values(): try: self._get_zone_waf_rulesets(zone)