diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 011ed8ec9a..6f1482426f 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -4,10 +4,13 @@ All notable changes to the **Prowler UI** are documented in this file. ## [1.20.0] (Prowler v5.20.0 UNRELEASED) -### 🐞 Changed +### 🔄 Changed - Attack Paths: Improved error handling for server errors (5xx) and network failures with user-friendly messages instead of raw internal errors and layout changes. [(#10249)](https://github.com/prowler-cloud/prowler/pull/10249) - Refactor simple providers with new components and styles.[(#10259)](https://github.com/prowler-cloud/prowler/pull/10259) +- AWS Organizations onboarding now uses a clearer 3-step flow: deploy the ProwlerScan role in the management account via CloudFormation Stack, deploy to member accounts via StackSet with a copyable template URL, and confirm with the Role ARN [(#10274)](https://github.com/prowler-cloud/prowler/pull/10274) + +--- ## [1.19.1] (Prowler v5.19.1 UNRELEASED) diff --git a/ui/components/providers/organizations/org-setup-form.tsx b/ui/components/providers/organizations/org-setup-form.tsx index 0a62efedb7..3990ffb435 100644 --- a/ui/components/providers/organizations/org-setup-form.tsx +++ b/ui/components/providers/organizations/org-setup-form.tsx @@ -1,5 +1,6 @@ "use client"; +import { useClipboard } from "@heroui/use-clipboard"; import { zodResolver } from "@hookform/resolvers/zod"; import { Check, Copy, ExternalLink } from "lucide-react"; import { useSession } from "next-auth/react"; @@ -18,7 +19,11 @@ import { Button } from "@/components/shadcn/button/button"; import { Checkbox } from "@/components/shadcn/checkbox/checkbox"; import { TreeSpinner } from "@/components/shadcn/tree-view/tree-spinner"; import { Form } from "@/components/ui/form"; -import { getAWSCredentialsTemplateLinks } from "@/lib"; +import { + getAWSCredentialsTemplateLinks, + PROWLER_CF_TEMPLATE_URL, + STACKSET_CONSOLE_URL, +} from "@/lib"; import { ORG_SETUP_PHASE, OrgSetupPhase } from "@/types/organizations"; import { useOrgSetupSubmission } from "./hooks/use-org-setup-submission"; @@ -39,7 +44,7 @@ const orgSetupSchema = z.object({ .min(1, "Role ARN is required") .regex( /^arn:aws:iam::\d{12}:role\//, - "Must be a valid IAM Role ARN (e.g., arn:aws:iam::123456789012:role/ProwlerOrgRole)", + "Must be a valid IAM Role ARN (e.g., arn:aws:iam::123456789012:role/ProwlerScan)", ), stackSetDeployed: z.boolean().refine((value) => value, { message: "You must confirm the StackSet deployment before continuing.", @@ -64,8 +69,13 @@ export function OrgSetupForm({ initialPhase = ORG_SETUP_PHASE.DETAILS, }: OrgSetupFormProps) { const { data: session } = useSession(); - const [isExternalIdCopied, setIsExternalIdCopied] = useState(false); const stackSetExternalId = session?.tenantId ?? ""; + const { copied: isExternalIdCopied, copy: copyExternalId } = useClipboard({ + timeout: 1500, + }); + const { copied: isTemplateUrlCopied, copy: copyTemplateUrl } = useClipboard({ + timeout: 1500, + }); const [setupPhase, setSetupPhase] = useState(initialPhase); const formId = "org-wizard-setup-form"; @@ -90,9 +100,10 @@ export function OrgSetupForm({ const awsOrgId = watch("awsOrgId") || ""; const isOrgIdValid = /^o-[a-z0-9]{10,32}$/.test(awsOrgId.trim()); - const stackSetQuickLink = - stackSetExternalId && - getAWSCredentialsTemplateLinks(stackSetExternalId).cloudformationQuickLink; + const templateLinks = stackSetExternalId + ? getAWSCredentialsTemplateLinks(stackSetExternalId) + : null; + const orgQuickLink = templateLinks?.cloudformationOrgQuickLink; const { apiError, setApiError, submitOrganizationSetup } = useOrgSetupSubmission({ @@ -260,35 +271,11 @@ export function OrgSetupForm({ {setupPhase === ORG_SETUP_PHASE.ACCESS && !isSubmitting && (
+ {/* External ID - shown first for both deployment steps */}

- 1) Launch the Prowler CloudFormation StackSet in your AWS - Console. -

- -
- -
-

- 2) Use the following Prowler External ID parameter in the - StackSet. + Use the following External ID when deploying + the CloudFormation Stack and StackSet.

@@ -302,15 +289,7 @@ export function OrgSetupForm({
+ {/* Step 1: Management account - CloudFormation Stack */}

- 3) Copy the Prowler IAM Role ARN from AWS and confirm the - StackSet is successfully deployed by clicking the checkbox - below. + 1) Deploy the ProwlerScan role in your{" "} + management account using a CloudFormation + Stack. +

+ +
+ + {/* Step 2: Member accounts - CloudFormation StackSet */} +
+

+ 2) Deploy the ProwlerScan role to{" "} + member accounts using a CloudFormation + StackSet. +

+

+ Open the StackSets console, select{" "} + Service-managed permissions, and paste the + template URL below. Set the ExternalId{" "} + parameter to the value shown above. +

+
+ + {PROWLER_CF_TEMPLATE_URL} + + +
+ +
+ + {/* Step 3: Role ARN + confirm */} +
+

+ 3) Paste the management account Role ARN and confirm both + deployments are complete.

@@ -365,7 +417,8 @@ export function OrgSetupForm({ htmlFor="stackSetDeployed" className="text-text-neutral-tertiary text-xs leading-5 font-normal" > - The StackSet has been successfully deployed in AWS + The Stack and StackSet have been successfully deployed in + AWS * diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts index 1fe10ef603..654bf27b43 100644 --- a/ui/lib/external-urls.ts +++ b/ui/lib/external-urls.ts @@ -8,6 +8,18 @@ export const DOCS_URLS = { "https://docs.prowler.com/user-guide/tutorials/prowler-cloud-aws-organizations", } as const; +// CloudFormation template URL for the ProwlerScan role. +// Also used (URL-encoded) as the templateURL param in cloudformationQuickLink +// and cloudformationOrgQuickLink below — keep both in sync. +export const PROWLER_CF_TEMPLATE_URL = + "https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/aws/cloudformation/prowler-scan-role.yml"; + +// AWS Console URL for creating a new StackSet. +// Hardcoded to us-east-1 — StackSets are typically managed from this region. +// Users in AWS GovCloud or China partitions would need different URLs. +export const STACKSET_CONSOLE_URL = + "https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacksets/create"; + export const getProviderHelpText = (provider: string) => { switch (provider) { case "aws": @@ -86,6 +98,7 @@ export const getAWSCredentialsTemplateLinks = ( cloudformation: string; terraform: string; cloudformationQuickLink: string; + cloudformationOrgQuickLink: string; } => { let links = {}; @@ -107,11 +120,24 @@ export const getAWSCredentialsTemplateLinks = ( }; } + const encodedTemplateUrl = encodeURIComponent(PROWLER_CF_TEMPLATE_URL); + const cfBaseUrl = + "https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate"; + const s3Params = bucketName + ? `¶m_EnableS3Integration=true¶m_S3IntegrationBucketName=${bucketName}` + : ""; + return { ...(links as { cloudformation: string; terraform: string; }), - cloudformationQuickLink: `https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https%3A%2F%2Fprowler-cloud-public.s3.eu-west-1.amazonaws.com%2Fpermissions%2Ftemplates%2Faws%2Fcloudformation%2Fprowler-scan-role.yml&stackName=Prowler¶m_ExternalId=${externalId}${bucketName ? `¶m_EnableS3Integration=true¶m_S3IntegrationBucketName=${bucketName}` : ""}`, + cloudformationQuickLink: + `${cfBaseUrl}?templateURL=${encodedTemplateUrl}` + + `&stackName=Prowler¶m_ExternalId=${externalId}${s3Params}`, + cloudformationOrgQuickLink: + `${cfBaseUrl}?templateURL=${encodedTemplateUrl}` + + `&stackName=Prowler¶m_ExternalId=${externalId}` + + `¶m_EnableOrganizations=true${s3Params}`, }; };