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 37e1594d73..f7300ec5fd 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 @@ -17,6 +17,57 @@ const Harness = () => { }; describe("AzureCertificateCredentialsForm browser flow", () => { + it("groups the six setup steps into three named phases before the form", async () => { + // When + const view = await render(); + + // Then + const phaseHeadings = [ + view.getByRole("heading", { name: "Create the application" }).element(), + view.getByRole("heading", { name: "Configure access" }).element(), + view.getByRole("heading", { name: "Deploy and connect" }).element(), + ]; + const phases = phaseHeadings.map((heading) => heading.closest("section")); + + expect(phases.every((phase) => phase !== null)).toBe(true); + expect(phases.map((phase) => phase?.querySelectorAll("li").length)).toEqual( + [2, 2, 2], + ); + const openAzureLink = view + .getByRole("link", { name: "Open Azure" }) + .element(); + const enterpriseApplicationsLink = view + .getByRole("link", { name: "Enterprise applications" }) + .element(); + const deployToAzureLink = view + .getByRole("link", { name: "Deploy to Azure" }) + .element(); + const openTemplateLink = view + .getByRole("link", { name: "Open template" }) + .element(); + + expect(phases[0]).toContainElement(openAzureLink); + expect(phases[0]).toContainElement( + view.getByRole("button", { name: "Generate certificate" }).element(), + ); + expect(phases[1]).toContainElement(enterpriseApplicationsLink); + expect(phases[2]).toContainElement(deployToAzureLink); + expect(deployToAzureLink.closest("li")).toContainElement(openTemplateLink); + expect(openAzureLink).toHaveAttribute( + "href", + "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/CreateApplicationBlade", + ); + expect(enterpriseApplicationsLink).toHaveAttribute( + "href", + "https://portal.azure.com/#view/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/~/AppAppsPreview", + ); + const guidance = phaseHeadings[0].parentElement?.parentElement; + const tenantIdInput = view + .getByRole("textbox", { name: "Tenant ID" }) + .element(); + expect(guidance?.nextElementSibling).toContainElement(tenantIdInput); + }); + it("renders deployment guidance and keeps certificate generation functional", async () => { // When const view = await render(); @@ -45,6 +96,9 @@ describe("AzureCertificateCredentialsForm browser flow", () => { }, { timeout: 20_000 }, ); + expect(view.getByRole("status").element()).toHaveTextContent( + /Downloaded prowler-cert\.cer/, + ); const bundle = view .getByRole("textbox", { name: "Certificate and Private Key Bundle (Base64)", diff --git a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx index aaecac6d2b..c087e209f8 100644 --- a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx @@ -88,115 +88,146 @@ export const AzureCertificateCredentialsForm = ({ -
    -
  1. - - 1. Create an App Registration. Copy its Directory - (tenant) ID and Application (client) ID. - - -
  2. +
    +
    +

    + Create the application +

    +
      +
    1. +
      + + Register an Azure application, then copy its Directory + (tenant) ID and Application (client) ID. + + +
      +
    2. +
    3. +
      + + Generate the certificate. The private bundle is filled below + and submitted to Prowler only when you connect. + + +
      +
      + {isGeneratingCert && ( +

      + Generating certificate... +

      + )} + {generatorError && ( +

      + {generatorError} +

      + )} + {generatedThumbprint && ( +

      + Downloaded prowler-cert.cer. Thumbprint{" "} + {generatedThumbprint} +

      + )} +
      +
    4. +
    +
    -
  3. -
    - - 2. Generate the certificate bundle and upload the - public prowler-cert.cer file under Certificates & secrets. The - private bundle is filled below and is submitted to Prowler only - when you connect. - - -
    - {generatorError && ( -

    {generatorError}

    - )} - {generatedThumbprint && ( -

    - Downloaded prowler-cert.cer. Thumbprint{" "} - {generatedThumbprint} -

    - )} -
  4. - -
  5. - 3. Add Microsoft Graph application permissions: - AuditLog.Read.All, Directory.Read.All (or Domain.Read.All), and - Policy.Read.All. Then grant admin consent. -
  6. - -
  7. - - 4. Copy the Service Principal Object ID from{" "} - - - Enterprise applications - - . - -
  8. - -
  9. - -

    - To deploy the ARM JSON manually, use Azure Portal → Build your own - template in the editor.{" "} - -

    -
  10. + Enterprise applications + + . + +
+ -
  • - 6. Return to Prowler and connect. Paste the Tenant ID - and Application Client ID below; the generated certificate bundle is - already filled. -
  • - +
    +

    + Deploy and connect +

    +
      +
    1. +
      + + Deploy subscription RBAC with the Service Principal Object ID. + The template creates ProwlerRole and assigns Reader and + ProwlerRole; it does not create Entra resources. + + +
      +

      + Manual deployment: in Azure Portal, use Build your own template + in the editor.{" "} + +

      +
    2. +
    3. + Return to Prowler and complete the fields below. The generated + certificate bundle is already filled. +
    4. +
    +
    +