mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-26 13:24:20 +00:00
fix(api): scope the secret reassignment to Azure certificate_content only
This commit is contained in:
1 parent
721a80921b
commit
e01ce68f10
2 files changed
+139
-8
No files matched your search
@@ -3591,6 +3591,112 @@ current-context: test-context
|
||||
provider_secret.refresh_from_db()
|
||||
assert provider_secret.secret["certificate_content"] == clean_b64
|
||||
|
||||
def test_provider_secrets_create_m365_preserves_whitespace_in_client_secret(
|
||||
self,
|
||||
authenticated_client,
|
||||
m365_provider,
|
||||
):
|
||||
# DRF `CharField.trim_whitespace` (the default) would silently strip
|
||||
# surrounding whitespace off opaque credentials if the outer write
|
||||
# path blindly persisted the validated dict. Legitimate M365 client
|
||||
# secrets can contain leading/trailing whitespace, so the raw
|
||||
# submitted value must survive the round-trip unchanged.
|
||||
whitespace_secret = " M365-Secret-With-Padding "
|
||||
data = {
|
||||
"data": {
|
||||
"type": "provider-secrets",
|
||||
"attributes": {
|
||||
"name": "M365 Secret",
|
||||
"secret_type": ProviderSecret.TypeChoices.STATIC,
|
||||
"secret": {
|
||||
"client_id": "87654321-4321-4321-4321-210987654321",
|
||||
"tenant_id": "12345678-1234-1234-1234-123456789012",
|
||||
"client_secret": whitespace_secret,
|
||||
},
|
||||
},
|
||||
"relationships": {
|
||||
"provider": {
|
||||
"data": {"type": "providers", "id": str(m365_provider.id)}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
response = authenticated_client.post(
|
||||
reverse("providersecret-list"),
|
||||
data=json.dumps(data),
|
||||
content_type="application/vnd.api+json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_201_CREATED, response.content
|
||||
provider_secret = ProviderSecret.objects.get(id=response.json()["data"]["id"])
|
||||
assert provider_secret.secret["client_secret"] == whitespace_secret
|
||||
|
||||
def test_provider_secrets_update_image_preserves_whitespace_in_registry_password(
|
||||
self,
|
||||
authenticated_client,
|
||||
image_provider,
|
||||
):
|
||||
# Container-registry passwords can be opaque high-entropy strings
|
||||
# generated by tooling that includes trailing whitespace as part of
|
||||
# the credential. The outer write path must keep the submitted
|
||||
# value verbatim so the registry still accepts it after a PATCH.
|
||||
create_response = authenticated_client.post(
|
||||
reverse("providersecret-list"),
|
||||
data=json.dumps(
|
||||
{
|
||||
"data": {
|
||||
"type": "provider-secrets",
|
||||
"attributes": {
|
||||
"name": "Registry Secret",
|
||||
"secret_type": ProviderSecret.TypeChoices.STATIC,
|
||||
"secret": {
|
||||
"registry_username": "prowler",
|
||||
"registry_password": "initial-password",
|
||||
},
|
||||
},
|
||||
"relationships": {
|
||||
"provider": {
|
||||
"data": {
|
||||
"type": "providers",
|
||||
"id": str(image_provider.id),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
content_type="application/vnd.api+json",
|
||||
)
|
||||
assert create_response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
whitespace_password = " registry-password-with-padding "
|
||||
provider_secret = ProviderSecret.objects.get(
|
||||
id=create_response.json()["data"]["id"]
|
||||
)
|
||||
response = authenticated_client.patch(
|
||||
reverse("providersecret-detail", kwargs={"pk": provider_secret.id}),
|
||||
data=json.dumps(
|
||||
{
|
||||
"data": {
|
||||
"type": "provider-secrets",
|
||||
"id": str(provider_secret.id),
|
||||
"attributes": {
|
||||
"secret": {
|
||||
"registry_username": "prowler",
|
||||
"registry_password": whitespace_password,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
content_type="application/vnd.api+json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK, response.content
|
||||
provider_secret.refresh_from_db()
|
||||
assert provider_secret.secret["registry_password"] == whitespace_password
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attributes, error_code, error_pointer",
|
||||
(
|
||||
|
||||
@@ -2163,11 +2163,24 @@ class ProviderSecretCreateSerializer(
|
||||
validated_secret = self.validate_secret_based_on_provider(
|
||||
provider.provider, secret_type, secret
|
||||
)
|
||||
# Persist the per-provider validator's normalized output (e.g.
|
||||
# `AzureProviderSecret` strips whitespace inside `certificate_content`).
|
||||
# Previously this reassignment only happened for OCI, so every
|
||||
# per-provider validator's normalization was discarded on write.
|
||||
validated_attrs["secret"] = validated_secret
|
||||
# OCI persists the full validated dict on purpose (its serializer
|
||||
# already performs the sanitization it wants). For Azure, only the
|
||||
# `certificate_content` field must be replaced with the normalized
|
||||
# (whitespace-stripped) value; the rest of the dict is left as the
|
||||
# caller submitted it so opaque credentials elsewhere in the payload
|
||||
# keep whatever whitespace they carried. Every other provider stays
|
||||
# on the outer JSONField's raw dict — DRF's `CharField.trim_whitespace`
|
||||
# would otherwise silently mutate opaque tokens/passwords.
|
||||
if provider.provider == Provider.ProviderChoices.ORACLECLOUD.value:
|
||||
validated_attrs["secret"] = validated_secret
|
||||
elif (
|
||||
provider.provider == Provider.ProviderChoices.AZURE.value
|
||||
and validated_secret.get("certificate_content")
|
||||
):
|
||||
validated_attrs["secret"] = {
|
||||
**secret,
|
||||
"certificate_content": validated_secret["certificate_content"],
|
||||
}
|
||||
return validated_attrs
|
||||
|
||||
|
||||
@@ -2202,9 +2215,21 @@ class ProviderSecretUpdateSerializer(BaseWriteProviderSecretSerializer):
|
||||
validated_secret = self.validate_secret_based_on_provider(
|
||||
provider.provider, secret_type, secret
|
||||
)
|
||||
# Persist the per-provider validator's normalized output — same
|
||||
# rationale as `ProviderSecretCreateSerializer.validate`.
|
||||
validated_attrs["secret"] = validated_secret
|
||||
# Same targeted persistence as `ProviderSecretCreateSerializer.validate`:
|
||||
# OCI keeps its full validated dict, Azure replaces only
|
||||
# `certificate_content`, and every other provider stays on the raw
|
||||
# submitted dict so opaque credentials do not get their whitespace
|
||||
# silently trimmed.
|
||||
if provider.provider == Provider.ProviderChoices.ORACLECLOUD.value:
|
||||
validated_attrs["secret"] = validated_secret
|
||||
elif (
|
||||
provider.provider == Provider.ProviderChoices.AZURE.value
|
||||
and validated_secret.get("certificate_content")
|
||||
):
|
||||
validated_attrs["secret"] = {
|
||||
**secret,
|
||||
"certificate_content": validated_secret["certificate_content"],
|
||||
}
|
||||
return validated_attrs
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user