feat: aws providers can be added via role

This commit is contained in:
Pablo Lara
2024-11-05 16:19:07 +01:00
parent e0c2720d31
commit 4a3ff78636
10 changed files with 300 additions and 36 deletions
+29 -6
View File
@@ -158,27 +158,50 @@ export const addCredentialsProvider = async (formData: FormData) => {
const providerId = formData.get("providerId");
const providerType = formData.get("providerType");
const isRole = formData.get("role_arn") !== null;
let secret = {};
let secretType = "static"; // Default to static credentials
if (providerType === "aws") {
secret = {
aws_access_key_id: formData.get("aws_access_key_id"),
aws_secret_access_key: formData.get("aws_secret_access_key"),
aws_session_token: formData.get("aws_session_token") || undefined,
};
if (isRole) {
// Role-based configuration for AWS
secretType = "role";
secret = {
role_arn: formData.get("role_arn"),
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 {
// Static credentials configuration for AWS
secret = {
aws_access_key_id: formData.get("aws_access_key_id"),
aws_secret_access_key: formData.get("aws_secret_access_key"),
aws_session_token: formData.get("aws_session_token") || undefined,
};
}
} else if (providerType === "azure") {
// Static credentials configuration for Azure
secret = {
client_id: formData.get("client_id"),
client_secret: formData.get("client_secret"),
tenant_id: formData.get("tenant_id"),
};
} else if (providerType === "gcp") {
// Static credentials configuration for GCP
secret = {
client_id: formData.get("client_id"),
client_secret: formData.get("client_secret"),
refresh_token: formData.get("refresh_token"),
};
} else if (providerType === "kubernetes") {
// Static credentials configuration for Kubernetes
secret = {
kubeconfig_content: formData.get("kubeconfig_content"),
};
@@ -188,7 +211,7 @@ export const addCredentialsProvider = async (formData: FormData) => {
data: {
type: "ProviderSecret",
attributes: {
secret_type: "static",
secret_type: secretType,
secret,
name: secretName,
},
@@ -1,7 +1,10 @@
import { redirect } from "next/navigation";
import React from "react";
import { ViaCredentialsForm } from "@/components/providers/workflow/forms";
import {
ViaCredentialsForm,
ViaRoleForm,
} from "@/components/providers/workflow/forms";
interface Props {
searchParams: { type: string; id: string; via?: string };
@@ -20,9 +23,13 @@ export default function AddCredentialsPage({ searchParams }: Props) {
(searchParams.type === "aws" && searchParams.via === "credentials") ||
(searchParams.type !== "aws" && !searchParams.via);
const useRoleForm =
searchParams.type === "aws" && searchParams.via === "role";
return (
<>
{useCredentialsForm && <ViaCredentialsForm searchParams={searchParams} />}
{useRoleForm && <ViaRoleForm searchParams={searchParams} />}
</>
);
}
@@ -3,3 +3,4 @@ export * from "./launch-scan-form";
export * from "./radio-group-aws-via-credentials-form";
export * from "./test-connection-form";
export * from "./via-credentials-form";
export * from "./via-role-form";
@@ -34,20 +34,9 @@ export const RadioGroupAWSViaCredentialsForm = ({
>
<div className="flex flex-col gap-4">
<span className="text-sm text-default-500">Using IAM Role</span>
<CustomRadio
description="Connect via CloudFormation"
value="cloudformation"
>
<CustomRadio description="Connect assuming IAM Role" value="role">
<div className="flex items-center">
<span className="ml-2">Connect via CloudFormation</span>
</div>
</CustomRadio>
<CustomRadio
description="Connect via Terraform"
value="terraform"
>
<div className="flex items-center">
<span className="ml-2">Connect via Terraform</span>
<span className="ml-2">Connect assuming IAM Role</span>
</div>
</CustomRadio>
<span className="text-sm text-default-500">
@@ -8,7 +8,7 @@ import * as z from "zod";
import { addCredentialsProvider } from "@/actions/providers/providers";
import { useToast } from "@/components/ui";
import { CustomButton, CustomInput } from "@/components/ui/custom";
import { CustomButton } from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import {
addCredentialsFormSchema,
@@ -50,7 +50,6 @@ export const ViaCredentialsForm = ({
const form = useForm<FormType>({
resolver: zodResolver(formSchema),
defaultValues: {
secretName: "",
providerId,
providerType,
...(providerType === "aws"
@@ -188,20 +187,6 @@ export const ViaCredentialsForm = ({
/>
)}
<span className="text-sm text-default-500">Name (Optional)</span>
<CustomInput
control={form.control}
name="secretName"
type="text"
label="Credential name"
labelPlacement="inside"
placeholder={"Enter the credential name"}
variant="bordered"
isRequired={false}
size="sm"
isInvalid={!!form.formState.errors.secretName}
/>
<div className="flex w-full justify-end sm:space-x-6">
<CustomButton
type="submit"
@@ -0,0 +1,125 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { SaveIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { Control, useForm } from "react-hook-form";
import * as z from "zod";
import { addCredentialsProvider } from "@/actions/providers/providers";
import { useToast } from "@/components/ui";
import { CustomButton } from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import {
addCredentialsRoleFormSchema,
ApiError,
AWSCredentialsRole,
} from "@/types";
import { AWSCredentialsRoleForm } from "./via-role/aws-role-form";
export const ViaRoleForm = ({
searchParams,
}: {
searchParams: { type: string; id: string };
}) => {
const router = useRouter();
const { toast } = useToast();
const providerType = searchParams.type;
const providerId = searchParams.id;
const formSchema = addCredentialsRoleFormSchema(providerType);
type FormSchemaType = z.infer<typeof formSchema>;
const form = useForm<FormSchemaType>({
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: "",
}
: {}),
},
});
const isLoading = form.formState.isSubmitting;
const onSubmitClient = async (values: FormSchemaType) => {
console.log("via ROLE form", values);
const formData = new FormData();
Object.entries(values).forEach(
([key, value]) =>
value !== undefined && formData.append(key, String(value)),
);
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,
});
}
});
} else {
router.push(
`/providers/test-connection?type=${providerType}&id=${providerId}`,
);
}
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmitClient)}
className="flex flex-col space-y-4"
>
<input type="hidden" name="providerId" value={providerId} />
<input type="hidden" name="providerType" value={providerType} />
{providerType === "aws" && (
<AWSCredentialsRoleForm
control={form.control as unknown as Control<AWSCredentialsRole>}
/>
)}
<div className="flex w-full justify-end sm:space-x-6">
<CustomButton
type="submit"
ariaLabel={"Save"}
className="w-1/2"
variant="solid"
color="action"
size="lg"
isLoading={isLoading}
startContent={!isLoading && <SaveIcon size={24} />}
>
{isLoading ? <>Loading</> : <span>Save</span>}
</CustomButton>
</div>
</form>
</Form>
);
};
@@ -0,0 +1,105 @@
import { Control } from "react-hook-form";
import { CustomInput } from "@/components/ui/custom";
import { AWSCredentialsRole } from "@/types";
export const AWSCredentialsRoleForm = ({
control,
}: {
control: Control<AWSCredentialsRole>;
}) => {
return (
<>
<div className="mb-4 text-left">
<div className="text-2xl font-bold leading-9 text-default-foreground">
Connect assuming IAM Role
</div>
<div className="py-2 text-default-500">
Please provide the information for your AWS credentials.
</div>
</div>
<CustomInput
control={control}
name="role_arn"
type="text"
label="Role ARN"
labelPlacement="inside"
placeholder="Enter the Role ARN"
variant="bordered"
isRequired
isInvalid={!!control._formState.errors.role_arn}
/>
<span className="text-sm text-default-500">Optional fields</span>
<CustomInput
control={control}
name="aws_access_key_id"
type="password"
label="AWS Access Key ID"
labelPlacement="inside"
placeholder="Enter the AWS Access Key ID"
variant="bordered"
isRequired={false}
isInvalid={!!control._formState.errors.aws_access_key_id}
/>
<CustomInput
control={control}
name="aws_secret_access_key"
type="password"
label="AWS Secret Access Key"
labelPlacement="inside"
placeholder="Enter the AWS Secret Access Key"
variant="bordered"
isRequired={false}
isInvalid={!!control._formState.errors.aws_secret_access_key}
/>
<CustomInput
control={control}
name="aws_session_token"
type="password"
label="AWS Session Token"
labelPlacement="inside"
placeholder="Enter the AWS Session Token"
variant="bordered"
isRequired={false}
isInvalid={!!control._formState.errors.aws_session_token}
/>
<CustomInput
control={control}
name="external_id"
type="text"
label="External ID"
labelPlacement="inside"
placeholder="Enter the External ID"
variant="bordered"
isRequired={false}
isInvalid={!!control._formState.errors.external_id}
/>
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
<CustomInput
control={control}
name="role_session_name"
type="text"
label="Role Session Name"
labelPlacement="inside"
placeholder="Enter the Role Session Name"
variant="bordered"
isRequired={false}
isInvalid={!!control._formState.errors.role_session_name}
/>
<CustomInput
control={control}
name="session_duration"
type="number"
label="Session Duration (seconds)"
labelPlacement="inside"
placeholder="Enter the session duration (default: 3600)"
variant="bordered"
isRequired={false}
isInvalid={!!control._formState.errors.session_duration}
/>
</div>
</>
);
};
@@ -0,0 +1 @@
export * from "./aws-role-form";
+10
View File
@@ -34,6 +34,16 @@ export type AWSCredentials = {
providerId: string;
};
export type AWSCredentialsRole = {
role_arn: string;
aws_access_key_id?: string;
aws_secret_access_key?: string;
aws_session_token?: string;
external_id?: string;
role_session_name?: string;
session_duration?: number;
};
export type AzureCredentials = {
client_id: string;
client_secret: string;
+18
View File
@@ -104,6 +104,24 @@ 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(),
});
export const testConnectionFormSchema = z.object({
providerId: z.string(),
});