mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
@@ -64,7 +64,7 @@ export const CreatePkiSyncModal = ({
|
||||
"Add Sync"
|
||||
)
|
||||
}
|
||||
className="max-w-2xl"
|
||||
className="max-w-3xl"
|
||||
bodyClassName="overflow-visible"
|
||||
subTitle={
|
||||
selectedSync ? undefined : "Select a third-party service to sync certificates to."
|
||||
|
||||
@@ -15,11 +15,13 @@ type Props = {
|
||||
export const EditPkiSyncModal = ({ pkiSync, onOpenChange, fields, ...props }: Props) => {
|
||||
if (!pkiSync) return null;
|
||||
|
||||
const modalClassName = fields === PkiSyncEditFields.Mappings ? "max-w-4xl" : "max-w-2xl";
|
||||
|
||||
return (
|
||||
<Modal {...props} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
title={<PkiSyncModalHeader isConfigured destination={pkiSync.destination} />}
|
||||
className="max-w-2xl"
|
||||
className={modalClassName}
|
||||
bodyClassName="overflow-visible"
|
||||
>
|
||||
<EditPkiSyncForm onComplete={() => onOpenChange(false)} fields={fields} pkiSync={pkiSync} />
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { FormControl, Select, SelectItem } from "@app/components/v2";
|
||||
import { AWS_REGIONS } from "@app/helpers/appConnections";
|
||||
import { PkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
|
||||
import { PkiSyncConnectionField } from "./PkiSyncConnectionField";
|
||||
|
||||
export const AwsSecretsManagerPkiSyncFields = () => {
|
||||
const { control, setValue } = useFormContext<
|
||||
TPkiSyncForm & { destination: PkiSync.AwsSecretsManager }
|
||||
>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PkiSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.region", "");
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
name="destinationConfig.region"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="AWS Region"
|
||||
tooltipText="Select the AWS region where your secrets will be stored in AWS Secrets Manager."
|
||||
>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="w-full border border-mineshaft-500 capitalize"
|
||||
position="popper"
|
||||
placeholder="Select an AWS region"
|
||||
>
|
||||
{AWS_REGIONS.map(({ name, slug }) => (
|
||||
<SelectItem value={slug} key={slug}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { FormControl, Input } from "@app/components/v2";
|
||||
import { PkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
|
||||
import { PkiSyncConnectionField } from "./PkiSyncConnectionField";
|
||||
|
||||
export const ChefPkiSyncFields = () => {
|
||||
const { control, setValue } = useFormContext<TPkiSyncForm & { destination: PkiSync.Chef }>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PkiSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.dataBagName", "");
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
name="destinationConfig.dataBagName"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Data Bag Name"
|
||||
tooltipText="Enter your Chef data bag name where certificates will be stored. This data bag will be used to store SSL/TLS certificates, private keys, and certificate chains. Data bag names must contain only alphanumeric characters, underscores, and hyphens."
|
||||
>
|
||||
<Input {...field} placeholder="ssl_certificates" maxLength={255} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Switch } from "@app/components/v2";
|
||||
@@ -16,6 +15,7 @@ import { PkiSyncFormSchema, TPkiSyncForm } from "./schemas/pki-sync-schema";
|
||||
import { PkiSyncCertificatesFields } from "./PkiSyncCertificatesFields";
|
||||
import { PkiSyncDestinationFields } from "./PkiSyncDestinationFields";
|
||||
import { PkiSyncDetailsFields } from "./PkiSyncDetailsFields";
|
||||
import { PkiSyncFieldMappingsFields } from "./PkiSyncFieldMappingsFields";
|
||||
import { PkiSyncOptionsFields } from "./PkiSyncOptionsFields";
|
||||
import { PkiSyncReviewFields } from "./PkiSyncReviewFields";
|
||||
|
||||
@@ -26,13 +26,38 @@ type Props = {
|
||||
initialData?: any;
|
||||
};
|
||||
|
||||
const FORM_TABS: { name: string; key: string; fields: (keyof TPkiSyncForm)[] }[] = [
|
||||
{ name: "Destination", key: "destination", fields: ["connection", "destinationConfig"] },
|
||||
{ name: "Sync Options", key: "options", fields: ["syncOptions"] },
|
||||
{ name: "Details", key: "details", fields: ["name", "description"] },
|
||||
{ name: "Certificates", key: "certificates", fields: ["certificateIds"] },
|
||||
{ name: "Review", key: "review", fields: [] }
|
||||
];
|
||||
const getFormTabs = (
|
||||
destination: PkiSync
|
||||
): { name: string; key: string; fields: (keyof TPkiSyncForm)[] }[] => {
|
||||
const baseTabs = [
|
||||
{
|
||||
name: "Destination",
|
||||
key: "destination",
|
||||
fields: ["connection", "destinationConfig"] as (keyof TPkiSyncForm)[]
|
||||
},
|
||||
{ name: "Sync Options", key: "options", fields: ["syncOptions"] as (keyof TPkiSyncForm)[] }
|
||||
];
|
||||
|
||||
if (destination === PkiSync.Chef || destination === PkiSync.AwsSecretsManager) {
|
||||
baseTabs.push({
|
||||
name: "Mappings",
|
||||
key: "mappings",
|
||||
fields: ["syncOptions"] as (keyof TPkiSyncForm)[]
|
||||
});
|
||||
}
|
||||
|
||||
baseTabs.push(
|
||||
{ name: "Details", key: "details", fields: ["name", "description"] as (keyof TPkiSyncForm)[] },
|
||||
{
|
||||
name: "Certificates",
|
||||
key: "certificates",
|
||||
fields: ["certificateIds"] as (keyof TPkiSyncForm)[]
|
||||
},
|
||||
{ name: "Review", key: "review", fields: [] as (keyof TPkiSyncForm)[] }
|
||||
);
|
||||
|
||||
return baseTabs;
|
||||
};
|
||||
|
||||
export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialData }: Props) => {
|
||||
const createPkiSync = useCreatePkiSync();
|
||||
@@ -42,6 +67,7 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
const FORM_TABS = getFormTabs(destination);
|
||||
|
||||
const { syncOption } = usePkiSyncOption(destination);
|
||||
|
||||
@@ -55,7 +81,19 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
|
||||
canImportCertificates: false,
|
||||
canRemoveCertificates: false,
|
||||
preserveArn: true,
|
||||
certificateNameSchema: syncOption?.defaultCertificateNameSchema
|
||||
certificateNameSchema: syncOption?.defaultCertificateNameSchema,
|
||||
...((destination === PkiSync.Chef || destination === PkiSync.AwsSecretsManager) && {
|
||||
fieldMappings: {
|
||||
certificate: "certificate",
|
||||
privateKey: "private_key",
|
||||
certificateChain: "certificate_chain",
|
||||
caCertificate: "ca_certificate"
|
||||
}
|
||||
}),
|
||||
...(destination === PkiSync.AwsSecretsManager && {
|
||||
preserveSecretOnRenewal: true,
|
||||
updateExistingCertificates: true
|
||||
})
|
||||
},
|
||||
...initialData
|
||||
} as Partial<TPkiSyncForm>,
|
||||
@@ -167,10 +205,10 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
|
||||
);
|
||||
|
||||
return (
|
||||
<form className={twMerge(isFinalStep && "max-h-[70vh] overflow-y-auto")}>
|
||||
<form className="flex max-h-[70vh] flex-col overflow-hidden">
|
||||
<FormProvider {...formMethods}>
|
||||
<Tab.Group selectedIndex={selectedTabIndex} onChange={setSelectedTabIndex}>
|
||||
<Tab.List className="-pb-1 mb-6 w-full border-b-2 border-mineshaft-600">
|
||||
<Tab.List className="-pb-1 mb-6 w-full flex-shrink-0 border-b-2 border-mineshaft-600">
|
||||
{FORM_TABS.map((tab, index) => (
|
||||
<Tab
|
||||
onClick={async (e) => {
|
||||
@@ -191,11 +229,11 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
<Tab.Panels className="flex-1 overflow-y-auto">
|
||||
<Tab.Panel className="max-h-full overflow-y-auto">
|
||||
<PkiSyncDestinationFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<Tab.Panel className="max-h-full overflow-y-auto">
|
||||
<PkiSyncOptionsFields destination={destination} />
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -225,20 +263,25 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
|
||||
}}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
{(destination === PkiSync.Chef || destination === PkiSync.AwsSecretsManager) && (
|
||||
<Tab.Panel className="max-h-full overflow-y-auto">
|
||||
<PkiSyncFieldMappingsFields destination={destination} />
|
||||
</Tab.Panel>
|
||||
)}
|
||||
<Tab.Panel className="max-h-full overflow-y-auto">
|
||||
<PkiSyncDetailsFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<Tab.Panel className="max-h-full overflow-y-auto">
|
||||
<PkiSyncCertificatesFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<Tab.Panel className="max-h-full overflow-y-auto">
|
||||
<PkiSyncReviewFields />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</FormProvider>
|
||||
|
||||
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
||||
<div className="mt-4 flex w-full flex-shrink-0 flex-row-reverse justify-between gap-4 border-t border-mineshaft-600 pt-4">
|
||||
<Button onClick={handleNext} colorSchema="secondary">
|
||||
{isFinalStep ? "Create Sync" : "Next"}
|
||||
</Button>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { TPkiSync, useUpdatePkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
import { TUpdatePkiSyncForm, UpdatePkiSyncFormSchema } from "./schemas/pki-sync-schema";
|
||||
import { PkiSyncDestinationFields } from "./PkiSyncDestinationFields";
|
||||
import { PkiSyncDetailsFields } from "./PkiSyncDetailsFields";
|
||||
import { PkiSyncFieldMappingsFields } from "./PkiSyncFieldMappingsFields";
|
||||
import { PkiSyncOptionsFields } from "./PkiSyncOptionsFields";
|
||||
import { PkiSyncSourceFields } from "./PkiSyncSourceFields";
|
||||
|
||||
@@ -66,6 +67,9 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => {
|
||||
case PkiSyncEditFields.Options:
|
||||
Component = <PkiSyncOptionsFields destination={pkiSync.destination} />;
|
||||
break;
|
||||
case PkiSyncEditFields.Mappings:
|
||||
Component = <PkiSyncFieldMappingsFields destination={pkiSync.destination} />;
|
||||
break;
|
||||
case PkiSyncEditFields.Source:
|
||||
Component = <PkiSyncSourceFields />;
|
||||
break;
|
||||
|
||||
@@ -4,7 +4,9 @@ import { PkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
|
||||
import { AwsCertificateManagerPkiSyncFields } from "./AwsCertificateManagerPkiSyncFields";
|
||||
import { AwsSecretsManagerPkiSyncFields } from "./AwsSecretsManagerPkiSyncFields";
|
||||
import { AzureKeyVaultPkiSyncFields } from "./AzureKeyVaultPkiSyncFields";
|
||||
import { ChefPkiSyncFields } from "./ChefPkiSyncFields";
|
||||
|
||||
export const PkiSyncDestinationFields = () => {
|
||||
const { watch } = useFormContext<TPkiSyncForm>();
|
||||
@@ -16,6 +18,10 @@ export const PkiSyncDestinationFields = () => {
|
||||
return <AzureKeyVaultPkiSyncFields />;
|
||||
case PkiSync.AwsCertificateManager:
|
||||
return <AwsCertificateManagerPkiSyncFields />;
|
||||
case PkiSync.AwsSecretsManager:
|
||||
return <AwsSecretsManagerPkiSyncFields />;
|
||||
case PkiSync.Chef:
|
||||
return <ChefPkiSyncFields />;
|
||||
default:
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-md border border-red-500 bg-red-100 p-4 text-red-700">
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { FormControl, Input } from "@app/components/v2";
|
||||
import { PkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
|
||||
|
||||
type Props = {
|
||||
destination?: PkiSync;
|
||||
};
|
||||
|
||||
export const PkiSyncFieldMappingsFields = ({ destination }: Props) => {
|
||||
const { control, watch } = useFormContext<TPkiSyncForm>();
|
||||
const currentDestination = destination || watch("destination");
|
||||
|
||||
if (currentDestination !== PkiSync.Chef && currentDestination !== PkiSync.AwsSecretsManager) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="mb-4 text-sm text-bunker-300">
|
||||
Configure how certificate fields are mapped to your{" "}
|
||||
{currentDestination === PkiSync.Chef ? "Chef data bag items" : "AWS secrets"}.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.fieldMappings.certificate"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Certificate Field"
|
||||
tooltipText={`The field name used to store the certificate content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
|
||||
>
|
||||
<Input {...field} placeholder="certificate" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.fieldMappings.privateKey"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Private Key Field"
|
||||
tooltipText={`The field name used to store the private key content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
|
||||
>
|
||||
<Input {...field} placeholder="private_key" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.fieldMappings.certificateChain"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Certificate Chain Field"
|
||||
tooltipText={`The field name used to store the certificate chain content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
|
||||
>
|
||||
<Input {...field} placeholder="certificate_chain" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.fieldMappings.caCertificate"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="CA Certificate Field"
|
||||
tooltipText={`The field name used to store the CA certificate content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
|
||||
>
|
||||
<Input {...field} placeholder="ca_certificate" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-lg border border-mineshaft-600 bg-mineshaft-800 p-4">
|
||||
<h4 className="mb-2 text-sm font-medium text-mineshaft-100">Preview JSON Structure</h4>
|
||||
<pre className="text-xs text-bunker-300">
|
||||
{`{
|
||||
"id": "certificate-item-name",
|
||||
"${watch("syncOptions.fieldMappings.certificate") || "certificate"}": "<certificate-content>",
|
||||
"${watch("syncOptions.fieldMappings.privateKey") || "private_key"}": "<private-key-content>",
|
||||
"${watch("syncOptions.fieldMappings.certificateChain") || "certificate_chain"}": "<certificate-chain-content>",
|
||||
"${watch("syncOptions.fieldMappings.caCertificate") || "ca_certificate"}": "<ca-certificate-content>"
|
||||
}`}
|
||||
</pre>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -95,6 +95,48 @@ export const PkiSyncOptionsFields = ({ destination }: Props) => {
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.includeRootCa"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
|
||||
id="include-root-ca"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
onCheckedChange={onChange}
|
||||
isChecked={value}
|
||||
>
|
||||
<p>
|
||||
Include Root CA in Certificate Chain{" "}
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={
|
||||
<>
|
||||
<p>
|
||||
When enabled, the full certificate chain including the root CA will be
|
||||
synced to the destination.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
When disabled, the root CA will be excluded from the certificate chain
|
||||
during sync operations, reducing the size of the synced certificate chain.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
Most applications and services work correctly with intermediate certificates
|
||||
only, as they can validate the trust chain up to a root CA they already
|
||||
trust.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
|
||||
</Tooltip>
|
||||
</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
{currentDestination === PkiSync.AwsCertificateManager && (
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -183,6 +225,97 @@ export const PkiSyncOptionsFields = ({ destination }: Props) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentDestination === PkiSync.AwsSecretsManager && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.preserveSecretOnRenewal"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
|
||||
id="preserve-secret-on-renewal"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
onCheckedChange={onChange}
|
||||
isChecked={value}
|
||||
>
|
||||
<p>
|
||||
Preserve Secret on Renewal{" "}
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={
|
||||
<>
|
||||
<p>
|
||||
<strong>Only applies to certificate renewals:</strong> When a certificate
|
||||
is renewed in Infisical, this option controls how the renewed certificate
|
||||
is handled in AWS Secrets Manager.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
When enabled, the renewed certificate will update the existing secret,
|
||||
preserving the same secret name and ARN. This allows consuming services to
|
||||
continue using the same secret reference without requiring updates.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
When disabled, the renewed certificate will be created as a new secret
|
||||
with a new name, and the old secret will be removed.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
|
||||
</Tooltip>
|
||||
</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentDestination === PkiSync.Chef && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.preserveItemOnRenewal"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
|
||||
id="preserve-item-on-renewal"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
onCheckedChange={onChange}
|
||||
isChecked={value}
|
||||
>
|
||||
<p>
|
||||
Preserve Data Bag Item on Renewal{" "}
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={
|
||||
<>
|
||||
<p>
|
||||
<strong>Only applies to certificate renewals:</strong> When a certificate
|
||||
is renewed in Infisical, this option controls how the renewed certificate
|
||||
is handled in Chef.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
When enabled, the renewed certificate will update the existing data bag
|
||||
item, preserving the same item name. This allows consuming services to
|
||||
continue using the same data bag item without requiring updates to Chef
|
||||
cookbooks or recipes.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
When disabled, the renewed certificate will be created as a new data bag
|
||||
item with a new name, and the old item will be removed.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
|
||||
</Tooltip>
|
||||
</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncOptions.certificateNameSchema"
|
||||
|
||||
@@ -7,6 +7,7 @@ import { BasePkiSyncSchema } from "./base-pki-sync-schema";
|
||||
const AwsCertificateManagerSyncOptionsSchema = z.object({
|
||||
canImportCertificates: z.boolean().default(false),
|
||||
canRemoveCertificates: z.boolean().default(false),
|
||||
includeRootCa: z.boolean().default(false),
|
||||
preserveArn: z.boolean().default(true),
|
||||
certificateNameSchema: z
|
||||
.string()
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { PkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import { BasePkiSyncSchema } from "./base-pki-sync-schema";
|
||||
|
||||
const AwsSecretsManagerFieldMappingsSchema = z.object({
|
||||
certificate: z.string().min(1, "Certificate field name is required").default("certificate"),
|
||||
privateKey: z.string().min(1, "Private key field name is required").default("private_key"),
|
||||
certificateChain: z
|
||||
.string()
|
||||
.min(1, "Certificate chain field name is required")
|
||||
.default("certificate_chain"),
|
||||
caCertificate: z
|
||||
.string()
|
||||
.min(1, "CA certificate field name is required")
|
||||
.default("ca_certificate")
|
||||
});
|
||||
|
||||
const AwsSecretsManagerSyncOptionsSchema = z.object({
|
||||
canImportCertificates: z.boolean().default(false),
|
||||
canRemoveCertificates: z.boolean().default(true),
|
||||
includeRootCa: z.boolean().default(false),
|
||||
preserveSecretOnRenewal: z.boolean().default(true),
|
||||
updateExistingCertificates: z.boolean().default(true),
|
||||
certificateNameSchema: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) => {
|
||||
if (!val) return true;
|
||||
|
||||
const allowedOptionalPlaceholders = [
|
||||
"{{environment}}",
|
||||
"{{profileId}}",
|
||||
"{{commonName}}",
|
||||
"{{friendlyName}}"
|
||||
];
|
||||
|
||||
const allowedPlaceholdersRegexPart = ["{{certificateId}}", ...allowedOptionalPlaceholders]
|
||||
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
|
||||
.join("|");
|
||||
|
||||
const allowedContentRegex = new RegExp(
|
||||
`^([a-zA-Z0-9_\\-]|${allowedPlaceholdersRegexPart})*$`
|
||||
);
|
||||
const contentIsValid = allowedContentRegex.test(val);
|
||||
|
||||
if (val.trim()) {
|
||||
const certificateIdRegex = /\{\{certificateId\}\}/;
|
||||
const certificateIdIsPresent = certificateIdRegex.test(val);
|
||||
return contentIsValid && certificateIdIsPresent;
|
||||
}
|
||||
|
||||
return contentIsValid;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Certificate name schema must include exactly one {{certificateId}} placeholder. It can also include {{environment}}, {{profileId}}, {{commonName}}, or {{friendlyName}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), hyphens (-), and underscores (_) are allowed besides the placeholders."
|
||||
}
|
||||
),
|
||||
fieldMappings: AwsSecretsManagerFieldMappingsSchema.optional().default({
|
||||
certificate: "certificate",
|
||||
privateKey: "private_key",
|
||||
certificateChain: "certificate_chain",
|
||||
caCertificate: "ca_certificate"
|
||||
})
|
||||
});
|
||||
|
||||
export const AwsSecretsManagerPkiSyncDestinationSchema = BasePkiSyncSchema(
|
||||
AwsSecretsManagerSyncOptionsSchema
|
||||
).merge(
|
||||
z.object({
|
||||
destination: z.literal(PkiSync.AwsSecretsManager),
|
||||
destinationConfig: z.object({
|
||||
region: z.string().min(1, "AWS region is required")
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
export const UpdateAwsSecretsManagerPkiSyncDestinationSchema =
|
||||
AwsSecretsManagerPkiSyncDestinationSchema.partial().merge(
|
||||
z.object({
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Name is required")
|
||||
.max(255, "Name must be less than 255 characters"),
|
||||
destination: z.literal(PkiSync.AwsSecretsManager),
|
||||
connection: z.object({
|
||||
id: z.string().uuid("Invalid connection ID format"),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "Connection name is required")
|
||||
.max(255, "Connection name must be less than 255 characters")
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -7,6 +7,7 @@ import { BasePkiSyncSchema } from "./base-pki-sync-schema";
|
||||
const AzureKeyVaultSyncOptionsSchema = z.object({
|
||||
canImportCertificates: z.boolean().default(false),
|
||||
canRemoveCertificates: z.boolean().default(true),
|
||||
includeRootCa: z.boolean().default(false),
|
||||
enableVersioning: z.boolean().default(true),
|
||||
certificateNameSchema: z
|
||||
.string()
|
||||
|
||||
@@ -6,6 +6,7 @@ export const BasePkiSyncSchema = <T extends AnyZodObject | undefined = undefined
|
||||
const baseSyncOptionsSchema = z.object({
|
||||
canImportCertificates: z.boolean().default(false),
|
||||
canRemoveCertificates: z.boolean().default(false),
|
||||
includeRootCa: z.boolean().default(false),
|
||||
certificateNameSchema: z
|
||||
.string()
|
||||
.optional()
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { PkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import { BasePkiSyncSchema } from "./base-pki-sync-schema";
|
||||
|
||||
const ChefFieldMappingsSchema = z.object({
|
||||
certificate: z.string().min(1, "Certificate field name is required").default("certificate"),
|
||||
privateKey: z.string().min(1, "Private key field name is required").default("private_key"),
|
||||
certificateChain: z
|
||||
.string()
|
||||
.min(1, "Certificate chain field name is required")
|
||||
.default("certificate_chain"),
|
||||
caCertificate: z
|
||||
.string()
|
||||
.min(1, "CA certificate field name is required")
|
||||
.default("ca_certificate")
|
||||
});
|
||||
|
||||
const ChefSyncOptionsSchema = z.object({
|
||||
canImportCertificates: z.boolean().default(false),
|
||||
canRemoveCertificates: z.boolean().default(true),
|
||||
includeRootCa: z.boolean().default(false),
|
||||
preserveItemOnRenewal: z.boolean().default(true),
|
||||
updateExistingCertificates: z.boolean().default(true),
|
||||
certificateNameSchema: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) => {
|
||||
if (!val) return true;
|
||||
|
||||
const allowedOptionalPlaceholders = [
|
||||
"{{environment}}",
|
||||
"{{profileId}}",
|
||||
"{{commonName}}",
|
||||
"{{friendlyName}}"
|
||||
];
|
||||
|
||||
const allowedPlaceholdersRegexPart = ["{{certificateId}}", ...allowedOptionalPlaceholders]
|
||||
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
|
||||
.join("|");
|
||||
|
||||
const allowedContentRegex = new RegExp(
|
||||
`^([a-zA-Z0-9_\\-]|${allowedPlaceholdersRegexPart})*$`
|
||||
);
|
||||
const contentIsValid = allowedContentRegex.test(val);
|
||||
|
||||
if (val.trim()) {
|
||||
const certificateIdRegex = /\{\{certificateId\}\}/;
|
||||
const certificateIdIsPresent = certificateIdRegex.test(val);
|
||||
return contentIsValid && certificateIdIsPresent;
|
||||
}
|
||||
|
||||
return contentIsValid;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Certificate item name schema must include exactly one {{certificateId}} placeholder. It can also include {{environment}}, {{profileId}}, {{commonName}}, or {{friendlyName}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), hyphens (-), and underscores (_) are allowed besides the placeholders."
|
||||
}
|
||||
),
|
||||
fieldMappings: ChefFieldMappingsSchema.optional().default({
|
||||
certificate: "certificate",
|
||||
privateKey: "private_key",
|
||||
certificateChain: "certificate_chain",
|
||||
caCertificate: "ca_certificate"
|
||||
})
|
||||
});
|
||||
|
||||
export const ChefPkiSyncDestinationSchema = BasePkiSyncSchema(ChefSyncOptionsSchema).merge(
|
||||
z.object({
|
||||
destination: z.literal(PkiSync.Chef),
|
||||
destinationConfig: z.object({
|
||||
dataBagName: z
|
||||
.string()
|
||||
.min(1, "Data bag name is required")
|
||||
.max(255, "Data bag name must be less than 255 characters")
|
||||
.regex(
|
||||
/^[a-zA-Z0-9_-]+$/,
|
||||
"Data bag name can only contain alphanumeric characters, underscores, and hyphens"
|
||||
)
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
export const UpdateChefPkiSyncDestinationSchema = ChefPkiSyncDestinationSchema.partial().merge(
|
||||
z.object({
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Name is required")
|
||||
.max(255, "Name must be less than 255 characters"),
|
||||
destination: z.literal(PkiSync.Chef),
|
||||
connection: z.object({
|
||||
id: z.string().uuid("Invalid connection ID format"),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "Connection name is required")
|
||||
.max(255, "Connection name must be less than 255 characters")
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -4,19 +4,31 @@ import {
|
||||
AwsCertificateManagerPkiSyncDestinationSchema,
|
||||
UpdateAwsCertificateManagerPkiSyncDestinationSchema
|
||||
} from "./aws-certificate-manager-pki-sync-destination-schema";
|
||||
import {
|
||||
AwsSecretsManagerPkiSyncDestinationSchema,
|
||||
UpdateAwsSecretsManagerPkiSyncDestinationSchema
|
||||
} from "./aws-secrets-manager-pki-sync-destination-schema";
|
||||
import {
|
||||
AzureKeyVaultPkiSyncDestinationSchema,
|
||||
UpdateAzureKeyVaultPkiSyncDestinationSchema
|
||||
} from "./azure-key-vault-pki-sync-destination-schema";
|
||||
import {
|
||||
ChefPkiSyncDestinationSchema,
|
||||
UpdateChefPkiSyncDestinationSchema
|
||||
} from "./chef-pki-sync-destination-schema";
|
||||
|
||||
const PkiSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||
AzureKeyVaultPkiSyncDestinationSchema,
|
||||
AwsCertificateManagerPkiSyncDestinationSchema
|
||||
AwsCertificateManagerPkiSyncDestinationSchema,
|
||||
AwsSecretsManagerPkiSyncDestinationSchema,
|
||||
ChefPkiSyncDestinationSchema
|
||||
]);
|
||||
|
||||
const UpdatePkiSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||
UpdateAzureKeyVaultPkiSyncDestinationSchema,
|
||||
UpdateAwsCertificateManagerPkiSyncDestinationSchema
|
||||
UpdateAwsCertificateManagerPkiSyncDestinationSchema,
|
||||
UpdateAwsSecretsManagerPkiSyncDestinationSchema,
|
||||
UpdateChefPkiSyncDestinationSchema
|
||||
]);
|
||||
|
||||
export const PkiSyncFormSchema = PkiSyncUnionSchema;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export enum PkiSyncEditFields {
|
||||
Details = "details",
|
||||
Options = "options",
|
||||
Mappings = "mappings",
|
||||
Source = "source",
|
||||
Destination = "destination"
|
||||
}
|
||||
|
||||
@@ -15,10 +15,20 @@ export const PKI_SYNC_MAP: Record<
|
||||
[PkiSync.AwsCertificateManager]: {
|
||||
name: "AWS Certificate Manager",
|
||||
image: "Amazon Web Services.png"
|
||||
},
|
||||
[PkiSync.AwsSecretsManager]: {
|
||||
name: "AWS Secrets Manager",
|
||||
image: "Amazon Web Services.png"
|
||||
},
|
||||
[PkiSync.Chef]: {
|
||||
name: "Chef",
|
||||
image: "Chef.png"
|
||||
}
|
||||
};
|
||||
|
||||
export const PKI_SYNC_CONNECTION_MAP: Record<PkiSync, AppConnection> = {
|
||||
[PkiSync.AzureKeyVault]: AppConnection.AzureKeyVault,
|
||||
[PkiSync.AwsCertificateManager]: AppConnection.AWS
|
||||
[PkiSync.AwsCertificateManager]: AppConnection.AWS,
|
||||
[PkiSync.AwsSecretsManager]: AppConnection.AWS,
|
||||
[PkiSync.Chef]: AppConnection.Chef
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export enum PkiSync {
|
||||
AzureKeyVault = "azure-key-vault",
|
||||
AwsCertificateManager = "aws-certificate-manager"
|
||||
AwsCertificateManager = "aws-certificate-manager",
|
||||
AwsSecretsManager = "aws-secrets-manager",
|
||||
Chef = "chef"
|
||||
}
|
||||
|
||||
export enum PkiSyncStatus {
|
||||
@@ -12,7 +14,7 @@ export enum PkiSyncStatus {
|
||||
|
||||
export enum CertificateSyncStatus {
|
||||
Pending = "pending",
|
||||
Syncing = "syncing",
|
||||
Running = "running",
|
||||
Succeeded = "succeeded",
|
||||
Failed = "failed"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { PkiSync } from "../enums";
|
||||
import { TRootPkiSync } from "./common";
|
||||
|
||||
export type TAwsSecretsManagerFieldMappings = {
|
||||
certificate: string;
|
||||
privateKey: string;
|
||||
certificateChain: string;
|
||||
caCertificate: string;
|
||||
};
|
||||
|
||||
export type TAwsSecretsManagerPkiSync = TRootPkiSync & {
|
||||
destination: PkiSync.AwsSecretsManager;
|
||||
destinationConfig: {
|
||||
region: string;
|
||||
keyId?: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.AWS;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
syncOptions: TRootPkiSync["syncOptions"] & {
|
||||
fieldMappings?: TAwsSecretsManagerFieldMappings;
|
||||
preserveSecretOnRenewal?: boolean;
|
||||
updateExistingCertificates?: boolean;
|
||||
};
|
||||
};
|
||||
16
frontend/src/hooks/api/pkiSyncs/types/chef-sync.ts
Normal file
16
frontend/src/hooks/api/pkiSyncs/types/chef-sync.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { PkiSync } from "../enums";
|
||||
import { TRootPkiSync } from "./common";
|
||||
|
||||
export type TChefPkiSync = TRootPkiSync & {
|
||||
destination: PkiSync.Chef;
|
||||
destinationConfig: {
|
||||
dataBagName: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.Chef;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -2,11 +2,23 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { CertificateSyncStatus, PkiSyncStatus } from "../enums";
|
||||
|
||||
export type TChefFieldMappings = {
|
||||
certificate: string;
|
||||
privateKey: string;
|
||||
certificateChain: string;
|
||||
caCertificate: string;
|
||||
};
|
||||
|
||||
export type RootPkiSyncOptions = {
|
||||
canImportCertificates: boolean;
|
||||
canRemoveCertificates: boolean;
|
||||
certificateNamePrefix?: string;
|
||||
certificateNameSchema?: string;
|
||||
preserveArn?: boolean;
|
||||
enableVersioning?: boolean;
|
||||
preserveItemOnRenewal?: boolean;
|
||||
updateExistingCertificates?: boolean;
|
||||
fieldMappings?: TChefFieldMappings;
|
||||
};
|
||||
|
||||
export type TRootPkiSync = {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { PkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import { TAwsCertificateManagerPkiSync } from "./aws-certificate-manager-sync";
|
||||
import { TAwsSecretsManagerPkiSync } from "./aws-secrets-manager-sync";
|
||||
import { TAzureKeyVaultPkiSync } from "./azure-key-vault-sync";
|
||||
import { TChefPkiSync } from "./chef-sync";
|
||||
|
||||
export type TPkiSyncOption = {
|
||||
name: string;
|
||||
@@ -16,7 +18,11 @@ export type TPkiSyncOption = {
|
||||
minCertificateNameLength?: number;
|
||||
};
|
||||
|
||||
export type TPkiSync = TAzureKeyVaultPkiSync | TAwsCertificateManagerPkiSync;
|
||||
export type TPkiSync =
|
||||
| TAzureKeyVaultPkiSync
|
||||
| TAwsCertificateManagerPkiSync
|
||||
| TAwsSecretsManagerPkiSync
|
||||
| TChefPkiSync;
|
||||
|
||||
export type TListPkiSyncs = { pkiSyncs: TPkiSync[] };
|
||||
|
||||
@@ -31,6 +37,17 @@ type TCreatePkiSyncDTOBase = {
|
||||
canRemoveCertificates: boolean;
|
||||
certificateNamePrefix?: string;
|
||||
certificateNameSchema?: string;
|
||||
preserveArn?: boolean;
|
||||
enableVersioning?: boolean;
|
||||
preserveItemOnRenewal?: boolean;
|
||||
updateExistingCertificates?: boolean;
|
||||
preserveSecretOnRenewal?: boolean;
|
||||
fieldMappings?: {
|
||||
certificate: string;
|
||||
privateKey: string;
|
||||
certificateChain: string;
|
||||
caCertificate: string;
|
||||
};
|
||||
};
|
||||
isAutoSyncEnabled: boolean;
|
||||
subscriberId?: string | null;
|
||||
@@ -43,6 +60,7 @@ export type TCreatePkiSyncDTO = TCreatePkiSyncDTOBase & {
|
||||
destinationConfig: {
|
||||
vaultBaseUrl?: string;
|
||||
region?: string;
|
||||
dataBagName?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -76,5 +94,7 @@ export type TTriggerPkiSyncRemoveCertificatesDTO = {
|
||||
};
|
||||
|
||||
export * from "./aws-certificate-manager-sync";
|
||||
export * from "./aws-secrets-manager-sync";
|
||||
export * from "./azure-key-vault-sync";
|
||||
export * from "./chef-sync";
|
||||
export * from "./common";
|
||||
|
||||
@@ -354,6 +354,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-mineshaft-300">Certificate Details</h4>
|
||||
<p className="text-sm text-mineshaft-400">Serial Number: {cert.serialNumber}</p>
|
||||
<p className="text-sm text-mineshaft-400">Certificate Id: {cert.id}</p>
|
||||
<p className="text-sm text-mineshaft-400">Common Name: {cert.commonName}</p>
|
||||
<p className="text-sm text-mineshaft-400">Status: {cert.status}</p>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
PkiSyncCertificatesSection,
|
||||
PkiSyncDestinationSection,
|
||||
PkiSyncDetailsSection,
|
||||
PkiSyncFieldMappingsSection,
|
||||
PkiSyncOptionsSection
|
||||
} from "./components";
|
||||
|
||||
@@ -63,6 +64,7 @@ const PageContent = () => {
|
||||
|
||||
const handleEditDetails = () => handlePopUpOpen("editSync", PkiSyncEditFields.Details);
|
||||
const handleEditOptions = () => handlePopUpOpen("editSync", PkiSyncEditFields.Options);
|
||||
const handleEditMappings = () => handlePopUpOpen("editSync", PkiSyncEditFields.Mappings);
|
||||
const handleEditDestination = () => handlePopUpOpen("editSync", PkiSyncEditFields.Destination);
|
||||
|
||||
return (
|
||||
@@ -103,6 +105,7 @@ const PageContent = () => {
|
||||
<div className="mr-4 flex w-72 flex-col gap-4">
|
||||
<PkiSyncDetailsSection pkiSync={pkiSync} onEditDetails={handleEditDetails} />
|
||||
<PkiSyncOptionsSection pkiSync={pkiSync} onEditOptions={handleEditOptions} />
|
||||
<PkiSyncFieldMappingsSection pkiSync={pkiSync} onEditMappings={handleEditMappings} />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col gap-4">
|
||||
<PkiSyncDestinationSection
|
||||
|
||||
@@ -42,14 +42,14 @@ type Props = {
|
||||
const getSyncStatusVariant = (status?: CertificateSyncStatus | null) => {
|
||||
if (status === CertificateSyncStatus.Succeeded) return "success";
|
||||
if (status === CertificateSyncStatus.Failed) return "danger";
|
||||
if (status === CertificateSyncStatus.Syncing) return "neutral";
|
||||
if (status === CertificateSyncStatus.Running) return "neutral";
|
||||
return "project";
|
||||
};
|
||||
|
||||
const getSyncStatusText = (status?: CertificateSyncStatus | null) => {
|
||||
if (status === CertificateSyncStatus.Succeeded) return "Synced";
|
||||
if (status === CertificateSyncStatus.Failed) return "Failed";
|
||||
if (status === CertificateSyncStatus.Syncing) return "Syncing";
|
||||
if (status === CertificateSyncStatus.Running) return "Syncing";
|
||||
if (status === CertificateSyncStatus.Pending) return "Pending";
|
||||
return "Unknown";
|
||||
};
|
||||
|
||||
@@ -13,7 +13,9 @@ import { PkiSync, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
import {
|
||||
AwsCertificateManagerPkiSyncDestinationSection,
|
||||
AzureKeyVaultPkiSyncDestinationSection
|
||||
AwsSecretsManagerPkiSyncDestinationSection,
|
||||
AzureKeyVaultPkiSyncDestinationSection,
|
||||
ChefPkiSyncDestinationSection
|
||||
} from "./PkiSyncDestinationSection/index";
|
||||
|
||||
const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => (
|
||||
@@ -38,9 +40,15 @@ export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props)
|
||||
case PkiSync.AwsCertificateManager:
|
||||
DestinationComponents = <AwsCertificateManagerPkiSyncDestinationSection pkiSync={pkiSync} />;
|
||||
break;
|
||||
case PkiSync.AwsSecretsManager:
|
||||
DestinationComponents = <AwsSecretsManagerPkiSyncDestinationSection pkiSync={pkiSync} />;
|
||||
break;
|
||||
case PkiSync.AzureKeyVault:
|
||||
DestinationComponents = <AzureKeyVaultPkiSyncDestinationSection pkiSync={pkiSync} />;
|
||||
break;
|
||||
case PkiSync.Chef:
|
||||
DestinationComponents = <ChefPkiSyncDestinationSection pkiSync={pkiSync} />;
|
||||
break;
|
||||
default:
|
||||
// For future destinations, return null (no additional fields to show)
|
||||
DestinationComponents = null;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { TAwsSecretsManagerPkiSync, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => (
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-medium text-mineshaft-300">{label}</p>
|
||||
<div className="text-sm text-mineshaft-300">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
type Props = {
|
||||
pkiSync: TPkiSync;
|
||||
};
|
||||
|
||||
export const AwsSecretsManagerPkiSyncDestinationSection = ({ pkiSync }: Props) => {
|
||||
const awsSecretsManagerPkiSync = pkiSync as TAwsSecretsManagerPkiSync;
|
||||
const { destinationConfig } = awsSecretsManagerPkiSync;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="AWS Region">
|
||||
{destinationConfig.region || "us-east-1"}
|
||||
</GenericFieldLabel>
|
||||
{destinationConfig.keyId && (
|
||||
<GenericFieldLabel label="KMS Key">{destinationConfig.keyId}</GenericFieldLabel>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => (
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-medium text-mineshaft-300">{label}</p>
|
||||
<div className="text-sm text-mineshaft-300">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
type Props = {
|
||||
pkiSync: TPkiSync;
|
||||
};
|
||||
|
||||
export const ChefPkiSyncDestinationSection = ({ pkiSync }: Props) => {
|
||||
const dataBagName =
|
||||
pkiSync.destinationConfig && "dataBagName" in pkiSync.destinationConfig
|
||||
? pkiSync.destinationConfig.dataBagName
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<GenericFieldLabel label="Chef Data Bag Name">
|
||||
{dataBagName || "Not specified"}
|
||||
</GenericFieldLabel>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,4 @@
|
||||
export { AwsCertificateManagerPkiSyncDestinationSection } from "./AwsCertificateManagerPkiSyncDestinationSection";
|
||||
export { AwsSecretsManagerPkiSyncDestinationSection } from "./AwsSecretsManagerPkiSyncDestinationSection";
|
||||
export { AzureKeyVaultPkiSyncDestinationSection } from "./AzureKeyVaultPkiSyncDestinationSection";
|
||||
export { ChefPkiSyncDestinationSection } from "./ChefPkiSyncDestinationSection";
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { subject } from "@casl/ability";
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { IconButton } from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v3";
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { PkiSync, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||
|
||||
const GenericFieldLabel = ({
|
||||
label,
|
||||
children,
|
||||
labelClassName
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
labelClassName?: string;
|
||||
}) => (
|
||||
<div className="mb-3">
|
||||
<p className={`mb-1 text-sm font-medium text-mineshaft-300 ${labelClassName || ""}`}>{label}</p>
|
||||
<div className="text-sm text-mineshaft-400">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
type Props = {
|
||||
pkiSync: TPkiSync;
|
||||
onEditMappings: VoidFunction;
|
||||
};
|
||||
|
||||
export const PkiSyncFieldMappingsSection = ({ pkiSync, onEditMappings }: Props) => {
|
||||
if (pkiSync.destination !== PkiSync.Chef && pkiSync.destination !== PkiSync.AwsSecretsManager) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fieldMappings = pkiSync.syncOptions?.fieldMappings;
|
||||
|
||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||
subscriberId: pkiSync.subscriberId || ""
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="text-lg font-medium text-mineshaft-100">Field Mappings</h3>
|
||||
<ProjectPermissionCan I={ProjectPermissionPkiSyncActions.Edit} a={permissionSubject}>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="Edit field mappings"
|
||||
onClick={onEditMappings}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<div className="space-y-3">
|
||||
<GenericFieldLabel label="Certificate Field">
|
||||
<Badge variant="neutral" className="max-w-full truncate">
|
||||
{fieldMappings?.certificate || "certificate"}
|
||||
</Badge>
|
||||
</GenericFieldLabel>
|
||||
|
||||
<GenericFieldLabel label="Private Key Field">
|
||||
<Badge variant="neutral" className="max-w-full truncate">
|
||||
{fieldMappings?.privateKey || "private_key"}
|
||||
</Badge>
|
||||
</GenericFieldLabel>
|
||||
|
||||
<GenericFieldLabel label="Certificate Chain Field">
|
||||
<Badge variant="neutral" className="max-w-full truncate">
|
||||
{fieldMappings?.certificateChain || "certificate_chain"}
|
||||
</Badge>
|
||||
</GenericFieldLabel>
|
||||
|
||||
<GenericFieldLabel label="CA Certificate Field">
|
||||
<Badge variant="neutral" className="max-w-full truncate">
|
||||
{fieldMappings?.caCertificate || "ca_certificate"}
|
||||
</Badge>
|
||||
</GenericFieldLabel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,5 +3,6 @@ export { PkiSyncAuditLogsSection } from "./PkiSyncAuditLogsSection";
|
||||
export { PkiSyncCertificatesSection } from "./PkiSyncCertificatesSection";
|
||||
export { PkiSyncDestinationSection } from "./PkiSyncDestinationSection";
|
||||
export { PkiSyncDetailsSection } from "./PkiSyncDetailsSection";
|
||||
export { PkiSyncFieldMappingsSection } from "./PkiSyncFieldMappingsSection";
|
||||
export { PkiSyncOptionsSection } from "./PkiSyncOptionsSection";
|
||||
export { PkiSyncSourceSection } from "./PkiSyncSourceSection";
|
||||
|
||||
Reference in New Issue
Block a user