From 90831d3084296a4c5522f88bfcf83bed8a87547c Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Wed, 22 Jan 2025 12:45:31 +0100
Subject: [PATCH] feat(providers): make external id field mandatory in the aws
role secret form (#6656)
---
ui/actions/providers/providers.ts | 2 +-
.../(set-up-provider)/launch-scan/page.tsx | 20 ++-
.../test-connection/page.tsx | 3 +-
.../workflow/forms/launch-scan-form.tsx | 6 +-
.../workflow/forms/test-connection-form.tsx | 48 ++++--
.../workflow/forms/update-via-role-form.tsx | 142 ++++++++++++------
.../via-credentials/aws-credentials-form.tsx | 2 +-
.../workflow/forms/via-role-form.tsx | 109 ++++++++++----
.../workflow/forms/via-role/aws-role-form.tsx | 113 +++++++++-----
ui/components/providers/workflow/index.ts | 1 +
.../workflow/skeleton-provider-workflow.tsx | 32 ++++
.../workflow/workflow-add-provider.tsx | 10 +-
ui/types/components.ts | 1 +
ui/types/formSchemas.ts | 33 ++--
14 files changed, 363 insertions(+), 159 deletions(-)
create mode 100644 ui/components/providers/workflow/skeleton-provider-workflow.tsx
diff --git a/ui/actions/providers/providers.ts b/ui/actions/providers/providers.ts
index 3b2ea126d3..03ac58ab15 100644
--- a/ui/actions/providers/providers.ts
+++ b/ui/actions/providers/providers.ts
@@ -175,13 +175,13 @@ export const addCredentialsProvider = async (formData: FormData) => {
secretType = "role";
secret = {
role_arn: formData.get("role_arn"),
+ external_id: formData.get("external_id"),
aws_access_key_id: formData.get("aws_access_key_id") || undefined,
aws_secret_access_key:
formData.get("aws_secret_access_key") || undefined,
aws_session_token: formData.get("aws_session_token") || undefined,
session_duration:
parseInt(formData.get("session_duration") as string, 10) || 3600,
- external_id: formData.get("external_id") || undefined,
role_session_name: formData.get("role_session_name") || undefined,
};
} else {
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/launch-scan/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/launch-scan/page.tsx
index 3e88a5ea46..cdfba93dbd 100644
--- a/ui/app/(prowler)/providers/(set-up-provider)/launch-scan/page.tsx
+++ b/ui/app/(prowler)/providers/(set-up-provider)/launch-scan/page.tsx
@@ -1,8 +1,9 @@
import { redirect } from "next/navigation";
-import React from "react";
+import React, { Suspense } from "react";
import { getProvider } from "@/actions/providers";
import { LaunchScanForm } from "@/components/providers/workflow/forms";
+import { SkeletonProviderWorkflow } from "@/components/providers/workflow/skeleton-provider-workflow";
interface Props {
searchParams: { type: string; id: string };
@@ -26,6 +27,23 @@ export default async function LaunchScanPage({ searchParams }: Props) {
redirect("/providers/connect-account");
}
+ return (
+ }>
+
+
+ );
+}
+
+async function SSRLaunchScan({
+ searchParams,
+}: {
+ searchParams: { type: string; id: string };
+}) {
+ const formData = new FormData();
+ formData.append("id", searchParams.id);
+
+ const providerData = await getProvider(formData);
+
return (
);
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx
index b3e3e29c44..b5274cf763 100644
--- a/ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx
+++ b/ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
import React, { Suspense } from "react";
import { getProvider } from "@/actions/providers";
+import { SkeletonProviderWorkflow } from "@/components/providers/workflow";
import { TestConnectionForm } from "@/components/providers/workflow/forms";
interface Props {
@@ -16,7 +17,7 @@ export default async function TestConnectionPage({ searchParams }: Props) {
}
return (
- Loading...
}>
+ }>
);
diff --git a/ui/components/providers/workflow/forms/launch-scan-form.tsx b/ui/components/providers/workflow/forms/launch-scan-form.tsx
index 2532fd2e90..968fb8ffb9 100644
--- a/ui/components/providers/workflow/forms/launch-scan-form.tsx
+++ b/ui/components/providers/workflow/forms/launch-scan-form.tsx
@@ -93,10 +93,8 @@ export const LaunchScanForm = ({
className="flex flex-col space-y-4"
>
-
- Scan started
-
-
+
Scan started
+
The scan has just started. From now on, a new scan will be launched
every 24 hours, starting from this moment.
diff --git a/ui/components/providers/workflow/forms/test-connection-form.tsx b/ui/components/providers/workflow/forms/test-connection-form.tsx
index 41e5ebed66..778df1e478 100644
--- a/ui/components/providers/workflow/forms/test-connection-form.tsx
+++ b/ui/components/providers/workflow/forms/test-connection-form.tsx
@@ -66,6 +66,7 @@ export const TestConnectionForm = ({
error: string | null;
} | null>(null);
const [isResettingCredentials, setIsResettingCredentials] = useState(false);
+ const [isRedirecting, setIsRedirecting] = useState(false);
const form = useForm
({
resolver: zodResolver(formSchema),
@@ -129,9 +130,9 @@ export const TestConnectionForm = ({
const isUpdated = urlParams.get("updated") === "true";
if (!isUpdated) {
- router.push(
- `/providers/launch-scan?type=${providerType}&id=${providerId}`,
- );
+ setIsRedirecting(true);
+ router.push("/scans");
+ return;
} else {
setConnectionStatus({
connected: true,
@@ -191,6 +192,25 @@ export const TestConnectionForm = ({
}
};
+ if (isRedirecting) {
+ return (
+
+
+
+
+ Scan initiated successfully
+
+
+ Redirecting to scans dashboard...
+
+
+
+ );
+ }
+
return (
-
+
It seems there was an issue with your credentials. Please review
your credentials and try again.
@@ -243,11 +261,11 @@ export const TestConnectionForm = ({
providerUID={providerData.data.attributes.uid}
/>
- {!isResettingCredentials && !connectionStatus?.error && (
-
+ {/* {!isResettingCredentials && !connectionStatus?.error && (
+
Test connection and launch scan
- )}
+ )} */}
@@ -308,7 +326,7 @@ export const TestConnectionForm = ({
{isUpdated && connectionStatus?.connected
? "Go to providers"
- : "Launch"}
+ : "Launch scan"}
)}
diff --git a/ui/components/providers/workflow/forms/update-via-role-form.tsx b/ui/components/providers/workflow/forms/update-via-role-form.tsx
index ee57dc0759..85e00e3cb1 100644
--- a/ui/components/providers/workflow/forms/update-via-role-form.tsx
+++ b/ui/components/providers/workflow/forms/update-via-role-form.tsx
@@ -3,7 +3,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
-import { Control, useForm } from "react-hook-form";
+import { useSession } from "next-auth/react";
+import { Control, useForm, UseFormSetValue } from "react-hook-form";
import * as z from "zod";
import { updateCredentialsProvider } from "@/actions/providers/providers";
@@ -25,80 +26,119 @@ export const UpdateViaRoleForm = ({
}) => {
const router = useRouter();
const { toast } = useToast();
+ const { data: session } = useSession();
const searchParamsObj = useSearchParams();
- // Handler for back button
- const handleBackStep = () => {
- const currentParams = new URLSearchParams(window.location.search);
- currentParams.delete("via");
- router.push(`?${currentParams.toString()}`);
- };
-
+ // Extract values from searchParams
const providerType = searchParams.type;
const providerId = searchParams.id;
const providerSecretId = searchParams.secretId || "";
+ const externalId = session?.tenantId;
const formSchema = addCredentialsRoleFormSchema(providerType);
- type FormSchemaType = z.infer
;
+ type FormSchemaType = z.infer & {
+ credentials_type: "aws-sdk-default" | "access-secret-key";
+ };
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
providerId,
providerType,
- ...(providerType === "aws"
- ? {
- role_arn: "",
- aws_access_key_id: "",
- aws_secret_access_key: "",
- aws_session_token: "",
- session_duration: 3600,
- external_id: "",
- role_session_name: "",
- }
- : {}),
+ credentials_type: "aws-sdk-default",
+ ...(providerType === "aws" && {
+ role_arn: "",
+ external_id: externalId,
+ aws_access_key_id: "",
+ aws_secret_access_key: "",
+ aws_session_token: "",
+ role_session_name: "",
+ session_duration: 3600,
+ }),
},
});
const isLoading = form.formState.isSubmitting;
+ // Handle form submission
const onSubmitClient = async (values: FormSchemaType) => {
- const formData = new FormData();
+ try {
+ const formData = new FormData();
- Object.entries(values).forEach(
- ([key, value]) =>
- value !== undefined && formData.append(key, String(value)),
- );
+ Object.entries(values).forEach(([key, value]) => {
+ if (key === "credentials_type") return;
- const data = await updateCredentialsProvider(providerSecretId, formData);
+ if (
+ values.credentials_type === "access-secret-key" &&
+ [
+ "aws_access_key_id",
+ "aws_secret_access_key",
+ "aws_session_token",
+ ].includes(key)
+ ) {
+ if (value !== undefined && value !== "") {
+ formData.append(key, String(value));
+ }
+ return;
+ }
- if (data?.errors && data.errors.length > 0) {
- data.errors.forEach((error: ApiError) => {
- const errorMessage = error.detail;
- switch (error.source.pointer) {
- case "/data/attributes/secret/role_arn":
- form.setError("role_arn" as keyof FormSchemaType, {
- type: "server",
- message: errorMessage,
- });
- break;
-
- default:
- toast({
- variant: "destructive",
- title: "Oops! Something went wrong",
- description: errorMessage,
- });
+ if (value !== undefined && value !== "") {
+ formData.append(key, String(value));
}
});
- } else {
- router.push(
- `/providers/test-connection?type=${providerType}&id=${providerId}&updated=true`,
- );
+
+ const data = await updateCredentialsProvider(providerSecretId, formData);
+
+ // Handle errors
+ if (data?.errors?.length) {
+ data.errors.forEach((error: ApiError) => {
+ const errorMessage = error.detail;
+ switch (error.source.pointer) {
+ case "/data/attributes/secret/role_arn":
+ form.setError("role_arn" as keyof FormSchemaType, {
+ type: "server",
+ message: errorMessage,
+ });
+ break;
+ case "/data/attributes/secret/external_id":
+ form.setError("external_id" as keyof FormSchemaType, {
+ type: "server",
+ message: errorMessage,
+ });
+ break;
+ default:
+ toast({
+ variant: "destructive",
+ title: "Oops! Something went wrong",
+ description: errorMessage,
+ });
+ }
+ });
+ } else {
+ // Redirect on success
+ router.push(
+ `/providers/test-connection?type=${providerType}&id=${providerId}&updated=true`,
+ );
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error("Error during submission:", error);
+ toast({
+ variant: "destructive",
+ title: "Submission failed",
+ description: "An error occurred while processing your request.",
+ });
}
};
+ // Handle back navigation
+ const handleBackStep = () => {
+ const currentParams = new URLSearchParams(window.location.search);
+ currentParams.delete("via");
+ router.push(`?${currentParams.toString()}`);
+ };
+
return (
+ {/* Conditional AWS Form */}
{providerType === "aws" && (
}
+ setValue={
+ form.setValue as unknown as UseFormSetValue
+ }
+ externalId={externalId || ""}
/>
)}
+ {/* Action Buttons */}
{searchParamsObj.get("via") === "role" && (
Connect via Credentials
-
+
Please provide the information for your AWS credentials.
diff --git a/ui/components/providers/workflow/forms/via-role-form.tsx b/ui/components/providers/workflow/forms/via-role-form.tsx
index 3a38cb1808..917f049c7c 100644
--- a/ui/components/providers/workflow/forms/via-role-form.tsx
+++ b/ui/components/providers/workflow/forms/via-role-form.tsx
@@ -3,7 +3,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
-import { Control, useForm } from "react-hook-form";
+import { useSession } from "next-auth/react";
+import { Control, useForm, UseFormSetValue } from "react-hook-form";
import * as z from "zod";
import { addCredentialsProvider } from "@/actions/providers/providers";
@@ -25,8 +26,9 @@ export const ViaRoleForm = ({
}) => {
const router = useRouter();
const { toast } = useToast();
-
+ const { data: session } = useSession();
const searchParamsObj = useSearchParams();
+ const externalId = session?.tenantId;
// Handler for back button
const handleBackStep = () => {
@@ -39,22 +41,25 @@ export const ViaRoleForm = ({
const providerId = searchParams.id;
const formSchema = addCredentialsRoleFormSchema(providerType);
- type FormSchemaType = z.infer;
+ type FormSchemaType = z.infer & {
+ credentials_type: "aws-sdk-default" | "access-secret-key";
+ };
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
providerId,
providerType,
+ credentials_type: "aws-sdk-default",
...(providerType === "aws"
? {
role_arn: "",
+ external_id: externalId,
aws_access_key_id: "",
aws_secret_access_key: "",
aws_session_token: "",
- session_duration: 3600,
- external_id: "",
role_session_name: "",
+ session_duration: 3600,
}
: {}),
},
@@ -65,36 +70,72 @@ export const ViaRoleForm = ({
const onSubmitClient = async (values: FormSchemaType) => {
const formData = new FormData();
- Object.entries(values).forEach(
- ([key, value]) =>
- value !== undefined && formData.append(key, String(value)),
- );
+ Object.entries(values).forEach(([key, value]) => {
+ // Do not include credentials_type
+ if (key === "credentials_type") return;
- const data = await addCredentialsProvider(formData);
-
- if (data?.errors && data.errors.length > 0) {
- data.errors.forEach((error: ApiError) => {
- const errorMessage = error.detail;
- switch (error.source.pointer) {
- case "/data/attributes/secret/role_arn":
- form.setError("role_arn" as keyof FormSchemaType, {
- type: "server",
- message: errorMessage,
- });
- break;
-
- default:
- toast({
- variant: "destructive",
- title: "Oops! Something went wrong",
- description: errorMessage,
- });
+ // If credentials_type is "access-secret-key", include the relevant fields
+ if (
+ values.credentials_type === "access-secret-key" &&
+ [
+ "aws_access_key_id",
+ "aws_secret_access_key",
+ "aws_session_token",
+ ].includes(key)
+ ) {
+ if (value !== undefined && value !== "") {
+ formData.append(key, String(value));
}
+ return;
+ }
+
+ // Add any other valid field
+ if (value !== undefined && value !== "") {
+ formData.append(key, String(value));
+ }
+ });
+
+ try {
+ const data = await addCredentialsProvider(formData);
+
+ if (data?.errors && data.errors.length > 0) {
+ data.errors.forEach((error: ApiError) => {
+ const errorMessage = error.detail;
+
+ switch (error.source.pointer) {
+ case "/data/attributes/secret/role_arn":
+ form.setError("role_arn" as keyof FormSchemaType, {
+ type: "server",
+ message: errorMessage,
+ });
+ break;
+ case "/data/attributes/secret/external_id":
+ form.setError("external_id" as keyof FormSchemaType, {
+ type: "server",
+ message: errorMessage,
+ });
+ break;
+ default:
+ toast({
+ variant: "destructive",
+ title: "Oops! Something went wrong",
+ description: errorMessage,
+ });
+ }
+ });
+ } else {
+ router.push(
+ `/providers/test-connection?type=${providerType}&id=${providerId}`,
+ );
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error("Error during submission:", error);
+ toast({
+ variant: "destructive",
+ title: "Submission failed",
+ description: "An error occurred while processing your request.",
});
- } else {
- router.push(
- `/providers/test-connection?type=${providerType}&id=${providerId}`,
- );
}
};
@@ -110,6 +151,10 @@ export const ViaRoleForm = ({
{providerType === "aws" && (
}
+ setValue={
+ form.setValue as unknown as UseFormSetValue
+ }
+ externalId={externalId || ""}
/>
)}
diff --git a/ui/components/providers/workflow/forms/via-role/aws-role-form.tsx b/ui/components/providers/workflow/forms/via-role/aws-role-form.tsx
index eb7f5d3a43..1240e20e07 100644
--- a/ui/components/providers/workflow/forms/via-role/aws-role-form.tsx
+++ b/ui/components/providers/workflow/forms/via-role/aws-role-form.tsx
@@ -1,24 +1,95 @@
-import { Control } from "react-hook-form";
+import { Select, SelectItem, Spacer } from "@nextui-org/react";
+import { Control, UseFormSetValue, useWatch } from "react-hook-form";
import { CustomInput } from "@/components/ui/custom";
import { AWSCredentialsRole } from "@/types";
export const AWSCredentialsRoleForm = ({
control,
+ setValue,
+ externalId,
}: {
control: Control;
+ setValue: UseFormSetValue;
+ externalId: string;
}) => {
+ const credentialsType = useWatch({
+ control,
+ name: "credentials_type" as const,
+ defaultValue: "aws-sdk-default",
+ });
+
return (
<>
Connect assuming IAM Role
-
+
Please provide the information for your AWS credentials.
+
Authentication
+
+
+
+ {credentialsType === "access-secret-key" && (
+ <>
+
+
+
+ >
+ )}
+
+
Assume Role
+
-
Optional fields
-
-
-
+
Optional fields
{
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/ui/components/providers/workflow/workflow-add-provider.tsx b/ui/components/providers/workflow/workflow-add-provider.tsx
index 6bae1f2dde..d428d92a6c 100644
--- a/ui/components/providers/workflow/workflow-add-provider.tsx
+++ b/ui/components/providers/workflow/workflow-add-provider.tsx
@@ -19,17 +19,11 @@ const steps = [
href: "/providers/add-credentials",
},
{
- title: "Test connection",
+ title: "Check connection and launch scan",
description:
- "Test your connection to verify that the credentials provided are valid for accessing your cloud account.",
+ "Test your connection to verify that the credentials provided are valid for accessing your cloud account and launch a scan.",
href: "/providers/test-connection",
},
- {
- title: "Success",
- description:
- "Your cloud account has been successfully connected and the scan has been launched.",
- href: "/providers/launch-scan",
- },
];
export const WorkflowAddProvider = () => {
diff --git a/ui/types/components.ts b/ui/types/components.ts
index b74bf259b9..6818f3199c 100644
--- a/ui/types/components.ts
+++ b/ui/types/components.ts
@@ -182,6 +182,7 @@ export type AWSCredentialsRole = {
external_id?: string;
role_session_name?: string;
session_duration?: number;
+ credentials_type?: "aws-sdk-default" | "access-secret-key";
};
export type AzureCredentials = {
diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts
index 76ec266c8f..68319f4a73 100644
--- a/ui/types/formSchemas.ts
+++ b/ui/types/formSchemas.ts
@@ -133,17 +133,28 @@ export const addCredentialsFormSchema = (providerType: string) =>
export const addCredentialsRoleFormSchema = (providerType: string) =>
providerType === "aws"
- ? z.object({
- providerId: z.string(),
- providerType: z.string(),
- role_arn: z.string().optional(),
- aws_access_key_id: z.string().optional(),
- aws_secret_access_key: z.string().optional(),
- aws_session_token: z.string().optional(),
- session_duration: z.number().optional(),
- external_id: z.string().optional(),
- role_session_name: z.string().optional(),
- })
+ ? z
+ .object({
+ providerId: z.string(),
+ providerType: z.string(),
+ role_arn: z.string().optional(),
+ external_id: z.string().optional(),
+ aws_access_key_id: z.string().optional(),
+ aws_secret_access_key: z.string().optional(),
+ aws_session_token: z.string().optional(),
+ session_duration: z.number().optional(),
+ role_session_name: z.string().optional(),
+ credentials_type: z.string().optional(),
+ })
+ .refine(
+ (data) =>
+ data.credentials_type !== "access-secret-key" ||
+ (data.aws_access_key_id && data.aws_secret_access_key),
+ {
+ message: "AWS Access Key ID and Secret Access Key are required.",
+ path: ["aws_access_key_id"],
+ },
+ )
: z.object({
providerId: z.string(),
providerType: z.string(),