mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-19 18:04:17 +00:00
feat(azure): add Deploy-to-Azure Bicep template and certificate auth
This commit is contained in:
1 parent
d41b2eaa0f
commit
ab738e8a4e
34 files changed
+1953
-93
No files matched your search
@@ -5,6 +5,7 @@ from api.v1.serializer_utils.integrations import (
|
||||
)
|
||||
from api.v1.serializer_utils.providers import ProviderSecretField
|
||||
from api.v1.serializers import (
|
||||
AzureProviderSecret,
|
||||
ImageProviderSecret,
|
||||
IntegrationSerializer,
|
||||
IntegrationUpdateSerializer,
|
||||
@@ -198,6 +199,79 @@ class TestImageProviderSecret:
|
||||
assert "non_field_errors" in serializer.errors
|
||||
|
||||
|
||||
class TestAzureProviderSecret:
|
||||
"""Coverage for the Azure provider secret serializer, including the
|
||||
certificate authentication path added for the Deploy-to-Azure quick-start
|
||||
(PROWLER-2378)."""
|
||||
|
||||
BASE = {
|
||||
"client_id": "87654321-4321-4321-4321-210987654321",
|
||||
"tenant_id": "12345678-1234-1234-1234-123456789012",
|
||||
}
|
||||
# Valid base64 of a tiny DER-shaped payload; the serializer only checks
|
||||
# that the string decodes as base64, not that it parses as a real cert.
|
||||
CERT_CONTENT_B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA"
|
||||
|
||||
def test_accepts_client_secret_only(self):
|
||||
# Backwards-compatibility guard: rows saved by the previous serializer
|
||||
# only carry `client_secret` and must keep round-tripping cleanly.
|
||||
serializer = AzureProviderSecret(
|
||||
data={**self.BASE, "client_secret": "fake-client-secret"}
|
||||
)
|
||||
assert serializer.is_valid(), serializer.errors
|
||||
assert serializer.validated_data["client_secret"] == "fake-client-secret"
|
||||
assert "certificate_content" not in serializer.validated_data
|
||||
|
||||
def test_accepts_certificate_content_only(self):
|
||||
serializer = AzureProviderSecret(
|
||||
data={**self.BASE, "certificate_content": self.CERT_CONTENT_B64}
|
||||
)
|
||||
assert serializer.is_valid(), serializer.errors
|
||||
assert serializer.validated_data["certificate_content"] == self.CERT_CONTENT_B64
|
||||
assert "client_secret" not in serializer.validated_data
|
||||
|
||||
def test_rejects_both_client_secret_and_certificate_content(self):
|
||||
# Mutually exclusive: the backend must reject a payload carrying both
|
||||
# so the ambiguity never reaches the SDK where `certificate_content`
|
||||
# silently wins.
|
||||
serializer = AzureProviderSecret(
|
||||
data={
|
||||
**self.BASE,
|
||||
"client_secret": "fake-client-secret",
|
||||
"certificate_content": self.CERT_CONTENT_B64,
|
||||
}
|
||||
)
|
||||
assert not serializer.is_valid()
|
||||
assert "non_field_errors" in serializer.errors
|
||||
|
||||
def test_rejects_missing_secret_and_certificate(self):
|
||||
# At least one credential material must be provided.
|
||||
serializer = AzureProviderSecret(data=self.BASE)
|
||||
assert not serializer.is_valid()
|
||||
assert "non_field_errors" in serializer.errors
|
||||
|
||||
def test_rejects_non_base64_certificate_content(self):
|
||||
# `validate_certificate_content` short-circuits obvious garbage before
|
||||
# it reaches the SDK, which would otherwise fail deep in azure-identity.
|
||||
serializer = AzureProviderSecret(
|
||||
data={**self.BASE, "certificate_content": "not!valid@base64$$"}
|
||||
)
|
||||
assert not serializer.is_valid()
|
||||
assert "certificate_content" in serializer.errors
|
||||
|
||||
def test_rejects_empty_strings_for_both(self):
|
||||
# DRF's CharField rejects "" at field-level before `validate()` runs.
|
||||
# The errors surface per-field rather than as non_field_errors, but
|
||||
# the important thing is that empty strings NEVER get persisted as
|
||||
# credentials.
|
||||
serializer = AzureProviderSecret(
|
||||
data={**self.BASE, "client_secret": "", "certificate_content": ""}
|
||||
)
|
||||
assert not serializer.is_valid()
|
||||
assert "client_secret" in serializer.errors
|
||||
assert "certificate_content" in serializer.errors
|
||||
|
||||
|
||||
class TestOracleCloudProviderSecret:
|
||||
def valid_secret(self, **overrides):
|
||||
secret = {
|
||||
|
||||
@@ -1787,8 +1787,36 @@ class AwsProviderSecret(serializers.Serializer):
|
||||
|
||||
class AzureProviderSecret(serializers.Serializer):
|
||||
client_id = serializers.CharField()
|
||||
client_secret = serializers.CharField()
|
||||
client_secret = serializers.CharField(required=False)
|
||||
tenant_id = serializers.CharField()
|
||||
certificate_content = serializers.CharField(required=False)
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs.get("client_secret") and attrs.get("certificate_content"):
|
||||
raise serializers.ValidationError(
|
||||
"You cannot provide both client_secret and certificate_content."
|
||||
)
|
||||
if not attrs.get("client_secret") and not attrs.get("certificate_content"):
|
||||
raise serializers.ValidationError(
|
||||
"You must provide either client_secret or certificate_content."
|
||||
)
|
||||
return super().validate(attrs)
|
||||
|
||||
def validate_certificate_content(self, certificate_content):
|
||||
"""Validate that Azure certificate content is valid base64 encoded data."""
|
||||
if certificate_content:
|
||||
try:
|
||||
base64.b64decode(certificate_content, validate=True)
|
||||
except Exception as e:
|
||||
raise ValidationError(
|
||||
{
|
||||
"certificate_content": [
|
||||
f"The provided certificate content is not valid base64 encoded data: {str(e)}"
|
||||
]
|
||||
},
|
||||
code="azure-certificate-content",
|
||||
)
|
||||
return certificate_content
|
||||
|
||||
class Meta:
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
Reference in new issue
Block a user