Add {{environment}} support for key schemas

This commit is contained in:
x032205
2025-06-04 21:20:16 -04:00
parent 6e1504dc73
commit 30d7e63a67
35 changed files with 140 additions and 61 deletions

View File

@@ -117,6 +117,7 @@ export const OCIVaultSyncFns = {
syncSecrets: async (secretSync: TOCIVaultSyncWithCredentials, secretMap: TSecretMap) => { syncSecrets: async (secretSync: TOCIVaultSyncWithCredentials, secretMap: TSecretMap) => {
const { const {
connection, connection,
environment,
destinationConfig: { compartmentOcid, vaultOcid, keyOcid } destinationConfig: { compartmentOcid, vaultOcid, keyOcid }
} = secretSync; } = secretSync;
@@ -213,7 +214,7 @@ export const OCIVaultSyncFns = {
// Update and delete secrets // Update and delete secrets
for await (const [key, variable] of Object.entries(variables)) { for await (const [key, variable] of Object.entries(variables)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
// Only update / delete active secrets // Only update / delete active secrets
if (variable.lifecycleState === vault.models.SecretSummary.LifecycleState.Active) { if (variable.lifecycleState === vault.models.SecretSummary.LifecycleState.Active) {

View File

@@ -127,6 +127,7 @@ export const OnePassSyncFns = {
syncSecrets: async (secretSync: TOnePassSyncWithCredentials, secretMap: TSecretMap) => { syncSecrets: async (secretSync: TOnePassSyncWithCredentials, secretMap: TSecretMap) => {
const { const {
connection, connection,
environment,
destinationConfig: { vaultId } destinationConfig: { vaultId }
} = secretSync; } = secretSync;
@@ -164,7 +165,7 @@ export const OnePassSyncFns = {
for await (const [key, variable] of Object.entries(items)) { for await (const [key, variable] of Object.entries(items)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
if (!(key in secretMap)) { if (!(key in secretMap)) {
try { try {

View File

@@ -294,7 +294,7 @@ const deleteParametersBatch = async (
export const AwsParameterStoreSyncFns = { export const AwsParameterStoreSyncFns = {
syncSecrets: async (secretSync: TAwsParameterStoreSyncWithCredentials, secretMap: TSecretMap) => { syncSecrets: async (secretSync: TAwsParameterStoreSyncWithCredentials, secretMap: TSecretMap) => {
const { destinationConfig, syncOptions } = secretSync; const { destinationConfig, syncOptions, environment } = secretSync;
const ssm = await getSSM(secretSync); const ssm = await getSSM(secretSync);
@@ -391,7 +391,7 @@ export const AwsParameterStoreSyncFns = {
const [key, parameter] = entry; const [key, parameter] = entry;
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, syncOptions.keySchema)) continue; if (!matchesSchema(key, environment?.slug || "", syncOptions.keySchema)) continue;
if (!(key in secretMap) || !secretMap[key].value) { if (!(key in secretMap) || !secretMap[key].value) {
parametersToDelete.push(parameter); parametersToDelete.push(parameter);

View File

@@ -57,7 +57,11 @@ const sleep = async () =>
setTimeout(resolve, 1000); setTimeout(resolve, 1000);
}); });
const getSecretsRecord = async (client: SecretsManagerClient, keySchema?: string): Promise<TAwsSecretsRecord> => { const getSecretsRecord = async (
client: SecretsManagerClient,
environment: string,
keySchema?: string
): Promise<TAwsSecretsRecord> => {
const awsSecretsRecord: TAwsSecretsRecord = {}; const awsSecretsRecord: TAwsSecretsRecord = {};
let hasNext = true; let hasNext = true;
let nextToken: string | undefined; let nextToken: string | undefined;
@@ -72,7 +76,7 @@ const getSecretsRecord = async (client: SecretsManagerClient, keySchema?: string
if (output.SecretList) { if (output.SecretList) {
output.SecretList.forEach((secretEntry) => { output.SecretList.forEach((secretEntry) => {
if (secretEntry.Name && matchesSchema(secretEntry.Name, keySchema)) { if (secretEntry.Name && matchesSchema(secretEntry.Name, environment, keySchema)) {
awsSecretsRecord[secretEntry.Name] = secretEntry; awsSecretsRecord[secretEntry.Name] = secretEntry;
} }
}); });
@@ -307,11 +311,11 @@ const processTags = ({
export const AwsSecretsManagerSyncFns = { export const AwsSecretsManagerSyncFns = {
syncSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials, secretMap: TSecretMap) => { syncSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials, secretMap: TSecretMap) => {
const { destinationConfig, syncOptions } = secretSync; const { destinationConfig, syncOptions, environment } = secretSync;
const client = await getSecretsManagerClient(secretSync); const client = await getSecretsManagerClient(secretSync);
const awsSecretsRecord = await getSecretsRecord(client, syncOptions.keySchema); const awsSecretsRecord = await getSecretsRecord(client, environment?.slug || "", syncOptions.keySchema);
const awsValuesRecord = await getSecretValuesRecord(client, awsSecretsRecord); const awsValuesRecord = await getSecretValuesRecord(client, awsSecretsRecord);
@@ -401,7 +405,7 @@ export const AwsSecretsManagerSyncFns = {
for await (const secretKey of Object.keys(awsSecretsRecord)) { for await (const secretKey of Object.keys(awsSecretsRecord)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(secretKey, syncOptions.keySchema)) continue; if (!matchesSchema(secretKey, environment?.slug || "", syncOptions.keySchema)) continue;
if (!(secretKey in secretMap) || !secretMap[secretKey].value) { if (!(secretKey in secretMap) || !secretMap[secretKey].value) {
try { try {
@@ -468,7 +472,11 @@ export const AwsSecretsManagerSyncFns = {
getSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials): Promise<TSecretMap> => { getSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials): Promise<TSecretMap> => {
const client = await getSecretsManagerClient(secretSync); const client = await getSecretsManagerClient(secretSync);
const awsSecretsRecord = await getSecretsRecord(client, secretSync.syncOptions.keySchema); const awsSecretsRecord = await getSecretsRecord(
client,
secretSync.environment?.slug || "",
secretSync.syncOptions.keySchema
);
const awsValuesRecord = await getSecretValuesRecord(client, awsSecretsRecord); const awsValuesRecord = await getSecretValuesRecord(client, awsSecretsRecord);
const { destinationConfig } = secretSync; const { destinationConfig } = secretSync;
@@ -503,11 +511,11 @@ export const AwsSecretsManagerSyncFns = {
} }
}, },
removeSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials, secretMap: TSecretMap) => { removeSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials, secretMap: TSecretMap) => {
const { destinationConfig, syncOptions } = secretSync; const { destinationConfig, syncOptions, environment } = secretSync;
const client = await getSecretsManagerClient(secretSync); const client = await getSecretsManagerClient(secretSync);
const awsSecretsRecord = await getSecretsRecord(client, syncOptions.keySchema); const awsSecretsRecord = await getSecretsRecord(client, environment?.slug || "", syncOptions.keySchema);
if (destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.OneToOne) { if (destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.OneToOne) {
for await (const secretKey of Object.keys(awsSecretsRecord)) { for await (const secretKey of Object.keys(awsSecretsRecord)) {

View File

@@ -141,7 +141,7 @@ export const azureAppConfigurationSyncFactory = ({
for await (const key of Object.keys(azureAppConfigSecrets)) { for await (const key of Object.keys(azureAppConfigSecrets)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
const azureSecret = azureAppConfigSecrets[key]; const azureSecret = azureAppConfigSecrets[key];
if ( if (

View File

@@ -194,7 +194,7 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
for await (const deleteSecretKey of deleteSecrets.filter( for await (const deleteSecretKey of deleteSecrets.filter(
(secret) => (secret) =>
matchesSchema(secret, secretSync.syncOptions.keySchema) && matchesSchema(secret, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema) &&
!setSecrets.find((setSecret) => setSecret.key === secret) !setSecrets.find((setSecret) => setSecret.key === secret)
)) { )) {
await request.delete(`${secretSync.destinationConfig.vaultBaseUrl}/secrets/${deleteSecretKey}?api-version=7.3`, { await request.delete(`${secretSync.destinationConfig.vaultBaseUrl}/secrets/${deleteSecretKey}?api-version=7.3`, {

View File

@@ -118,7 +118,7 @@ export const camundaSyncFactory = ({ kmsService, appConnectionDAL }: TCamundaSec
for await (const secret of Object.keys(camundaSecrets)) { for await (const secret of Object.keys(camundaSecrets)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(secret, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(secret, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
if (!(secret in secretMap) || !secretMap[secret].value) { if (!(secret in secretMap) || !secretMap[secret].value) {
try { try {

View File

@@ -117,7 +117,7 @@ export const databricksSyncFactory = ({ kmsService, appConnectionDAL }: TDatabri
for await (const secret of databricksSecretKeys) { for await (const secret of databricksSecretKeys) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(secret.key, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(secret.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
if (!(secret.key in secretMap)) { if (!(secret.key in secretMap)) {
await deleteDatabricksSecrets({ await deleteDatabricksSecrets({

View File

@@ -155,7 +155,7 @@ export const GcpSyncFns = {
for await (const key of Object.keys(gcpSecrets)) { for await (const key of Object.keys(gcpSecrets)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
try { try {
if (!(key in secretMap) || !secretMap[key].value) { if (!(key in secretMap) || !secretMap[key].value) {

View File

@@ -224,7 +224,8 @@ export const GithubSyncFns = {
for await (const encryptedSecret of encryptedSecrets) { for await (const encryptedSecret of encryptedSecrets) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(encryptedSecret.name, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(encryptedSecret.name, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema))
continue;
if (!(encryptedSecret.name in secretMap)) { if (!(encryptedSecret.name in secretMap)) {
await deleteSecret(client, secretSync, encryptedSecret); await deleteSecret(client, secretSync, encryptedSecret);

View File

@@ -68,6 +68,7 @@ export const HCVaultSyncFns = {
syncSecrets: async (secretSync: THCVaultSyncWithCredentials, secretMap: TSecretMap) => { syncSecrets: async (secretSync: THCVaultSyncWithCredentials, secretMap: TSecretMap) => {
const { const {
connection, connection,
environment,
destinationConfig: { mount, path }, destinationConfig: { mount, path },
syncOptions: { disableSecretDeletion, keySchema } syncOptions: { disableSecretDeletion, keySchema }
} = secretSync; } = secretSync;
@@ -97,7 +98,7 @@ export const HCVaultSyncFns = {
for await (const [key] of Object.entries(variables)) { for await (const [key] of Object.entries(variables)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, keySchema)) continue; if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
if (!(key in secretMap)) { if (!(key in secretMap)) {
delete variables[key]; delete variables[key];

View File

@@ -201,7 +201,8 @@ export const HumanitecSyncFns = {
for await (const humanitecSecret of humanitecSecrets) { for await (const humanitecSecret of humanitecSecrets) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(humanitecSecret.key, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(humanitecSecret.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema))
continue;
if (!secretMap[humanitecSecret.key]) { if (!secretMap[humanitecSecret.key]) {
await deleteSecret(secretSync, humanitecSecret); await deleteSecret(secretSync, humanitecSecret);

View File

@@ -1,5 +1,5 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import RE2 from "re2"; import handlebars from "handlebars";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault"; import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault";
@@ -68,13 +68,17 @@ type TSyncSecretDeps = {
}; };
// Add schema to secret keys // Add schema to secret keys
const addSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecretMap => { const addSchema = (unprocessedSecretMap: TSecretMap, environment: string, schema?: string): TSecretMap => {
if (!schema) return unprocessedSecretMap; if (!schema) return unprocessedSecretMap;
const processedSecretMap: TSecretMap = {}; const processedSecretMap: TSecretMap = {};
for (const [key, value] of Object.entries(unprocessedSecretMap)) { for (const [key, value] of Object.entries(unprocessedSecretMap)) {
const newKey = new RE2("{{secretKey}}").replace(schema, key); const newKey = handlebars.compile(schema)({
secretKey: key,
environment
});
processedSecretMap[newKey] = value; processedSecretMap[newKey] = value;
} }
@@ -103,21 +107,40 @@ const stripSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecret
}; };
// Checks if a key matches a schema // Checks if a key matches a schema
export const matchesSchema = (key: string, schema?: string): boolean => { export const matchesSchema = (key: string, environment: string, schema?: string): boolean => {
if (!schema) return true; if (!schema) return true;
const [prefix, suffix] = schema.split("{{secretKey}}"); const compiledSchemaPattern = handlebars.compile(schema)({
if (prefix === undefined || suffix === undefined) return true; secretKey: "{{secretKey}}", // Keep secretKey
environment
});
return key.startsWith(prefix) && key.endsWith(suffix); // This edge-case shouldn't be possible
if (!compiledSchemaPattern.includes("{{secretKey}}")) {
return key === compiledSchemaPattern;
}
const parts = compiledSchemaPattern.split("{{secretKey}}");
const prefix = parts[0];
const suffix = parts[parts.length - 1];
if (prefix === "" && suffix === "") return true;
// If prefix is empty, key must end with suffix
if (prefix === "") return key.endsWith(suffix);
// If suffix is empty, key must start with prefix
if (suffix === "") return key.startsWith(prefix);
return key.startsWith(prefix) && key.endsWith(suffix) && key.length >= prefix.length + suffix.length;
}; };
// Filter only for secrets with keys that match the schema // Filter only for secrets with keys that match the schema
const filterForSchema = (secretMap: TSecretMap, schema?: string): TSecretMap => { const filterForSchema = (secretMap: TSecretMap, environment: string, schema?: string): TSecretMap => {
const filteredMap: TSecretMap = {}; const filteredMap: TSecretMap = {};
for (const [key, value] of Object.entries(secretMap)) { for (const [key, value] of Object.entries(secretMap)) {
if (matchesSchema(key, schema)) { if (matchesSchema(key, environment, schema)) {
filteredMap[key] = value; filteredMap[key] = value;
} }
} }
@@ -131,7 +154,7 @@ export const SecretSyncFns = {
secretMap: TSecretMap, secretMap: TSecretMap,
{ kmsService, appConnectionDAL }: TSyncSecretDeps { kmsService, appConnectionDAL }: TSyncSecretDeps
): Promise<void> => { ): Promise<void> => {
const schemaSecretMap = addSchema(secretMap, secretSync.syncOptions.keySchema); const schemaSecretMap = addSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema);
switch (secretSync.destination) { switch (secretSync.destination) {
case SecretSync.AWSParameterStore: case SecretSync.AWSParameterStore:
@@ -255,14 +278,17 @@ export const SecretSyncFns = {
); );
} }
return stripSchema(filterForSchema(secretMap), secretSync.syncOptions.keySchema); return stripSchema(
filterForSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema),
secretSync.syncOptions.keySchema
);
}, },
removeSecrets: ( removeSecrets: (
secretSync: TSecretSyncWithCredentials, secretSync: TSecretSyncWithCredentials,
secretMap: TSecretMap, secretMap: TSecretMap,
{ kmsService, appConnectionDAL }: TSyncSecretDeps { kmsService, appConnectionDAL }: TSyncSecretDeps
): Promise<void> => { ): Promise<void> => {
const schemaSecretMap = addSchema(secretMap, secretSync.syncOptions.keySchema); const schemaSecretMap = addSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema);
switch (secretSync.destination) { switch (secretSync.destination) {
case SecretSync.AWSParameterStore: case SecretSync.AWSParameterStore:

View File

@@ -28,10 +28,29 @@ const BaseSyncOptionsSchema = <T extends AnyZodObject | undefined = undefined>({
keySchema: z keySchema: z
.string() .string()
.optional() .optional()
.refine((val) => !val || new RE2(/^(?:[a-zA-Z0-9_\-/]*)(?:\{\{secretKey\}\})(?:[a-zA-Z0-9_\-/]*)$/).test(val), { .refine(
message: (val) => {
"Key schema must include one {{secretKey}} and only contain letters, numbers, dashes, underscores, slashes, and the {{secretKey}} placeholder." if (!val) return true;
})
const allowedOptionalPlaceholders = ["{{environment}}"];
const allowedPlaceholdersRegexPart = ["{{secretKey}}", ...allowedOptionalPlaceholders]
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")) // Escape regex special characters
.join("|");
const allowedContentRegex = new RE2(`^([a-zA-Z0-9_\\-/]|${allowedPlaceholdersRegexPart})*$`);
const contentIsValid = allowedContentRegex.test(val);
// Check if {{secretKey}} appears exactly once
const secretKeyCount = (val.match(/\{\{secretKey\}\}/g) || []).length;
return contentIsValid && secretKeyCount === 1;
},
{
message:
"Key schema must include exactly one {{secretKey}} placeholder. It can also include {{environment}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), dashes (-), underscores (_), and slashes (/) are allowed besides the placeholders."
}
)
.describe(SecretSyncs.SYNC_OPTIONS(destination).keySchema), .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)
}); });

View File

@@ -127,7 +127,7 @@ export const TeamCitySyncFns = {
for await (const [key, variable] of Object.entries(variables)) { for await (const [key, variable] of Object.entries(variables)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue; if (!matchesSchema(key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
if (!(key in secretMap)) { if (!(key in secretMap)) {
try { try {

View File

@@ -232,8 +232,11 @@ export const TerraformCloudSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for (const terraformCloudVariable of terraformCloudVariables) { for (const terraformCloudVariable of terraformCloudVariables) {
// eslint-disable-next-line no-continue if (
if (!matchesSchema(terraformCloudVariable.key, secretSync.syncOptions.keySchema)) continue; !matchesSchema(terraformCloudVariable.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema)
)
// eslint-disable-next-line no-continue
continue;
if (!Object.prototype.hasOwnProperty.call(secretMap, terraformCloudVariable.key)) { if (!Object.prototype.hasOwnProperty.call(secretMap, terraformCloudVariable.key)) {
await deleteVariable(secretSync, terraformCloudVariable); await deleteVariable(secretSync, terraformCloudVariable);

View File

@@ -291,8 +291,9 @@ export const VercelSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const vercelSecret of vercelSecrets) { for await (const vercelSecret of vercelSecrets) {
// eslint-disable-next-line no-continue if (!matchesSchema(vercelSecret.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema))
if (!matchesSchema(vercelSecret.key, secretSync.syncOptions.keySchema)) continue; // eslint-disable-next-line no-continue
continue;
if (!secretMap[vercelSecret.key]) { if (!secretMap[vercelSecret.key]) {
await deleteSecret(secretSync, vercelSecret); await deleteSecret(secretSync, vercelSecret);

View File

@@ -128,6 +128,7 @@ export const WindmillSyncFns = {
syncSecrets: async (secretSync: TWindmillSyncWithCredentials, secretMap: TSecretMap) => { syncSecrets: async (secretSync: TWindmillSyncWithCredentials, secretMap: TSecretMap) => {
const { const {
connection, connection,
environment,
destinationConfig: { path }, destinationConfig: { path },
syncOptions: { disableSecretDeletion, keySchema } syncOptions: { disableSecretDeletion, keySchema }
} = secretSync; } = secretSync;
@@ -171,7 +172,7 @@ export const WindmillSyncFns = {
for await (const [key, variable] of Object.entries(variables)) { for await (const [key, variable] of Object.entries(variables)) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!matchesSchema(key, keySchema)) continue; if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
if (!(key in secretMap)) { if (!(key in secretMap)) {
try { try {

View File

@@ -46,7 +46,7 @@ description: "Learn how to configure a 1Password Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over 1Password when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over 1Password when keys conflict.
- **Import Secrets (Prioritize 1Password)**: Imports secrets from the destination endpoint before syncing, prioritizing values from 1Password over Infisical when keys conflict. - **Import Secrets (Prioritize 1Password)**: Imports secrets from the destination endpoint before syncing, prioritizing values from 1Password over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -40,7 +40,7 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Parameter Store when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Parameter Store when keys conflict.
- **Import Secrets (Prioritize AWS Parameter Store)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Parameter Store over Infisical when keys conflict. - **Import Secrets (Prioritize AWS Parameter Store)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Parameter Store over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -43,7 +43,7 @@ description: "Learn how to configure an AWS Secrets Manager Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict.
- **Import Secrets (Prioritize AWS Secrets Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict. - **Import Secrets (Prioritize AWS Secrets Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -48,7 +48,7 @@ description: "Learn how to configure an Azure App Configuration Sync for Infisic
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict.
- **Import Secrets (Prioritize Azure App Configuration)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict. - **Import Secrets (Prioritize Azure App Configuration)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -51,7 +51,7 @@ description: "Learn how to configure a Azure Key Vault Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict.
- **Import Secrets (Prioritize Azure Key Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict. - **Import Secrets (Prioritize Azure Key Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -39,7 +39,7 @@ description: "Learn how to configure a Camunda Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Camunda when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Camunda when keys conflict.
- **Import Secrets (Prioritize Camunda)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Camunda over Infisical when keys conflict. - **Import Secrets (Prioritize Camunda)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Camunda over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -46,7 +46,7 @@ description: "Learn how to configure a Databricks Sync for Infisical."
<Note> <Note>
Databricks does not support importing secrets. Databricks does not support importing secrets.
</Note> </Note>
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -42,7 +42,7 @@ description: "Learn how to configure a GCP Secret Manager Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over GCP Secret Manager when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over GCP Secret Manager when keys conflict.
- **Import Secrets (Prioritize GCP Secret Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from GCP Secret Manager over Infisical when keys conflict. - **Import Secrets (Prioritize GCP Secret Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from GCP Secret Manager over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -62,7 +62,7 @@ description: "Learn how to configure a GitHub Sync for Infisical."
<Note> <Note>
GitHub does not support importing secrets. GitHub does not support importing secrets.
</Note> </Note>
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -54,7 +54,7 @@ description: "Learn how to configure a Hashicorp Vault Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Hashicorp Vault when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Hashicorp Vault when keys conflict.
- **Import Secrets (Prioritize Hashicorp Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Hashicorp Vault over Infisical when keys conflict. - **Import Secrets (Prioritize Hashicorp Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Hashicorp Vault over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -55,7 +55,7 @@ description: "Learn how to configure a Humanitec Sync for Infisical."
<Note> <Note>
Humanitec does not support importing secrets. Humanitec does not support importing secrets.
</Note> </Note>
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -57,7 +57,7 @@ description: "Learn how to configure an Oracle Cloud Infrastructure Vault Sync f
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over OCI Vault when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over OCI Vault when keys conflict.
- **Import Secrets (Prioritize OCI Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from OCI Vault over Infisical when keys conflict. - **Import Secrets (Prioritize OCI Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from OCI Vault over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -48,7 +48,7 @@ description: "Learn how to configure a TeamCity Sync for Infisical."
<Note> <Note>
Infisical only syncs secrets from within the target scope; inherited secrets will not be imported. Infisical only syncs secrets from within the target scope; inherited secrets will not be imported.
</Note> </Note>
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -56,7 +56,7 @@ description: "Learn how to configure a Terraform Cloud Sync for Infisical."
<Note> <Note>
Terraform Cloud does not support importing secrets. Terraform Cloud does not support importing secrets.
</Note> </Note>
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -43,7 +43,7 @@ description: "Learn how to configure a Vercel Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Vercel when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Vercel when keys conflict.
- **Import Secrets (Prioritize Vercel)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Vercel over Infisical when keys conflict. - **Import Secrets (Prioritize Vercel)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Vercel over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -44,7 +44,7 @@ description: "Learn how to configure a Windmill Sync for Infisical."
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Windmill when keys conflict. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Windmill when keys conflict.
- **Import Secrets (Prioritize Windmill)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Windmill over Infisical when keys conflict. - **Import Secrets (Prioritize Windmill)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Windmill over Infisical when keys conflict.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and {{environment}} for the environment.
<Note> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note> </Note>

View File

@@ -13,11 +13,27 @@ export const BaseSecretSyncSchema = <T extends AnyZodObject | undefined = undefi
.string() .string()
.optional() .optional()
.refine( .refine(
(val) => (val) => {
!val || /^(?:[a-zA-Z0-9_\-/]*)(?:\{\{secretKey\}\})(?:[a-zA-Z0-9_\-/]*)$/.test(val), if (!val) return true;
const allowedOptionalPlaceholders = ["{{environment}}"];
const allowedPlaceholdersRegexPart = ["{{secretKey}}", ...allowedOptionalPlaceholders]
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")) // Escape regex special characters
.join("|");
const allowedContentRegex = new RegExp(
`^([a-zA-Z0-9_\\-/]|${allowedPlaceholdersRegexPart})*$`
);
const contentIsValid = allowedContentRegex.test(val);
const secretKeyCount = (val.match(/\{\{secretKey\}\}/g) || []).length;
return contentIsValid && secretKeyCount === 1;
},
{ {
message: message:
"Key schema must include one {{secretKey}} and only contain letters, numbers, dashes, underscores, slashes, and the {{secretKey}} placeholder." "Key schema must include exactly one {{secretKey}} placeholder. It can also include {{environment}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), dashes (-), underscores (_), and slashes (/) are allowed besides the placeholders."
} }
) )
}); });