diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/AddExternalKmsForm.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/AddExternalKmsForm.tsx new file mode 100644 index 000000000..f6ee93ef2 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/AddExternalKmsForm.tsx @@ -0,0 +1,100 @@ +import { useState } from "react"; +import { faAws } from "@fortawesome/free-brands-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AnimatePresence, motion } from "framer-motion"; + +import { Modal, ModalContent } from "@app/components/v2"; + +import { AwsKmsForm } from "./AwsKmsForm"; + +type Props = { + isOpen?: boolean; + onToggle: (isOpen: boolean) => void; +}; + +enum WizardSteps { + SelectProvider = "select-provider", + ProviderInputs = "provider-inputs" +} + +enum ExternalKmsProvider { + AWS = "aws" +} + +const EXTERNAL_KMS_LIST = [ + { + icon: faAws, + provider: ExternalKmsProvider.AWS, + title: "AWS KMS" + } +]; + +export const AddExternalKmsForm = ({ isOpen, onToggle }: Props) => { + const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider); + const [selectedProvider, setSelectedProvider] = useState(null); + + const handleFormReset = (state: boolean = false) => { + onToggle(state); + setWizardStep(WizardSteps.SelectProvider); + setSelectedProvider(null); + }; + + return ( + handleFormReset(state)}> + + + {wizardStep === WizardSteps.SelectProvider && ( + +
Select a KMS Provider
+
+ {EXTERNAL_KMS_LIST.map(({ icon, provider, title }) => ( +
{ + setSelectedProvider(provider); + setWizardStep(WizardSteps.ProviderInputs); + }} + onKeyDown={(evt) => { + if (evt.key === "Enter") { + setSelectedProvider(provider); + setWizardStep(WizardSteps.ProviderInputs); + } + }} + > + +
{title}
+
+ ))} +
+
+ )} + {wizardStep === WizardSteps.ProviderInputs && + selectedProvider === ExternalKmsProvider.AWS && ( + + + + )} +
+
+
+ ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/AwsKmsForm.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/AwsKmsForm.tsx new file mode 100644 index 000000000..40373572b --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/AwsKmsForm.tsx @@ -0,0 +1,191 @@ +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { FormControl, Input, Select, SelectItem } from "@app/components/v2"; + +const AWS_REGIONS = [ + { name: "US East (Ohio)", slug: "us-east-2" }, + { name: "US East (N. Virginia)", slug: "us-east-1" }, + { name: "US West (N. California)", slug: "us-west-1" }, + { name: "US West (Oregon)", slug: "us-west-2" }, + { name: "Africa (Cape Town)", slug: "af-south-1" }, + { name: "Asia Pacific (Hong Kong)", slug: "ap-east-1" }, + { name: "Asia Pacific (Hyderabad)", slug: "ap-south-2" }, + { name: "Asia Pacific (Jakarta)", slug: "ap-southeast-3" }, + { name: "Asia Pacific (Melbourne)", slug: "ap-southeast-4" }, + { name: "Asia Pacific (Mumbai)", slug: "ap-south-1" }, + { name: "Asia Pacific (Osaka)", slug: "ap-northeast-3" }, + { name: "Asia Pacific (Seoul)", slug: "ap-northeast-2" }, + { name: "Asia Pacific (Singapore)", slug: "ap-southeast-1" }, + { name: "Asia Pacific (Sydney)", slug: "ap-southeast-2" }, + { name: "Asia Pacific (Tokyo)", slug: "ap-northeast-1" }, + { name: "Canada (Central)", slug: "ca-central-1" }, + { name: "Europe (Frankfurt)", slug: "eu-central-1" }, + { name: "Europe (Ireland)", slug: "eu-west-1" }, + { name: "Europe (London)", slug: "eu-west-2" }, + { name: "Europe (Milan)", slug: "eu-south-1" }, + { name: "Europe (Paris)", slug: "eu-west-3" }, + { name: "Europe (Spain)", slug: "eu-south-2" }, + { name: "Europe (Stockholm)", slug: "eu-north-1" }, + { name: "Europe (Zurich)", slug: "eu-central-2" }, + { name: "Middle East (Bahrain)", slug: "me-south-1" }, + { name: "Middle East (UAE)", slug: "me-central-1" }, + { name: "South America (Sao Paulo)", slug: "sa-east-1" }, + { name: "AWS GovCloud (US-East)", slug: "us-gov-east-1" }, + { name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" } +]; + +export enum KmsAwsCredentialType { + AssumeRole = "assume-role", + AccessKey = "access-key" +} + +const formSchema = z.object({ + credential: z.discriminatedUnion("type", [ + z.object({ + type: z.literal(KmsAwsCredentialType.AccessKey), + data: z.object({ + accessKey: z.string().trim().min(1), + secretKey: z.string().trim().min(1) + }) + }), + z.object({ + type: z.literal(KmsAwsCredentialType.AssumeRole), + data: z.object({ + assumeRoleArn: z.string().trim().min(1), + externalId: z.string().trim().min(1).optional() + }) + }) + ]), + awsRegion: z.string().min(1).trim().describe("AWS region to connect"), + kmsKeyId: z.string().trim().optional() +}); + +type TForm = z.infer; + +export const AwsKmsForm = () => { + const { control, handleSubmit, watch } = useForm({ + resolver: zodResolver(formSchema) + }); + + const selectedAwsAuthType = watch("credential.type"); + + const handleAddAwsKms = () => {}; + + return ( +
+ ( + + + + )} + /> + + {selectedAwsAuthType === KmsAwsCredentialType.AccessKey ? ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + ) : ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + )} + ( + + + + )} + /> + ( + + + + )} + /> + + ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/index.tsx new file mode 100644 index 000000000..53bdefd3c --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/AddExternalKmsForm/index.tsx @@ -0,0 +1 @@ +export { AddExternalKmsForm } from "./AddExternalKmsForm"; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx new file mode 100644 index 000000000..da3ed0b07 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx @@ -0,0 +1,60 @@ +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Button, UpgradePlanModal } from "@app/components/v2"; +import { + OrgPermissionActions, + OrgPermissionSubjects, + useOrganization, + useSubscription +} from "@app/context"; +import { withPermission } from "@app/hoc"; +import { usePopUp } from "@app/hooks"; + +import { AddExternalKmsForm } from "./AddExternalKmsForm"; + +export const OrgEncryptionTab = withPermission( + () => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ + "upgradePlan", + "addExternalKMS" + ] as const); + const { subscription } = useSubscription(); + + return ( +
+
+

Encryption

+ +
+

+ Configure the Key Management System used for encrypting/decrypting your data at rest +

+ handlePopUpToggle("upgradePlan", isOpen)} + text="You can add audit log streams if you switch to Infisical's Enterprise plan." + /> + handlePopUpToggle("addExternalKMS", state)} + /> +
+ ); + }, + { action: OrgPermissionActions.Edit, subject: OrgPermissionSubjects.Settings } +); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/index.tsx new file mode 100644 index 000000000..c32c02d76 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgEncryptionTab/index.tsx @@ -0,0 +1 @@ +export { OrgEncryptionTab } from "./OrgEncryptionTab"; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx index fdf541c54..3894bb28f 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx @@ -3,11 +3,13 @@ import { Tab } from "@headlessui/react"; import { AuditLogStreamsTab } from "../AuditLogStreamTab"; import { OrgAuthTab } from "../OrgAuthTab"; +import { OrgEncryptionTab } from "../OrgEncryptionTab"; import { OrgGeneralTab } from "../OrgGeneralTab"; const tabs = [ { name: "General", key: "tab-org-general" }, { name: "Security", key: "tab-org-security" }, + { name: "Encryption", key: "tab-org-encryption" }, { name: "Audit Log Streams", key: "tag-audit-log-streams" } ]; export const OrgTabGroup = () => { @@ -19,8 +21,9 @@ export const OrgTabGroup = () => { {({ selected }) => ( @@ -35,6 +38,9 @@ export const OrgTabGroup = () => { + + +