mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
refactor(api): reuse SDK certificate validator and fix schema drift
Delete the API copy of `validate_certificate_bundle` and re-export the SDK one. The local copy diverged: it missed `UnsupportedAlgorithm` on PKCS#12 loading, `TypeError` on password-protected PEM keys, `DSA` and `ENCRYPTED`/`OPENSSH` PEM label prefixes, and the leaf-first normalization the SDK now depends on for azure-identity's thumbprint. A single source keeps future SDK fixes from silently skipping the API. Log the caught exception in `AzureProviderSecret.validate_certificate_content` before raising the client-facing ValidationError so root-causing a bundle parse failure doesn't need a rerun with debug on. Drop `additionalProperties: false` from the two new Azure oneOf variants in the ProviderSecretField schema. No other credential variant sets it, so codegen'd clients that enforced the flag would reject payloads the DRF serializer accepts.
This commit is contained in:
@@ -21307,7 +21307,6 @@ components:
|
||||
- client_id
|
||||
- client_secret
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: Azure Certificate Credentials
|
||||
properties:
|
||||
@@ -21327,7 +21326,6 @@ components:
|
||||
- client_id
|
||||
- certificate_content
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: M365 Static Credentials
|
||||
properties:
|
||||
@@ -23458,7 +23456,6 @@ components:
|
||||
- client_id
|
||||
- client_secret
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: Azure Certificate Credentials
|
||||
properties:
|
||||
@@ -23478,7 +23475,6 @@ components:
|
||||
- client_id
|
||||
- certificate_content
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: M365 Static Credentials
|
||||
properties:
|
||||
@@ -23925,7 +23921,6 @@ components:
|
||||
- client_id
|
||||
- client_secret
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: Azure Certificate Credentials
|
||||
properties:
|
||||
@@ -23945,7 +23940,6 @@ components:
|
||||
- client_id
|
||||
- certificate_content
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: M365 Static Credentials
|
||||
properties:
|
||||
@@ -24414,7 +24408,6 @@ components:
|
||||
- client_id
|
||||
- client_secret
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: Azure Certificate Credentials
|
||||
properties:
|
||||
@@ -24434,7 +24427,6 @@ components:
|
||||
- client_id
|
||||
- certificate_content
|
||||
- tenant_id
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
title: M365 Static Credentials
|
||||
properties:
|
||||
@@ -27073,7 +27065,6 @@ components:
|
||||
required:
|
||||
- type
|
||||
- id
|
||||
additionalProperties: false
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
|
||||
@@ -402,10 +402,6 @@ class TestProviderSecretFieldSchema:
|
||||
"client_secret",
|
||||
"tenant_id",
|
||||
}
|
||||
assert (
|
||||
azure_schemas["Azure Client Secret Credentials"]["additionalProperties"]
|
||||
is False
|
||||
)
|
||||
assert azure_schemas["Azure Certificate Credentials"]["required"] == [
|
||||
"client_id",
|
||||
"certificate_content",
|
||||
@@ -416,10 +412,6 @@ class TestProviderSecretFieldSchema:
|
||||
"certificate_content",
|
||||
"tenant_id",
|
||||
}
|
||||
assert (
|
||||
azure_schemas["Azure Certificate Credentials"]["additionalProperties"]
|
||||
is False
|
||||
)
|
||||
|
||||
def test_oraclecloud_schema_includes_legacy_region_field(self):
|
||||
schema = ProviderSecretField._spectacular_annotation["field"]
|
||||
|
||||
@@ -96,7 +96,6 @@ from rest_framework_json_api import serializers
|
||||
},
|
||||
},
|
||||
"required": ["client_id", "client_secret", "tenant_id"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -117,7 +116,6 @@ from rest_framework_json_api import serializers
|
||||
},
|
||||
},
|
||||
"required": ["client_id", "certificate_content", "tenant_id"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
@@ -1812,6 +1812,9 @@ class AzureProviderSecret(serializers.Serializer):
|
||||
certificate_data = base64.b64decode(certificate_content, validate=True)
|
||||
validate_certificate_bundle(certificate_data)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}"
|
||||
)
|
||||
# Field validators are invoked per-field; DRF already knows
|
||||
# this error belongs to `certificate_content` and will nest
|
||||
# the message under that key. Raising a dict here would
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import ipaddress
|
||||
import re
|
||||
import socket
|
||||
import string
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# Re-exported so the SDK stays the single source of truth for bundle parsing:
|
||||
# it covers PKCS#12 UnsupportedAlgorithm, encrypted PEM keys (TypeError), the
|
||||
# full private-key PEM label set, and leaf-first normalization for
|
||||
# azure-identity's thumbprint. A local copy silently drifted before.
|
||||
from prowler.providers.azure.lib.certificate import ( # noqa: F401
|
||||
validate_certificate_bundle,
|
||||
)
|
||||
|
||||
LIGHTHOUSE_OPENAI_COMPATIBLE_ALLOWED_SCHEMES = frozenset({"https"})
|
||||
LIGHTHOUSE_NAT64_WELL_KNOWN_PREFIX = ipaddress.IPv6Network("64:ff9b::/96")
|
||||
LIGHTHOUSE_BLOCKED_METADATA_HOSTS = frozenset(
|
||||
@@ -25,45 +28,6 @@ LIGHTHOUSE_BLOCKED_METADATA_HOSTS = frozenset(
|
||||
)
|
||||
|
||||
|
||||
def validate_certificate_bundle(certificate_data: bytes) -> None:
|
||||
"""Validate that certificate data contains a matching certificate and key."""
|
||||
try:
|
||||
private_key, certificate, _ = pkcs12.load_key_and_certificates(
|
||||
certificate_data, None, default_backend()
|
||||
)
|
||||
except ValueError:
|
||||
certificate_match = re.search(
|
||||
rb"-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----",
|
||||
certificate_data,
|
||||
re.DOTALL,
|
||||
)
|
||||
private_key_match = re.search(
|
||||
rb"-----BEGIN (?:RSA |EC )?PRIVATE KEY-----.*?-----END (?:RSA |EC )?PRIVATE KEY-----",
|
||||
certificate_data,
|
||||
re.DOTALL,
|
||||
)
|
||||
if not certificate_match or not private_key_match:
|
||||
raise ValueError(
|
||||
"the payload must contain a certificate and its private key"
|
||||
)
|
||||
certificate = x509.load_pem_x509_certificate(
|
||||
certificate_match.group(), default_backend()
|
||||
)
|
||||
private_key = serialization.load_pem_private_key(
|
||||
private_key_match.group(), password=None, backend=default_backend()
|
||||
)
|
||||
|
||||
if certificate is None or private_key is None:
|
||||
raise ValueError("the payload must contain a certificate and its private key")
|
||||
|
||||
encoding = serialization.Encoding.DER
|
||||
public_format = serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
if certificate.public_key().public_bytes(
|
||||
encoding, public_format
|
||||
) != private_key.public_key().public_bytes(encoding, public_format):
|
||||
raise ValueError("the certificate does not match the private key")
|
||||
|
||||
|
||||
def _normalize_hostname(hostname: str) -> str:
|
||||
return hostname.rstrip(".").lower()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user