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