mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-15 00:57:55 +00:00
Compare commits
4 Commits
PROWLER-13
...
ensure-key
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d35e5a4bec | ||
|
|
861be13b7d | ||
|
|
62809e523e | ||
|
|
5ff6c3c35f |
@@ -38,6 +38,7 @@ All notable changes to the **Prowler API** are documented in this file.
|
|||||||
- Attack Paths: scan no longer raises `DatabaseError` when provider is deleted mid-scan [(#10116)](https://github.com/prowler-cloud/prowler/pull/10116)
|
- Attack Paths: scan no longer raises `DatabaseError` when provider is deleted mid-scan [(#10116)](https://github.com/prowler-cloud/prowler/pull/10116)
|
||||||
- Tenant compliance summaries recalculated after provider deletion [(#10172)](https://github.com/prowler-cloud/prowler/pull/10172)
|
- Tenant compliance summaries recalculated after provider deletion [(#10172)](https://github.com/prowler-cloud/prowler/pull/10172)
|
||||||
- Security Hub export retries transient replica conflicts without failing integrations [(#10144)](https://github.com/prowler-cloud/prowler/pull/10144)
|
- Security Hub export retries transient replica conflicts without failing integrations [(#10144)](https://github.com/prowler-cloud/prowler/pull/10144)
|
||||||
|
- Cloudflare provider secrets now reject API key format in `api_token` and non-key values in `api_key` credentials [(#10195)](https://github.com/prowler-cloud/prowler/pull/10195)
|
||||||
|
|
||||||
### 🔐 Security
|
### 🔐 Security
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import pytest
|
|||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from api.v1.serializer_utils.integrations import S3ConfigSerializer
|
from api.v1.serializer_utils.integrations import S3ConfigSerializer
|
||||||
from api.v1.serializers import ImageProviderSecret
|
from api.v1.serializers import (
|
||||||
|
CloudflareApiKeyProviderSecret,
|
||||||
|
CloudflareTokenProviderSecret,
|
||||||
|
ImageProviderSecret,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestS3ConfigSerializer:
|
class TestS3ConfigSerializer:
|
||||||
@@ -133,3 +137,39 @@ class TestImageProviderSecret:
|
|||||||
serializer = ImageProviderSecret(data={"registry_password": "pass"})
|
serializer = ImageProviderSecret(data={"registry_password": "pass"})
|
||||||
assert not serializer.is_valid()
|
assert not serializer.is_valid()
|
||||||
assert "non_field_errors" in serializer.errors
|
assert "non_field_errors" in serializer.errors
|
||||||
|
|
||||||
|
|
||||||
|
class TestCloudflareProviderSecret:
|
||||||
|
"""Test cases for Cloudflare provider credential formats."""
|
||||||
|
|
||||||
|
def test_valid_api_token(self):
|
||||||
|
serializer = CloudflareTokenProviderSecret(
|
||||||
|
data={"api_token": "Sn3lZJTBX6kkg7OdcBUAxOO963GEIyGQqnFTOFYY"}
|
||||||
|
)
|
||||||
|
assert serializer.is_valid(), serializer.errors
|
||||||
|
|
||||||
|
def test_invalid_api_token_with_api_key_format(self):
|
||||||
|
serializer = CloudflareTokenProviderSecret(
|
||||||
|
data={"api_token": "144c9defac04969c7bfad8efaa8ea194"}
|
||||||
|
)
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert "api_token" in serializer.errors
|
||||||
|
|
||||||
|
def test_valid_api_key_and_email(self):
|
||||||
|
serializer = CloudflareApiKeyProviderSecret(
|
||||||
|
data={
|
||||||
|
"api_key": "144c9defac04969c7bfad8efaa8ea194",
|
||||||
|
"api_email": "user@example.com",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert serializer.is_valid(), serializer.errors
|
||||||
|
|
||||||
|
def test_invalid_api_key_with_token_format(self):
|
||||||
|
serializer = CloudflareApiKeyProviderSecret(
|
||||||
|
data={
|
||||||
|
"api_key": "Sn3lZJTBX6kkg7OdcBUAxOO963GEIyGQqnFTOFYY",
|
||||||
|
"api_email": "user@example.com",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert "api_key" in serializer.errors
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -1704,6 +1705,15 @@ class OracleCloudProviderSecret(serializers.Serializer):
|
|||||||
class CloudflareTokenProviderSecret(serializers.Serializer):
|
class CloudflareTokenProviderSecret(serializers.Serializer):
|
||||||
api_token = serializers.CharField()
|
api_token = serializers.CharField()
|
||||||
|
|
||||||
|
def validate_api_token(self, value: str) -> str:
|
||||||
|
# Cloudflare Global API Key is 32 hex chars; reject it in token field.
|
||||||
|
if re.fullmatch(r"[a-fA-F0-9]{32}", (value or "").strip()):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"This value matches Cloudflare API Key format. "
|
||||||
|
"Use 'api_key' and 'api_email' instead."
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
resource_name = "provider-secrets"
|
resource_name = "provider-secrets"
|
||||||
|
|
||||||
@@ -1712,6 +1722,14 @@ class CloudflareApiKeyProviderSecret(serializers.Serializer):
|
|||||||
api_key = serializers.CharField()
|
api_key = serializers.CharField()
|
||||||
api_email = serializers.EmailField()
|
api_email = serializers.EmailField()
|
||||||
|
|
||||||
|
def validate_api_key(self, value: str) -> str:
|
||||||
|
if not re.fullmatch(r"[a-fA-F0-9]{32}", (value or "").strip()):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Invalid Cloudflare API Key format. "
|
||||||
|
"Use a 32-character hexadecimal Global API Key."
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
resource_name = "provider-secrets"
|
resource_name = "provider-secrets"
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
|||||||
### 🐞 Fixed
|
### 🐞 Fixed
|
||||||
|
|
||||||
- Findings Severity Over Time chart on Overview not responding to provider and account filters, and chart clipping at Y-axis maximum values [(#10103)](https://github.com/prowler-cloud/prowler/pull/10103)
|
- Findings Severity Over Time chart on Overview not responding to provider and account filters, and chart clipping at Y-axis maximum values [(#10103)](https://github.com/prowler-cloud/prowler/pull/10103)
|
||||||
|
- Cloudflare credentials form now blocks API key values in `api_token` and token-like values in `api_key` [(#10195)](https://github.com/prowler-cloud/prowler/pull/10195)
|
||||||
|
|
||||||
### 🔐 Security
|
### 🔐 Security
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
|
|||||||
|
|
||||||
import { PROVIDER_TYPES, ProviderType } from "./providers";
|
import { PROVIDER_TYPES, ProviderType } from "./providers";
|
||||||
|
|
||||||
|
const CLOUDFLARE_GLOBAL_API_KEY_REGEX = /^[a-fA-F0-9]{32}$/;
|
||||||
|
|
||||||
export const addRoleFormSchema = z.object({
|
export const addRoleFormSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
manage_users: z.boolean().default(false),
|
manage_users: z.boolean().default(false),
|
||||||
@@ -379,6 +381,15 @@ export const addCredentialsFormSchema = (
|
|||||||
message: "API Token is required",
|
message: "API Token is required",
|
||||||
path: [ProviderCredentialFields.CLOUDFLARE_API_TOKEN],
|
path: [ProviderCredentialFields.CLOUDFLARE_API_TOKEN],
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
CLOUDFLARE_GLOBAL_API_KEY_REGEX.test(apiToken.trim())
|
||||||
|
) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message:
|
||||||
|
"This looks like an API Key. Use API Token credentials instead.",
|
||||||
|
path: [ProviderCredentialFields.CLOUDFLARE_API_TOKEN],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (via === "api_key") {
|
} else if (via === "api_key") {
|
||||||
const apiKey = data[ProviderCredentialFields.CLOUDFLARE_API_KEY];
|
const apiKey = data[ProviderCredentialFields.CLOUDFLARE_API_KEY];
|
||||||
@@ -389,6 +400,15 @@ export const addCredentialsFormSchema = (
|
|||||||
message: "API Key is required",
|
message: "API Key is required",
|
||||||
path: [ProviderCredentialFields.CLOUDFLARE_API_KEY],
|
path: [ProviderCredentialFields.CLOUDFLARE_API_KEY],
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
!CLOUDFLARE_GLOBAL_API_KEY_REGEX.test(apiKey.trim())
|
||||||
|
) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message:
|
||||||
|
"API Key must be a 32-character hexadecimal Cloudflare Global API Key.",
|
||||||
|
path: [ProviderCredentialFields.CLOUDFLARE_API_KEY],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!apiEmail || apiEmail.trim() === "") {
|
if (!apiEmail || apiEmail.trim() === "") {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
|
|||||||
Reference in New Issue
Block a user