Merge pull request #3732 from Infisical/ENG-2809

Add {{environment}} support for key schemas
This commit is contained in:
x032205
2025-06-05 16:27:22 -04:00
committed by GitHub
37 changed files with 176 additions and 66 deletions
@@ -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) {
@@ -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 {
@@ -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);
@@ -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)) {
@@ -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 (
@@ -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`, {
@@ -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 {
@@ -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({
@@ -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) {
@@ -223,8 +223,9 @@ export const GithubSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const encryptedSecret of encryptedSecrets) { for await (const encryptedSecret of encryptedSecrets) {
// eslint-disable-next-line no-continue if (!matchesSchema(encryptedSecret.name, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema))
if (!matchesSchema(encryptedSecret.name, secretSync.syncOptions.keySchema)) continue; // eslint-disable-next-line no-continue
continue;
if (!(encryptedSecret.name in secretMap)) { if (!(encryptedSecret.name in secretMap)) {
await deleteSecret(client, secretSync, encryptedSecret); await deleteSecret(client, secretSync, encryptedSecret);
@@ -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];
@@ -200,8 +200,9 @@ export const HumanitecSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const humanitecSecret of humanitecSecrets) { for await (const humanitecSecret of humanitecSecrets) {
// eslint-disable-next-line no-continue if (!matchesSchema(humanitecSecret.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema))
if (!matchesSchema(humanitecSecret.key, secretSync.syncOptions.keySchema)) continue; // eslint-disable-next-line no-continue
continue;
if (!secretMap[humanitecSecret.key]) { if (!secretMap[humanitecSecret.key]) {
await deleteSecret(secretSync, humanitecSecret); await deleteSecret(secretSync, humanitecSecret);
@@ -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;
} }
@@ -82,10 +86,17 @@ const addSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecretMa
}; };
// Strip schema from secret keys // Strip schema from secret keys
const stripSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecretMap => { const stripSchema = (unprocessedSecretMap: TSecretMap, environment: string, schema?: string): TSecretMap => {
if (!schema) return unprocessedSecretMap; if (!schema) return unprocessedSecretMap;
const [prefix, suffix] = schema.split("{{secretKey}}"); const compiledSchemaPattern = handlebars.compile(schema)({
secretKey: "{{secretKey}}", // Keep secretKey
environment
});
const parts = compiledSchemaPattern.split("{{secretKey}}");
const prefix = parts[0];
const suffix = parts[parts.length - 1];
const strippedMap: TSecretMap = {}; const strippedMap: TSecretMap = {};
@@ -103,21 +114,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 +161,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 +285,16 @@ export const SecretSyncFns = {
); );
} }
return stripSchema(filterForSchema(secretMap), secretSync.syncOptions.keySchema); const filtered = filterForSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema);
const stripped = stripSchema(filtered, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema);
return stripped;
}, },
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:
@@ -28,10 +28,30 @@ 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}} is present
const secretKeyRegex = new RE2(/\{\{secretKey\}\}/);
const secretKeyIsPresent = secretKeyRegex.test(val);
return contentIsValid && secretKeyIsPresent;
},
{
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)
}); });
@@ -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 {
@@ -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);
@@ -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);
@@ -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 {
+1 -1
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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
+1 -1
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>
@@ -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>
@@ -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>
+1 -1
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>
@@ -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>
+1 -1
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>
+1 -1
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>
@@ -101,6 +101,10 @@ Key Schemas transform your secret keys by applying a prefix, suffix, or format p
Any destination secrets which do not match the schema will not get deleted or updated by Infisical. Any destination secrets which do not match the schema will not get deleted or updated by Infisical.
Key Schemas use handlebars syntax to define dynamic values. Here's a full list of available variables:
- `{{secretKey}}` - The key of the secret
- `{{environment}}` - The environment which the secret is in (e.g. dev, staging, prod)
**Example:** **Example:**
- Infisical key: `SECRET_1` - Infisical key: `SECRET_1`
- Schema: `INFISICAL_{{secretKey}}` - Schema: `INFISICAL_{{secretKey}}`
+1 -1
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>
@@ -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>
+1 -1
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>
+1 -1
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>
@@ -131,7 +131,27 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl <FormControl
tooltipClassName="max-w-md" tooltipClassName="max-w-md"
tooltipText="When a secret is synced, its key will be injected into the key schema before it reaches the destination. This is useful for organization." tooltipText={
<div className="flex flex-col gap-3">
<span>
When a secret is synced, values will be injected into the key schema before it
reaches the destination. This is useful for organization.
</span>
<div className="flex flex-col">
<span>Available keys:</span>
<ul className="list-disc pl-4 text-sm">
<li>
<code>{"{{secretKey}}"}</code> - The key of the secret
</li>
<li>
<code>{"{{environment}}"}</code> - The environment which the secret is in
(e.g. dev, staging, prod)
</li>
</ul>
</div>
</div>
}
isError={Boolean(error)} isError={Boolean(error)}
isOptional isOptional
errorText={error?.message} errorText={error?.message}
@@ -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."
} }
) )
}); });