mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
improvement: address feedback
This commit is contained in:
@@ -24,9 +24,25 @@ const listAwsKmsKeys = async (
|
||||
region
|
||||
});
|
||||
|
||||
const { Aliases = [] } = await awsKms.listAliases({ Limit: 100 }).promise();
|
||||
const aliasEntries: AWS.KMS.AliasList = [];
|
||||
let aliasMarker: string | undefined;
|
||||
do {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const response = await awsKms.listAliases({ Limit: 100, Marker: aliasMarker }).promise();
|
||||
aliasEntries.push(...(response.Aliases || []));
|
||||
aliasMarker = response.NextMarker;
|
||||
} while (aliasMarker);
|
||||
|
||||
const aliasEntries = Aliases.filter((aliasEntry) => {
|
||||
const keyMetadataRecord: Record<string, AWS.KMS.KeyMetadata | undefined> = {};
|
||||
for await (const aliasEntry of aliasEntries) {
|
||||
if (aliasEntry.TargetKeyId) {
|
||||
const keyDescription = await awsKms.describeKey({ KeyId: aliasEntry.TargetKeyId }).promise();
|
||||
|
||||
keyMetadataRecord[aliasEntry.TargetKeyId] = keyDescription.KeyMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
const validAliasEntries = aliasEntries.filter((aliasEntry) => {
|
||||
if (!aliasEntry.TargetKeyId) return false;
|
||||
|
||||
if (destination === SecretSync.AWSParameterStore && aliasEntry.AliasName === "alias/aws/ssm") return true;
|
||||
@@ -36,13 +52,18 @@ const listAwsKmsKeys = async (
|
||||
|
||||
if (aliasEntry.AliasName?.includes("alias/aws/")) return false;
|
||||
|
||||
const keyMetadata = keyMetadataRecord[aliasEntry.TargetKeyId];
|
||||
|
||||
if (!keyMetadata || keyMetadata.KeyUsage !== "ENCRYPT_DECRYPT" || keyMetadata.KeySpec !== "SYMMETRIC_DEFAULT")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const kmsKeys = aliasEntries.map((alias) => {
|
||||
const kmsKeys = validAliasEntries.map((aliasEntry) => {
|
||||
return {
|
||||
id: alias.TargetKeyId!,
|
||||
alias: alias.AliasName!
|
||||
id: aliasEntry.TargetKeyId!,
|
||||
alias: aliasEntry.AliasName!
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -137,10 +137,9 @@ const getParameterMetadataByPath = async (ssm: AWS.SSM, path: string): Promise<T
|
||||
|
||||
const getParameterStoreTagsRecord = async (
|
||||
ssm: AWS.SSM,
|
||||
path: string,
|
||||
awsParameterStoreSecretsRecord: TAWSParameterStoreRecord,
|
||||
areTagsPresent: boolean
|
||||
): Promise<TAWSParameterStoreTagsRecord> => {
|
||||
needsTagsPermissions: boolean
|
||||
): Promise<{ shouldManageTags: boolean; awsParameterStoreTagsRecord: TAWSParameterStoreTagsRecord }> => {
|
||||
const awsParameterStoreTagsRecord: TAWSParameterStoreTagsRecord = {};
|
||||
|
||||
for await (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
|
||||
@@ -163,15 +162,23 @@ const getParameterStoreTagsRecord = async (
|
||||
} catch (e) {
|
||||
// users aren't required to provide tag permissions to use sync so we handle gracefully if unauthorized
|
||||
// and they aren't trying to configure tags
|
||||
if ((e as AWSError).code === "AccessDeniedException" && !areTagsPresent) {
|
||||
return {};
|
||||
if ((e as AWSError).code === "AccessDeniedException") {
|
||||
if (!needsTagsPermissions) {
|
||||
return { shouldManageTags: false, awsParameterStoreTagsRecord: {} };
|
||||
}
|
||||
|
||||
throw new SecretSyncError({
|
||||
message:
|
||||
"IAM role has inadequate permissions to manage resource tags. Ensure the following polices are present: ssm:ListTagsForResource, ssm:AddTagsToResource, and ssm:RemoveTagsFromResource",
|
||||
shouldRetry: false
|
||||
});
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return awsParameterStoreTagsRecord;
|
||||
return { shouldManageTags: true, awsParameterStoreTagsRecord };
|
||||
};
|
||||
|
||||
const processParameterTags = ({
|
||||
@@ -294,13 +301,11 @@ export const AwsParameterStoreSyncFns = {
|
||||
|
||||
const awsParameterStoreMetadataRecord = await getParameterMetadataByPath(ssm, destinationConfig.path);
|
||||
|
||||
const awsParameterStoreTagsRecord = await getParameterStoreTagsRecord(
|
||||
const { shouldManageTags, awsParameterStoreTagsRecord } = await getParameterStoreTagsRecord(
|
||||
ssm,
|
||||
destinationConfig.path,
|
||||
awsParameterStoreSecretsRecord,
|
||||
Boolean(syncOptions.tags || syncOptions.syncSecretMetadataAsTags)
|
||||
Boolean(syncOptions.tags?.length || syncOptions.syncSecretMetadataAsTags)
|
||||
);
|
||||
|
||||
const syncTagsRecord = Object.fromEntries(syncOptions.tags?.map((tag) => [tag.key, tag.value]) ?? []);
|
||||
|
||||
for await (const entry of Object.entries(secretMap)) {
|
||||
@@ -316,7 +321,7 @@ export const AwsParameterStoreSyncFns = {
|
||||
if (
|
||||
!(key in awsParameterStoreSecretsRecord) ||
|
||||
value !== awsParameterStoreSecretsRecord[key].Value ||
|
||||
syncOptions.keyId !== awsParameterStoreMetadataRecord[key]?.KeyId
|
||||
(syncOptions.keyId ?? "alias/aws/ssm") !== awsParameterStoreMetadataRecord[key]?.KeyId
|
||||
) {
|
||||
try {
|
||||
await putParameter(ssm, {
|
||||
@@ -334,28 +339,44 @@ export const AwsParameterStoreSyncFns = {
|
||||
}
|
||||
}
|
||||
|
||||
const { tagsToAdd, tagKeysToRemove } = processParameterTags({
|
||||
syncTagsRecord: {
|
||||
// configured sync tags take preference over secret metadata
|
||||
...(syncOptions.syncSecretMetadataAsTags &&
|
||||
Object.fromEntries(secretMetadata?.map((tag) => [tag.key, tag.value]) ?? [])),
|
||||
...syncTagsRecord
|
||||
},
|
||||
awsTagsRecord: awsParameterStoreTagsRecord[key] ?? {}
|
||||
});
|
||||
|
||||
if (tagsToAdd.length) {
|
||||
await addTagsToParameter(ssm, {
|
||||
ResourceId: `${destinationConfig.path}${key}`,
|
||||
Tags: tagsToAdd
|
||||
if (shouldManageTags) {
|
||||
const { tagsToAdd, tagKeysToRemove } = processParameterTags({
|
||||
syncTagsRecord: {
|
||||
// configured sync tags take preference over secret metadata
|
||||
...(syncOptions.syncSecretMetadataAsTags &&
|
||||
Object.fromEntries(secretMetadata?.map((tag) => [tag.key, tag.value]) ?? [])),
|
||||
...syncTagsRecord
|
||||
},
|
||||
awsTagsRecord: awsParameterStoreTagsRecord[key] ?? {}
|
||||
});
|
||||
}
|
||||
|
||||
if (tagKeysToRemove.length) {
|
||||
await removeTagsFromParameter(ssm, {
|
||||
ResourceId: `${destinationConfig.path}${key}`,
|
||||
TagKeys: tagKeysToRemove
|
||||
});
|
||||
if (tagsToAdd.length) {
|
||||
try {
|
||||
await addTagsToParameter(ssm, {
|
||||
ResourceId: `${destinationConfig.path}${key}`,
|
||||
Tags: tagsToAdd
|
||||
});
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
secretKey: key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (tagKeysToRemove.length) {
|
||||
try {
|
||||
await removeTagsFromParameter(ssm, {
|
||||
ResourceId: `${destinationConfig.path}${key}`,
|
||||
TagKeys: tagKeysToRemove
|
||||
});
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
secretKey: key
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,14 +35,17 @@ const AwsParameterStoreSyncOptionsSchema = z.object({
|
||||
.string()
|
||||
.regex(
|
||||
/^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$/u,
|
||||
"Tag keys can only contain Unicode letters, digits, white space and any of the following: _."
|
||||
"Invalid resource tag key: keys can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
|
||||
)
|
||||
.min(1, "AWS tag key required")
|
||||
.max(128, "AWS tag name cannot exceed 128 characters"),
|
||||
.min(1, "Resource tag key required")
|
||||
.max(128, "Resource tag name cannot exceed 128 characters"),
|
||||
value: z
|
||||
.string()
|
||||
.regex(/^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$/u, "Invalid AWS tag value")
|
||||
.max(256, "Tag values can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-")
|
||||
.regex(
|
||||
/^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$/u,
|
||||
"Invalid resource tag value: tag values can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
|
||||
)
|
||||
.max(256, "Resource tag value cannot exceed 256 characters")
|
||||
})
|
||||
.array()
|
||||
.max(50)
|
||||
|
||||
@@ -77,6 +77,13 @@ via the UI or API for the third-party service you intend to sync secrets to.
|
||||
- <strong>Destination:</strong> The App Connection to utilize and the destination endpoint to deploy secrets to. These can vary between services.
|
||||
- <strong>Options:</strong> Customize how secrets should be synced. Examples include adding a suffix or prefix to your secrets, or importing secrets from the destination on the initial sync.
|
||||
|
||||
<Note>
|
||||
Secret Syncs are the source of truth for connected third-party services. Any secret,
|
||||
including associated data, not present or imported in Infisical before syncing will be
|
||||
overwritten, and changes directly in the connected service outside of infisical may also
|
||||
be overwritten by future syncs.
|
||||
</Note>
|
||||
|
||||
<Info>
|
||||
Some third-party services do not support importing secrets.
|
||||
</Info>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
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, Checkbox, FormControl, Switch } from "@app/components/v2";
|
||||
import { Button, FormControl, Modal, ModalContent, Switch } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||
import {
|
||||
@@ -42,8 +44,9 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { name: destinationName } = SECRET_SYNC_MAP[destination];
|
||||
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
const [confirmOverwrite, setConfirmOverwrite] = useState(false);
|
||||
|
||||
const { syncOption } = useSecretSyncOption(destination);
|
||||
|
||||
@@ -77,6 +80,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
onComplete(secretSync);
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
setShowConfirmation(false);
|
||||
createNotification({
|
||||
title: `Failed to add ${destinationName} Sync`,
|
||||
text: err.message,
|
||||
@@ -94,7 +98,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
setSelectedTabIndex((prev) => prev - 1);
|
||||
};
|
||||
|
||||
const { handleSubmit, trigger, watch, control } = formMethods;
|
||||
const { handleSubmit, trigger, control } = formMethods;
|
||||
|
||||
const isStepValid = async (index: number) => trigger(FORM_TABS[index].fields);
|
||||
|
||||
@@ -102,7 +106,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
|
||||
const handleNext = async () => {
|
||||
if (isFinalStep) {
|
||||
handleSubmit(onSubmit)();
|
||||
setShowConfirmation(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,113 +127,146 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
return isEnabled;
|
||||
};
|
||||
|
||||
const initialSyncBehavior = watch("syncOptions.initialSyncBehavior");
|
||||
if (showConfirmation)
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col rounded-sm border border-l-[2px] border-mineshaft-600 border-l-primary bg-mineshaft-700/80 px-4 py-3">
|
||||
<div className="mb-1 flex items-center text-sm">
|
||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||
Secret Sync Behavior
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-bunker-200">
|
||||
Secret Syncs are the source of truth for connected third-party services. Any secret,
|
||||
including associated data, not present or imported in Infisical before syncing will be
|
||||
overwritten, and changes directly in the connected service outside of infisical may also
|
||||
be overwritten by future syncs.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-4">
|
||||
<Button
|
||||
isDisabled={createSecretSync.isPending}
|
||||
isLoading={createSecretSync.isPending}
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
colorSchema="secondary"
|
||||
>
|
||||
I Understand
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
isDisabled={createSecretSync.isPending}
|
||||
variant="plain"
|
||||
onClick={() => setShowConfirmation(false)}
|
||||
colorSchema="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<form className={twMerge(isFinalStep && "max-h-[70vh] overflow-y-auto")}>
|
||||
<FormProvider {...formMethods}>
|
||||
<Tab.Group selectedIndex={selectedTabIndex} onChange={setSelectedTabIndex}>
|
||||
<Tab.List className="-pb-1 mb-6 w-full border-b-2 border-mineshaft-600">
|
||||
{FORM_TABS.map((tab, index) => (
|
||||
<Tab
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
const isEnabled = await isTabEnabled(index);
|
||||
setSelectedTabIndex((prev) => (isEnabled ? index : prev));
|
||||
}}
|
||||
className={({ selected }) =>
|
||||
`w-30 -mb-[0.14rem] ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
|
||||
selected
|
||||
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
|
||||
: "text-bunker-300"
|
||||
}`
|
||||
}
|
||||
key={tab.key}
|
||||
>
|
||||
{index + 1}. {tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
<SecretSyncSourceFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncDestinationFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncOptionsFields />
|
||||
<Controller
|
||||
control={control}
|
||||
name="isAutoSyncEnabled"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
helperText={
|
||||
value
|
||||
? "Secrets will automatically be synced when changes occur in the source location."
|
||||
: "Secrets will not automatically be synced when changes occur in the source location. You can still trigger syncs manually."
|
||||
}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/50"
|
||||
id="auto-sync-enabled"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
onCheckedChange={onChange}
|
||||
isChecked={value}
|
||||
<>
|
||||
<form className={twMerge(isFinalStep && "max-h-[70vh] overflow-y-auto")}>
|
||||
<FormProvider {...formMethods}>
|
||||
<Tab.Group selectedIndex={selectedTabIndex} onChange={setSelectedTabIndex}>
|
||||
<Tab.List className="-pb-1 mb-6 w-full border-b-2 border-mineshaft-600">
|
||||
{FORM_TABS.map((tab, index) => (
|
||||
<Tab
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
const isEnabled = await isTabEnabled(index);
|
||||
setSelectedTabIndex((prev) => (isEnabled ? index : prev));
|
||||
}}
|
||||
className={({ selected }) =>
|
||||
`w-30 -mb-[0.14rem] ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
|
||||
selected
|
||||
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
|
||||
: "text-bunker-300"
|
||||
}`
|
||||
}
|
||||
key={tab.key}
|
||||
>
|
||||
{index + 1}. {tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
<SecretSyncSourceFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncDestinationFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncOptionsFields />
|
||||
<Controller
|
||||
control={control}
|
||||
name="isAutoSyncEnabled"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
helperText={
|
||||
value
|
||||
? "Secrets will automatically be synced when changes occur in the source location."
|
||||
: "Secrets will not automatically be synced when changes occur in the source location. You can still trigger syncs manually."
|
||||
}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<p className="w-[8.4rem]">Auto-Sync {value ? "Enabled" : "Disabled"}</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncDetailsFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncReviewFields />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</FormProvider>
|
||||
{isFinalStep &&
|
||||
initialSyncBehavior === SecretSyncInitialSyncBehavior.OverwriteDestination && (
|
||||
<Checkbox
|
||||
id="confirm-overwrite"
|
||||
isChecked={confirmOverwrite}
|
||||
containerClassName="-mt-5"
|
||||
onCheckedChange={(isChecked) => setConfirmOverwrite(Boolean(isChecked))}
|
||||
>
|
||||
<p
|
||||
className={`mt-5 text-wrap text-xs ${confirmOverwrite ? "text-mineshaft-200" : "text-red"}`}
|
||||
>
|
||||
I understand all secrets present in the configured {destinationName} destination will
|
||||
be removed if they are not present within Infisical.
|
||||
</p>
|
||||
</Checkbox>
|
||||
)}
|
||||
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
||||
<Button
|
||||
isDisabled={
|
||||
isFinalStep &&
|
||||
initialSyncBehavior === SecretSyncInitialSyncBehavior.OverwriteDestination &&
|
||||
!confirmOverwrite
|
||||
}
|
||||
onClick={handleNext}
|
||||
colorSchema="secondary"
|
||||
>
|
||||
{isFinalStep ? "Create Sync" : "Next"}
|
||||
</Button>
|
||||
{selectedTabIndex > 0 && (
|
||||
<Button onClick={handlePrev} colorSchema="secondary">
|
||||
Back
|
||||
<Switch
|
||||
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/50"
|
||||
id="auto-sync-enabled"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
onCheckedChange={onChange}
|
||||
isChecked={value}
|
||||
>
|
||||
<p className="w-[8.4rem]">Auto-Sync {value ? "Enabled" : "Disabled"}</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncDetailsFields />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<SecretSyncReviewFields />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</FormProvider>
|
||||
|
||||
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
||||
<Button onClick={handleNext} colorSchema="secondary">
|
||||
{isFinalStep ? "Create Sync" : "Next"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
{selectedTabIndex > 0 && (
|
||||
<Button onClick={handlePrev} colorSchema="secondary">
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
<Modal isOpen={showConfirmation} onOpenChange={setShowConfirmation}>
|
||||
<ModalContent
|
||||
title="Import Secrets"
|
||||
subTitle={`Import secrets into Infisical from this ${destinationName} Sync destination.`}
|
||||
>
|
||||
<div className="mt-6 flex flex-col rounded-sm border border-l-[2px] border-mineshaft-600 border-l-primary bg-mineshaft-700/80 px-4 py-3">
|
||||
<div className="mb-1 flex items-center text-sm">
|
||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||
Secret Sync Behavior
|
||||
</div>
|
||||
<p className="mb-2 mt-1 text-sm text-bunker-200">
|
||||
Secret Syncs are the source of truth for connected third-party services. Any secret
|
||||
not present or imported in Infisical before syncing will be overwritten, and changes
|
||||
made directly in the connected service may also be overwritten by future syncs from
|
||||
Infisical.
|
||||
</p>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -63,6 +63,31 @@ export const AwsParameterStoreSyncOptionsFields = () => {
|
||||
onChange={(option) =>
|
||||
onChange((option as SingleValue<TAwsConnectionKmsKey>)?.alias ?? null)
|
||||
}
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
noOptionsMessage={({ inputValue }) =>
|
||||
inputValue ? undefined : (
|
||||
<p>
|
||||
To configure a KMS key, ensure the following permissions are present on the
|
||||
selected IAM role:{" "}
|
||||
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
|
||||
"kms:ListKeys"
|
||||
</span>
|
||||
,{" "}
|
||||
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
|
||||
"kms:ListAliases"
|
||||
</span>
|
||||
,{" "}
|
||||
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
|
||||
"kms:Encrypt"
|
||||
</span>
|
||||
,{" "}
|
||||
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
|
||||
"kms:Decrypt"
|
||||
</span>
|
||||
.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
options={kmsKeys}
|
||||
placeholder="Leave blank to use default KMS key"
|
||||
getOptionLabel={(option) =>
|
||||
|
||||
@@ -18,8 +18,8 @@ export const AwsParameterStoreSyncOptionsReviewFields = () => {
|
||||
return (
|
||||
<>
|
||||
{keyId && <SecretSyncLabel label="KMS Key">{keyId}</SecretSyncLabel>}
|
||||
{tags?.length && (
|
||||
<SecretSyncLabel label="AWS Tags">
|
||||
{tags && tags.length > 0 && (
|
||||
<SecretSyncLabel label="Resource Tags">
|
||||
<Tooltip
|
||||
side="right"
|
||||
className="max-w-xl p-1"
|
||||
@@ -52,7 +52,7 @@ export const AwsParameterStoreSyncOptionsReviewFields = () => {
|
||||
</SecretSyncLabel>
|
||||
)}
|
||||
{syncSecretMetadataAsTags && (
|
||||
<SecretSyncLabel label="AWS Tags">
|
||||
<SecretSyncLabel label="Sync Secret Metadata as Resource Tags">
|
||||
<Badge variant="success">Enabled</Badge>
|
||||
</SecretSyncLabel>
|
||||
)}
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
export type TDatabricksSecretScope = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TDatabricksConnectionListSecretScopesResponse = {
|
||||
secretScopes: TDatabricksSecretScope[];
|
||||
};
|
||||
|
||||
export type TListAwsConnectionKmsKeys = {
|
||||
connectionId: string;
|
||||
region: string;
|
||||
|
||||
@@ -19,7 +19,7 @@ export const SecretSyncsTab = () => {
|
||||
const { data: secretSyncs = [], isPending: isSecretSyncsPending } = useListSecretSyncs(
|
||||
currentWorkspace.id,
|
||||
{
|
||||
refetchInterval: 4000
|
||||
refetchInterval: 30000
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const PageContent = () => {
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["editSync"] as const);
|
||||
|
||||
const { data: secretSync, isPending } = useGetSecretSync(destination, syncId, {
|
||||
refetchInterval: 4000
|
||||
refetchInterval: 30000
|
||||
});
|
||||
|
||||
if (isPending) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export const AwsParameterStoreSyncOptionsSection = ({ secretSync }: Props) => {
|
||||
return (
|
||||
<>
|
||||
{keyId && <SecretSyncLabel label="KMS Key">{keyId}</SecretSyncLabel>}
|
||||
{tags?.length && (
|
||||
{tags && tags.length > 0 && (
|
||||
<SecretSyncLabel label="Resource Tags">
|
||||
<Tooltip
|
||||
side="right"
|
||||
|
||||
Reference in New Issue
Block a user