diff --git a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.integration.test.tsx b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.integration.test.tsx index f7300ec5fd..f1491885b8 100644 --- a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.integration.test.tsx +++ b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.integration.test.tsx @@ -78,13 +78,13 @@ describe("AzureCertificateCredentialsForm browser flow", () => { view.getByRole("link", { name: "Deploy to Azure" }).element(), ).toHaveAttribute( "href", - "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fdocs.prowler.com%2Fassets%2Ftemplates%2Fazure%2Fprowler-scan.json", + "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fprowler-cloud%2Fprowler%2Fmaster%2Fpermissions%2Ftemplates%2Fazure%2Fbicep%2Fprowler-scan.json", ); expect( view.getByRole("link", { name: "Open template" }).element(), ).toHaveAttribute( "href", - "https://docs.prowler.com/assets/templates/azure/prowler-scan.json", + "https://raw.githubusercontent.com/prowler-cloud/prowler/master/permissions/templates/azure/bicep/prowler-scan.json", ); await view.getByRole("button", { name: "Generate certificate" }).click(); diff --git a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.test.tsx b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.test.tsx index 06930bafa2..ec06a5c2d7 100644 --- a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.test.tsx +++ b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.test.tsx @@ -36,7 +36,7 @@ describe("AzureCertificateCredentialsForm", () => { expect(link).toHaveAttribute("rel", "noopener noreferrer"); expect(link).toHaveAttribute( "href", - "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fdocs.prowler.com%2Fassets%2Ftemplates%2Fazure%2Fprowler-scan.json", + "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fprowler-cloud%2Fprowler%2Fmaster%2Fpermissions%2Ftemplates%2Fazure%2Fbicep%2Fprowler-scan.json", ); }); @@ -48,7 +48,7 @@ describe("AzureCertificateCredentialsForm", () => { const link = screen.getByRole("link", { name: "Open template" }); expect(link).toHaveAttribute( "href", - "https://docs.prowler.com/assets/templates/azure/prowler-scan.json", + "https://raw.githubusercontent.com/prowler-cloud/prowler/master/permissions/templates/azure/bicep/prowler-scan.json", ); expect(link).toHaveAttribute("target", "_blank"); expect(link).toHaveAttribute("rel", "noopener noreferrer"); @@ -83,7 +83,7 @@ describe("AzureCertificateCredentialsForm", () => { }, { element: screen.getByRole("link", { name: "Open template" }), - href: "https://docs.prowler.com/assets/templates/azure/prowler-scan.json", + href: "https://raw.githubusercontent.com/prowler-cloud/prowler/master/permissions/templates/azure/bicep/prowler-scan.json", }, ]; diff --git a/ui/lib/azure-cert-generator.ts b/ui/lib/azure-cert-generator.ts index 75ad3304dd..d560ffb322 100644 --- a/ui/lib/azure-cert-generator.ts +++ b/ui/lib/azure-cert-generator.ts @@ -20,8 +20,10 @@ import "reflect-metadata"; import { + BasicConstraintsExtension, cryptoProvider, - Extension, + KeyUsageFlags, + KeyUsagesExtension, X509CertificateGenerator, } from "@peculiar/x509"; @@ -131,7 +133,13 @@ export async function generateProwlerCertificate( hash: "SHA-256", }, keys: keyPair, - extensions: [] as Extension[], + // Match `openssl x509 -req` defaults: mark the leaf as end-entity + // (`cA=false`) and declare `digitalSignature` so audit tooling and + // strict CA validators don't flag the certificate as unusual. + extensions: [ + new BasicConstraintsExtension(false, undefined, true), + new KeyUsagesExtension(KeyUsageFlags.digitalSignature, true), + ], }); const certPem = cert.toString("pem"); diff --git a/ui/lib/error-mappings.ts b/ui/lib/error-mappings.ts index ff15c790c6..566d2ebbf2 100644 --- a/ui/lib/error-mappings.ts +++ b/ui/lib/error-mappings.ts @@ -16,6 +16,8 @@ export const PROVIDER_CREDENTIALS_ERROR_MAPPING: Record = { [ErrorPointers.AWS_SESSION_TOKEN]: ProviderCredentialFields.AWS_SESSION_TOKEN, [ErrorPointers.CLIENT_ID]: ProviderCredentialFields.CLIENT_ID, [ErrorPointers.CLIENT_SECRET]: ProviderCredentialFields.CLIENT_SECRET, + [ErrorPointers.CERTIFICATE_CONTENT]: + ProviderCredentialFields.CERTIFICATE_CONTENT, [ErrorPointers.USER]: ProviderCredentialFields.USER, [ErrorPointers.PASSWORD]: ProviderCredentialFields.PASSWORD, [ErrorPointers.TENANT_ID]: ProviderCredentialFields.TENANT_ID, diff --git a/ui/lib/external-urls.test.ts b/ui/lib/external-urls.test.ts index 48a8e72f17..bd9eef6478 100644 --- a/ui/lib/external-urls.test.ts +++ b/ui/lib/external-urls.test.ts @@ -294,10 +294,10 @@ describe("getAzureDeploymentQuickLink", () => { // Then expect(PROWLER_AZURE_ARM_TEMPLATE_URL).toBe( - "https://docs.prowler.com/assets/templates/azure/prowler-scan.json", + "https://raw.githubusercontent.com/prowler-cloud/prowler/master/permissions/templates/azure/bicep/prowler-scan.json", ); expect(url).toBe( - "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fdocs.prowler.com%2Fassets%2Ftemplates%2Fazure%2Fprowler-scan.json", + "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fprowler-cloud%2Fprowler%2Fmaster%2Fpermissions%2Ftemplates%2Fazure%2Fbicep%2Fprowler-scan.json", ); expect(url).not.toContain("localhost"); expect(url).not.toContain("prowler-cloud-public.s3"); diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts index 268fcbf8c8..35b96fbf2e 100644 --- a/ui/lib/external-urls.ts +++ b/ui/lib/external-urls.ts @@ -40,8 +40,12 @@ export const getAttackPathHubUrl = (queryId: string): string => export const PROWLER_CF_TEMPLATE_URL = "https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/aws/cloudformation/prowler-scan-role.yml"; +// Stopgap: point the Azure Portal at the raw template on GitHub until the +// docs deploy publishes the file under `docs.prowler.com/assets/...`. +// The Portal fetches this URL over HTTPS, so `raw.githubusercontent.com` +// works exactly the same for the Deploy-to-Azure flow. export const PROWLER_AZURE_ARM_TEMPLATE_URL = - "https://docs.prowler.com/assets/templates/azure/prowler-scan.json"; + "https://raw.githubusercontent.com/prowler-cloud/prowler/master/permissions/templates/azure/bicep/prowler-scan.json"; // Prowler Cloud billing/subscription management page. export const BILLING_URL = "https://cloud.prowler.com/billing"; diff --git a/ui/tests/providers/providers-page.ts b/ui/tests/providers/providers-page.ts index 38ce3ab1ea..17504c219a 100644 --- a/ui/tests/providers/providers-page.ts +++ b/ui/tests/providers/providers-page.ts @@ -1640,7 +1640,7 @@ export class ProvidersPage extends BasePage { }), ).toHaveAttribute( "href", - "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fdocs.prowler.com%2Fassets%2Ftemplates%2Fazure%2Fprowler-scan.json", + "https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fprowler-cloud%2Fprowler%2Fmaster%2Fpermissions%2Ftemplates%2Fazure%2Fbicep%2Fprowler-scan.json", ); await expect( this.page.getByRole("link", { @@ -1649,7 +1649,7 @@ export class ProvidersPage extends BasePage { }), ).toHaveAttribute( "href", - "https://docs.prowler.com/assets/templates/azure/prowler-scan.json", + "https://raw.githubusercontent.com/prowler-cloud/prowler/master/permissions/templates/azure/bicep/prowler-scan.json", ); await expect( this.page.getByRole("button", { name: "Generate certificate" }), diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 0c202a2280..a7e654baac 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -7,6 +7,16 @@ import { MAX_SAML_ADDITIONAL_EMAIL_DOMAINS } from "@/types/saml"; import { PROVIDER_TYPES, ProviderType } from "./providers"; +// Matches the API's `_MAX_CERTIFICATE_CONTENT_LENGTH` in +// `api/src/backend/api/v1/serializers.py`, i.e. base64 of the SDK's 50 KiB +// `_MAX_CERTIFICATE_BUNDLE_BYTES` cap. Reject oversized certificate +// content client-side so the user sees the error inline before a +// round-trip that the API would 400 with the same message. +export const MAX_CERTIFICATE_CONTENT_LENGTH = 68268; + +export const CERTIFICATE_CONTENT_MAX_SIZE_ERROR = + "Certificate content exceeds the maximum size."; + export const KUBECONFIG_UNSUPPORTED_COMMAND_AUTHENTICATION_ERROR = "Kubernetes kubeconfig command-based authentication is not supported in Prowler Cloud for security reasons."; @@ -241,6 +251,10 @@ export const addCredentialsFormSchema = ( [ProviderCredentialFields.CLIENT_SECRET]: z.string().optional(), [ProviderCredentialFields.CERTIFICATE_CONTENT]: z .string() + .max( + MAX_CERTIFICATE_CONTENT_LENGTH, + CERTIFICATE_CONTENT_MAX_SIZE_ERROR, + ) .optional(), [ProviderCredentialFields.TENANT_ID]: z.guid({ error: "Tenant ID must be a valid GUID", @@ -284,6 +298,10 @@ export const addCredentialsFormSchema = ( .optional(), [ProviderCredentialFields.CERTIFICATE_CONTENT]: z .string() + .max( + MAX_CERTIFICATE_CONTENT_LENGTH, + CERTIFICATE_CONTENT_MAX_SIZE_ERROR, + ) .optional(), [ProviderCredentialFields.TENANT_ID]: z .string()