mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
with stripSchema and filterForSchema
This commit is contained in:
@@ -2109,6 +2109,7 @@ export const SecretSyncs = {
|
||||
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||
return {
|
||||
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
|
||||
keySchema: `Specify the format to use for structuring secret keys in the ${destinationName} destination.`,
|
||||
disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
|
||||
};
|
||||
},
|
||||
|
||||
@@ -367,7 +367,17 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
||||
querystring: z.object({
|
||||
importBehavior: z
|
||||
.nativeEnum(SecretSyncImportBehavior)
|
||||
.describe(SecretSyncs.IMPORT_SECRETS(destination).importBehavior)
|
||||
.describe(SecretSyncs.IMPORT_SECRETS(destination).importBehavior),
|
||||
filterForSchema: z
|
||||
.enum(["true", "false"])
|
||||
.optional()
|
||||
.default("false")
|
||||
.transform((v) => v === "true"),
|
||||
stripSchema: z
|
||||
.enum(["true", "false"])
|
||||
.optional()
|
||||
.default("false")
|
||||
.transform((v) => v === "true")
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ secretSync: responseSchema })
|
||||
@@ -376,13 +386,15 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { syncId } = req.params;
|
||||
const { importBehavior } = req.query;
|
||||
const { importBehavior, filterForSchema, stripSchema } = req.query;
|
||||
|
||||
const secretSync = (await server.services.secretSync.triggerSecretSyncImportSecretsById(
|
||||
{
|
||||
syncId,
|
||||
destination,
|
||||
importBehavior
|
||||
importBehavior,
|
||||
filterForSchema,
|
||||
stripSchema
|
||||
},
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
@@ -61,45 +61,63 @@ type TSyncSecretDeps = {
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
};
|
||||
|
||||
// const addAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
||||
// let secretMap = { ...unprocessedSecretMap };
|
||||
//
|
||||
// const { appendSuffix, prependPrefix } = secretSync.syncOptions;
|
||||
//
|
||||
// if (appendSuffix || prependPrefix) {
|
||||
// secretMap = {};
|
||||
// Object.entries(unprocessedSecretMap).forEach(([key, value]) => {
|
||||
// secretMap[`${prependPrefix || ""}${key}${appendSuffix || ""}`] = value;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return secretMap;
|
||||
// };
|
||||
//
|
||||
// const stripAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
||||
// let secretMap = { ...unprocessedSecretMap };
|
||||
//
|
||||
// const { appendSuffix, prependPrefix } = secretSync.syncOptions;
|
||||
//
|
||||
// if (appendSuffix || prependPrefix) {
|
||||
// secretMap = {};
|
||||
// Object.entries(unprocessedSecretMap).forEach(([key, value]) => {
|
||||
// let processedKey = key;
|
||||
//
|
||||
// if (prependPrefix && processedKey.startsWith(prependPrefix)) {
|
||||
// processedKey = processedKey.slice(prependPrefix.length);
|
||||
// }
|
||||
//
|
||||
// if (appendSuffix && processedKey.endsWith(appendSuffix)) {
|
||||
// processedKey = processedKey.slice(0, -appendSuffix.length);
|
||||
// }
|
||||
//
|
||||
// secretMap[processedKey] = value;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return secretMap;
|
||||
// };
|
||||
interface TSyncSecretConfig {
|
||||
filterForSchema?: boolean;
|
||||
stripSchema?: boolean;
|
||||
}
|
||||
|
||||
// Add schema to secret keys
|
||||
const addSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecretMap => {
|
||||
if (!schema) return unprocessedSecretMap;
|
||||
|
||||
const processedSecretMap: TSecretMap = {};
|
||||
|
||||
for (const [key, value] of Object.entries(unprocessedSecretMap)) {
|
||||
const newKey = schema.replace("{{secretKey}}", key);
|
||||
processedSecretMap[newKey] = value;
|
||||
}
|
||||
|
||||
return processedSecretMap;
|
||||
};
|
||||
|
||||
// Strip schema from secret keys
|
||||
const stripSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecretMap => {
|
||||
if (!schema) return unprocessedSecretMap;
|
||||
|
||||
const [prefix, suffix] = schema.split("{{secretKey}}");
|
||||
|
||||
const strippedMap: TSecretMap = {};
|
||||
|
||||
for (const [key, value] of Object.entries(unprocessedSecretMap)) {
|
||||
if (!key.startsWith(prefix) || !key.endsWith(suffix)) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const strippedKey = key.slice(prefix.length, key.length - suffix.length);
|
||||
strippedMap[strippedKey] = value;
|
||||
}
|
||||
|
||||
return strippedMap;
|
||||
};
|
||||
|
||||
// Filter only for secrets with keys that match the schema
|
||||
const filterForSchema = (secretMap: TSecretMap, schema?: string): TSecretMap => {
|
||||
if (!schema) return secretMap;
|
||||
|
||||
const [prefix, suffix] = schema.split("{{secretKey}}");
|
||||
if (prefix === undefined || suffix === undefined) return secretMap;
|
||||
|
||||
const filteredMap: TSecretMap = {};
|
||||
|
||||
for (const [key, value] of Object.entries(secretMap)) {
|
||||
if (key.startsWith(prefix) && key.endsWith(suffix)) {
|
||||
filteredMap[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return filteredMap;
|
||||
};
|
||||
|
||||
export const SecretSyncFns = {
|
||||
syncSecrets: (
|
||||
@@ -107,51 +125,51 @@ export const SecretSyncFns = {
|
||||
secretMap: TSecretMap,
|
||||
{ kmsService, appConnectionDAL }: TSyncSecretDeps
|
||||
): Promise<void> => {
|
||||
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
||||
const schemaSecretMap = addSchema(secretMap, secretSync.syncOptions.keySchema);
|
||||
|
||||
switch (secretSync.destination) {
|
||||
case SecretSync.AWSParameterStore:
|
||||
return AwsParameterStoreSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return AwsParameterStoreSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AWSSecretsManager:
|
||||
return AwsSecretsManagerSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return AwsSecretsManagerSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.GitHub:
|
||||
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return GithubSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.GCPSecretManager:
|
||||
return GcpSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return GcpSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AzureKeyVault:
|
||||
return azureKeyVaultSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).syncSecrets(secretSync, secretMap);
|
||||
}).syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AzureAppConfiguration:
|
||||
return azureAppConfigurationSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).syncSecrets(secretSync, secretMap);
|
||||
}).syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Databricks:
|
||||
return databricksSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).syncSecrets(secretSync, secretMap);
|
||||
}).syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Humanitec:
|
||||
return HumanitecSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return HumanitecSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.TerraformCloud:
|
||||
return TerraformCloudSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return TerraformCloudSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Camunda:
|
||||
return camundaSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).syncSecrets(secretSync, secretMap);
|
||||
}).syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Vercel:
|
||||
return VercelSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return VercelSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Windmill:
|
||||
return WindmillSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return WindmillSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.HCVault:
|
||||
return HCVaultSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return HCVaultSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.TeamCity:
|
||||
return TeamCitySyncFns.syncSecrets(secretSync, secretMap);
|
||||
return TeamCitySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.OCIVault:
|
||||
return OCIVaultSyncFns.syncSecrets(secretSync, secretMap);
|
||||
return OCIVaultSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
@@ -160,7 +178,8 @@ export const SecretSyncFns = {
|
||||
},
|
||||
getSecrets: async (
|
||||
secretSync: TSecretSyncWithCredentials,
|
||||
{ kmsService, appConnectionDAL }: TSyncSecretDeps
|
||||
{ kmsService, appConnectionDAL }: TSyncSecretDeps,
|
||||
config?: TSyncSecretConfig
|
||||
): Promise<TSecretMap> => {
|
||||
let secretMap: TSecretMap;
|
||||
switch (secretSync.destination) {
|
||||
@@ -226,59 +245,68 @@ export const SecretSyncFns = {
|
||||
);
|
||||
}
|
||||
|
||||
return secretMap;
|
||||
// return stripAffixes(secretSync, secretMap);
|
||||
let processedSecretMap = secretMap;
|
||||
|
||||
if (config?.filterForSchema) {
|
||||
processedSecretMap = filterForSchema(processedSecretMap);
|
||||
}
|
||||
|
||||
if (config?.stripSchema) {
|
||||
return stripSchema(processedSecretMap, secretSync.syncOptions.keySchema);
|
||||
}
|
||||
|
||||
return processedSecretMap;
|
||||
},
|
||||
removeSecrets: (
|
||||
secretSync: TSecretSyncWithCredentials,
|
||||
secretMap: TSecretMap,
|
||||
{ kmsService, appConnectionDAL }: TSyncSecretDeps
|
||||
): Promise<void> => {
|
||||
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
||||
const schemaSecretMap = addSchema(secretMap, secretSync.syncOptions.keySchema);
|
||||
|
||||
switch (secretSync.destination) {
|
||||
case SecretSync.AWSParameterStore:
|
||||
return AwsParameterStoreSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return AwsParameterStoreSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AWSSecretsManager:
|
||||
return AwsSecretsManagerSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return AwsSecretsManagerSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.GitHub:
|
||||
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return GithubSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.GCPSecretManager:
|
||||
return GcpSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return GcpSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AzureKeyVault:
|
||||
return azureKeyVaultSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).removeSecrets(secretSync, secretMap);
|
||||
}).removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AzureAppConfiguration:
|
||||
return azureAppConfigurationSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).removeSecrets(secretSync, secretMap);
|
||||
}).removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Databricks:
|
||||
return databricksSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).removeSecrets(secretSync, secretMap);
|
||||
}).removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Humanitec:
|
||||
return HumanitecSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return HumanitecSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.TerraformCloud:
|
||||
return TerraformCloudSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return TerraformCloudSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Camunda:
|
||||
return camundaSyncFactory({
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
}).removeSecrets(secretSync, secretMap);
|
||||
}).removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Vercel:
|
||||
return VercelSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return VercelSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Windmill:
|
||||
return WindmillSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return WindmillSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.HCVault:
|
||||
return HCVaultSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return HCVaultSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.TeamCity:
|
||||
return TeamCitySyncFns.removeSecrets(secretSync, secretMap);
|
||||
return TeamCitySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.OCIVault:
|
||||
return OCIVaultSyncFns.removeSecrets(secretSync, secretMap);
|
||||
return OCIVaultSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
|
||||
@@ -319,9 +319,12 @@ export const secretSyncQueueFactory = ({
|
||||
);
|
||||
};
|
||||
|
||||
// TODO(andrey): Possibly add a "stripSchema" parameter for imports?
|
||||
const $importSecrets = async (
|
||||
secretSync: TSecretSyncWithCredentials,
|
||||
importBehavior: SecretSyncImportBehavior
|
||||
importBehavior: SecretSyncImportBehavior,
|
||||
filterForSchema: boolean,
|
||||
stripSchema: boolean
|
||||
): Promise<TSecretMap> => {
|
||||
const { projectId, environment, folder } = secretSync;
|
||||
|
||||
@@ -330,10 +333,17 @@ export const secretSyncQueueFactory = ({
|
||||
"Invalid Secret Sync source configuration: folder no longer exists. Please update source environment and secret path."
|
||||
);
|
||||
|
||||
const importedSecrets = await SecretSyncFns.getSecrets(secretSync, {
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
});
|
||||
const importedSecrets = await SecretSyncFns.getSecrets(
|
||||
secretSync,
|
||||
{
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
},
|
||||
{
|
||||
filterForSchema,
|
||||
stripSchema
|
||||
}
|
||||
);
|
||||
|
||||
if (!Object.keys(importedSecrets).length) return {};
|
||||
|
||||
@@ -439,11 +449,14 @@ export const secretSyncQueueFactory = ({
|
||||
const secretMap = await $getInfisicalSecrets(secretSync);
|
||||
|
||||
if (!lastSyncedAt && initialSyncBehavior !== SecretSyncInitialSyncBehavior.OverwriteDestination) {
|
||||
// TODO(andrey): Possibly add a way to filter / strip schemas on initial sync?
|
||||
const importedSecretMap = await $importSecrets(
|
||||
secretSyncWithCredentials,
|
||||
initialSyncBehavior === SecretSyncInitialSyncBehavior.ImportPrioritizeSource
|
||||
? SecretSyncImportBehavior.PrioritizeSource
|
||||
: SecretSyncImportBehavior.PrioritizeDestination
|
||||
: SecretSyncImportBehavior.PrioritizeDestination,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
Object.entries(importedSecretMap).forEach(([key, secretData]) => {
|
||||
@@ -535,7 +548,7 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
const $handleImportSecretsJob = async (job: TSecretSyncImportSecretsDTO) => {
|
||||
const {
|
||||
data: { syncId, auditLogInfo, importBehavior }
|
||||
data: { syncId, auditLogInfo, importBehavior, filterForSchema, stripSchema }
|
||||
} = job;
|
||||
|
||||
const secretSync = await secretSyncDAL.findById(syncId);
|
||||
@@ -573,7 +586,9 @@ export const secretSyncQueueFactory = ({
|
||||
credentials
|
||||
}
|
||||
} as TSecretSyncWithCredentials,
|
||||
importBehavior
|
||||
importBehavior,
|
||||
filterForSchema,
|
||||
stripSchema
|
||||
);
|
||||
|
||||
isSuccess = true;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import RE2 from "re2";
|
||||
import { AnyZodObject, z } from "zod";
|
||||
|
||||
import { SecretSyncsSchema } from "@app/db/schemas/secret-syncs";
|
||||
@@ -24,6 +25,14 @@ const BaseSyncOptionsSchema = <T extends AnyZodObject | undefined = undefined>({
|
||||
? z.nativeEnum(SecretSyncInitialSyncBehavior)
|
||||
: z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination)
|
||||
).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior),
|
||||
keySchema: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((val) => !val || new RE2(/^(?:[a-zA-Z0-9\-/]*)(?:\{\{secretKey\}\})(?:[a-zA-Z0-9\-/]*)$/).test(val), {
|
||||
message:
|
||||
"Key schema must include {{secretKey}} and only contain letters, numbers, dashes, slashes, and the {{secretKey}} placeholder."
|
||||
})
|
||||
.describe(SecretSyncs.SYNC_OPTIONS(destination).keySchema),
|
||||
disableSecretDeletion: z.boolean().optional().describe(SecretSyncs.SYNC_OPTIONS(destination).disableSecretDeletion)
|
||||
});
|
||||
|
||||
|
||||
@@ -231,6 +231,8 @@ export type TQueueSecretSyncImportSecretsByIdDTO = {
|
||||
syncId: string;
|
||||
importBehavior: SecretSyncImportBehavior;
|
||||
auditLogInfo?: AuditLogInfo;
|
||||
filterForSchema: boolean;
|
||||
stripSchema: boolean;
|
||||
};
|
||||
|
||||
export type TTriggerSecretSyncImportSecretsByIdDTO = {
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem
|
||||
SelectItem,
|
||||
Switch
|
||||
} from "@app/components/v2";
|
||||
import { SECRET_SYNC_IMPORT_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||
import {
|
||||
@@ -31,30 +32,49 @@ type ContentProps = {
|
||||
};
|
||||
|
||||
const FormSchema = z.object({
|
||||
importBehavior: z.nativeEnum(SecretSyncImportBehavior)
|
||||
importBehavior: z.nativeEnum(SecretSyncImportBehavior),
|
||||
filterForSchema: z.boolean(),
|
||||
stripSchema: z.boolean()
|
||||
});
|
||||
|
||||
type TFormData = z.infer<typeof FormSchema>;
|
||||
|
||||
const Content = ({ secretSync, onComplete }: ContentProps) => {
|
||||
const { id: syncId, destination, projectId } = secretSync;
|
||||
const {
|
||||
id: syncId,
|
||||
destination,
|
||||
projectId,
|
||||
syncOptions: { keySchema }
|
||||
} = secretSync;
|
||||
const destinationName = SECRET_SYNC_MAP[destination].name;
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = useForm<TFormData>({ resolver: zodResolver(FormSchema) });
|
||||
} = useForm<TFormData>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
filterForSchema: false,
|
||||
stripSchema: false
|
||||
}
|
||||
});
|
||||
|
||||
const triggerImportSecrets = useTriggerSecretSyncImportSecrets();
|
||||
|
||||
const handleTriggerImportSecrets = async ({ importBehavior }: TFormData) => {
|
||||
const handleTriggerImportSecrets = async ({
|
||||
importBehavior,
|
||||
filterForSchema,
|
||||
stripSchema
|
||||
}: TFormData) => {
|
||||
try {
|
||||
await triggerImportSecrets.mutateAsync({
|
||||
syncId,
|
||||
destination,
|
||||
importBehavior,
|
||||
projectId
|
||||
projectId,
|
||||
filterForSchema,
|
||||
stripSchema
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@@ -131,6 +151,64 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{keySchema && (
|
||||
<>
|
||||
<Controller
|
||||
control={control}
|
||||
name="filterForSchema"
|
||||
defaultValue
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipClassName="max-w-md"
|
||||
tooltipText={
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
If enabled, Infisical will only import destination secrets that match your key
|
||||
schema:
|
||||
</p>
|
||||
<code className="text-mineshaft-300">{keySchema}</code>
|
||||
</div>
|
||||
}
|
||||
label="Filter Keys for Schema"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Switch
|
||||
id="filter-for-schema"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
>
|
||||
Only import destination secrets that match schema
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="stripSchema"
|
||||
defaultValue
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipClassName="max-w-md"
|
||||
tooltipText="If enabled, Infisical will strip secret keys according to your schema. Keys that do not match the schema will not be affected."
|
||||
label="Strip Schema"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Switch
|
||||
id="strip-schema"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
>
|
||||
Strip schema from imported secret keys
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-8 flex w-full items-center justify-between gap-2">
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Controller, useFormContext } from "react-hook-form";
|
||||
import { faQuestionCircle, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { FormControl, Select, SelectItem, Switch, Tooltip } from "@app/components/v2";
|
||||
import { FormControl, Input, Select, SelectItem, Switch, Tooltip } from "@app/components/v2";
|
||||
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||
import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
@@ -122,6 +122,22 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipClassName="max-w-md"
|
||||
tooltipText="When a secret is synced, it's key will be injected into the key schema before it reaches the destination. This is useful for organization."
|
||||
isError={Boolean(error)}
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
label="Key Schema"
|
||||
>
|
||||
<Input value={value} onChange={onChange} placeholder="prefix/{{secretKey}}/suffix" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="syncOptions.keySchema"
|
||||
/>
|
||||
{AdditionalSyncOptionsFieldsComponent}
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -161,34 +177,6 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{/* <Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
label="Prepend Prefix"
|
||||
>
|
||||
<Input className="uppercase" value={value} onChange={onChange} placeholder="INF_" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="syncOptions.prependPrefix"
|
||||
/>
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
label="Append Suffix"
|
||||
>
|
||||
<Input className="uppercase" value={value} onChange={onChange} placeholder="_INF" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="syncOptions.appendSuffix"
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,11 +41,7 @@ export const SecretSyncReviewFields = () => {
|
||||
connection,
|
||||
environment,
|
||||
secretPath,
|
||||
syncOptions: {
|
||||
// appendSuffix, prependPrefix,
|
||||
disableSecretDeletion,
|
||||
initialSyncBehavior
|
||||
},
|
||||
syncOptions: { disableSecretDeletion, initialSyncBehavior, keySchema },
|
||||
destination,
|
||||
isAutoSyncEnabled
|
||||
} = watch();
|
||||
@@ -137,8 +133,7 @@ export const SecretSyncReviewFields = () => {
|
||||
<GenericFieldLabel label="Initial Sync Behavior">
|
||||
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destinationName).name}
|
||||
</GenericFieldLabel>
|
||||
{/* <SecretSyncLabel label="Prepend Prefix">{prependPrefix}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Append Suffix">{appendSuffix}</SecretSyncLabel> */}
|
||||
<GenericFieldLabel label="Key Schema">{keySchema}</GenericFieldLabel>
|
||||
{AdditionalSyncOptionsFieldsComponent}
|
||||
{disableSecretDeletion && (
|
||||
<GenericFieldLabel label="Secret Deletion">
|
||||
|
||||
@@ -8,18 +8,17 @@ export const BaseSecretSyncSchema = <T extends AnyZodObject | undefined = undefi
|
||||
) => {
|
||||
const baseSyncOptionsSchema = z.object({
|
||||
initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior),
|
||||
disableSecretDeletion: z.boolean().optional().default(false)
|
||||
// scott: removed temporarily for evaluation of template formatting
|
||||
// prependPrefix: z
|
||||
// .string()
|
||||
// .trim()
|
||||
// .transform((str) => str.toUpperCase())
|
||||
// .optional(),
|
||||
// appendSuffix: z
|
||||
// .string()
|
||||
// .trim()
|
||||
// .transform((str) => str.toUpperCase())
|
||||
// .optional()
|
||||
disableSecretDeletion: z.boolean().optional().default(false),
|
||||
keySchema: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) => !val || /^(?:[a-zA-Z0-9\-/]*)(?:\{\{secretKey\}\})(?:[a-zA-Z0-9\-/]*)$/.test(val),
|
||||
{
|
||||
message:
|
||||
"Key schema must include {{secretKey}} and only contain letters, numbers, dashes, slashes, and the {{secretKey}} placeholder."
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
const syncOptionsSchema = additionalSyncOptions
|
||||
|
||||
@@ -86,10 +86,12 @@ export const useTriggerSecretSyncImportSecrets = () => {
|
||||
mutationFn: async ({
|
||||
syncId,
|
||||
destination,
|
||||
importBehavior
|
||||
importBehavior,
|
||||
filterForSchema,
|
||||
stripSchema
|
||||
}: TTriggerSecretSyncImportSecretsDTO) => {
|
||||
const { data } = await apiRequest.post(
|
||||
`/api/v1/secret-syncs/${destination}/${syncId}/import-secrets?importBehavior=${importBehavior}`
|
||||
`/api/v1/secret-syncs/${destination}/${syncId}/import-secrets?importBehavior=${importBehavior}&filterForSchema=${filterForSchema}&stripSchema=${stripSchema}`
|
||||
);
|
||||
|
||||
return data;
|
||||
|
||||
@@ -82,6 +82,8 @@ export type TTriggerSecretSyncImportSecretsDTO = {
|
||||
syncId: string;
|
||||
importBehavior: SecretSyncImportBehavior;
|
||||
projectId: string;
|
||||
filterForSchema: boolean;
|
||||
stripSchema: boolean;
|
||||
};
|
||||
|
||||
export type TTriggerSecretSyncRemoveSecretsDTO = {
|
||||
|
||||
@@ -4,8 +4,7 @@ import { SecretSyncInitialSyncBehavior, SecretSyncStatus } from "@app/hooks/api/
|
||||
export type RootSyncOptions = {
|
||||
initialSyncBehavior: SecretSyncInitialSyncBehavior;
|
||||
disableSecretDeletion?: boolean;
|
||||
// prependPrefix?: string;
|
||||
// appendSuffix?: string;
|
||||
keySchema?: string;
|
||||
};
|
||||
|
||||
export type TRootSecretSync = {
|
||||
|
||||
@@ -21,12 +21,7 @@ type Props = {
|
||||
export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) => {
|
||||
const {
|
||||
destination,
|
||||
syncOptions: {
|
||||
// appendSuffix,
|
||||
// prependPrefix,
|
||||
initialSyncBehavior,
|
||||
disableSecretDeletion
|
||||
}
|
||||
syncOptions: { initialSyncBehavior, disableSecretDeletion, keySchema }
|
||||
} = secretSync;
|
||||
|
||||
let AdditionalSyncOptionsComponent: ReactNode;
|
||||
@@ -88,8 +83,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
||||
<GenericFieldLabel label="Initial Sync Behavior">
|
||||
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destination).name}
|
||||
</GenericFieldLabel>
|
||||
{/* <SecretSyncLabel label="Prefix">{prependPrefix}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Suffix">{appendSuffix}</SecretSyncLabel> */}
|
||||
<GenericFieldLabel label="Key Schema">{keySchema}</GenericFieldLabel>
|
||||
{AdditionalSyncOptionsComponent}
|
||||
{disableSecretDeletion && (
|
||||
<GenericFieldLabel label="Secret Deletion">
|
||||
|
||||
Reference in New Issue
Block a user