diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index 367e9265af..5aa50f334a 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -146,23 +146,23 @@ export const dynamicSecretLeaseServiceFactory = ({ let result; try { - let identityName = ""; + const identity: { name: string } = { name: "" }; if (actor === ActorType.USER) { const user = await userDAL.findById(actorId); if (user) { - identityName = extractEmailUsername(user.username); + identity.name = extractEmailUsername(user.username); } } else if (actor === ActorType.Machine) { - const identity = await identityDAL.findById(actorId); - if (identity) { - identityName = identity.name; + const machineIdentity = await identityDAL.findById(actorId); + if (machineIdentity) { + identity.name = machineIdentity.name; } } result = await selectedProvider.create({ inputs: decryptedStoredInput, expireAt: expireAt.getTime(), usernameTemplate: dynamicSecretCfg.usernameTemplate, - identityName + identity }); } catch (error: unknown) { if (error && typeof error === "object" && error !== null && "sqlMessage" in error) { diff --git a/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts b/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts index 701efc2bd9..89371f1bd4 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts @@ -133,14 +133,14 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; const randomUsername = `inf-${customAlphabet(charset, 32)()}`; if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -179,15 +179,17 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { + name: string; + }; }) => { - const { inputs, expireAt, usernameTemplate, identityName } = data; + const { inputs, expireAt, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); if (!(await validateConnection(providerInputs))) { throw new BadRequestError({ message: "Failed to establish connection" }); } - const leaseUsername = generateUsername(usernameTemplate, identityName); + const leaseUsername = generateUsername(usernameTemplate, identity); const leasePassword = generatePassword(); const leaseExpiration = new Date(expireAt).toISOString(); diff --git a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts index 57c12485e7..205612ac53 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -24,14 +24,14 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; import { compileUsernameTemplate } from "./templateUtils"; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -65,14 +65,16 @@ export const AwsIamProvider = (): TDynamicProviderFns => { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { + name: string; + }; }) => { - const { inputs, usernameTemplate, identityName } = data; + const { inputs, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs; const createUserRes = await client.send( new CreateUserCommand({ diff --git a/backend/src/ee/services/dynamic-secret/providers/cassandra.ts b/backend/src/ee/services/dynamic-secret/providers/cassandra.ts index cc3f1fa6ac..b939dcad6a 100644 --- a/backend/src/ee/services/dynamic-secret/providers/cassandra.ts +++ b/backend/src/ee/services/dynamic-secret/providers/cassandra.ts @@ -15,13 +15,13 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -80,13 +80,13 @@ export const CassandraProvider = (): TDynamicProviderFns => { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { name: string }; }) => { - const { inputs, expireAt, usernameTemplate, identityName } = data; + const { inputs, expireAt, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); const { keyspace } = providerInputs; const expiration = new Date(expireAt).toISOString(); diff --git a/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts b/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts index b8f1ae588d..32d21ee76e 100644 --- a/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts +++ b/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts @@ -13,13 +13,13 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -71,12 +71,12 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => { return infoResponse; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { - const { inputs, usernameTemplate, identityName } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { + const { inputs, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); await connection.security.putUser({ diff --git a/backend/src/ee/services/dynamic-secret/providers/ldap.ts b/backend/src/ee/services/dynamic-secret/providers/ldap.ts index 447ce4a946..1de8aa1e6f 100644 --- a/backend/src/ee/services/dynamic-secret/providers/ldap.ts +++ b/backend/src/ee/services/dynamic-secret/providers/ldap.ts @@ -23,13 +23,13 @@ const encodePassword = (password?: string) => { return base64Password; }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -197,8 +197,8 @@ export const LdapProvider = (): TDynamicProviderFns => { return dnArray; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { - const { inputs, usernameTemplate, identityName } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { + const { inputs, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); @@ -225,7 +225,7 @@ export const LdapProvider = (): TDynamicProviderFns => { }); } } else { - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif }); diff --git a/backend/src/ee/services/dynamic-secret/providers/models.ts b/backend/src/ee/services/dynamic-secret/providers/models.ts index f3b2a7ab02..5681b8f770 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -364,7 +364,9 @@ export type TDynamicProviderFns = { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { + name: string; + }; }) => Promise<{ entityId: string; data: unknown }>; validateConnection: (inputs: unknown) => Promise; validateProviderInputs: (inputs: object) => Promise; diff --git a/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts b/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts index 0baee26a8b..6da8b4b4ef 100644 --- a/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts +++ b/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts @@ -13,13 +13,13 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -68,13 +68,13 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { name: string }; }) => { - const { inputs, expireAt, usernameTemplate, identityName } = data; + const { inputs, expireAt, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); const expiration = new Date(expireAt).toISOString(); await client({ diff --git a/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts b/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts index 4a25e8ce85..331a355a76 100644 --- a/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts +++ b/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts @@ -13,13 +13,13 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -60,12 +60,12 @@ export const MongoDBProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { - const { inputs, usernameTemplate, identityName } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { + const { inputs, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); const db = client.db(providerInputs.database); diff --git a/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts b/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts index 91156fa3e2..76081c86cf 100644 --- a/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts +++ b/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts @@ -15,13 +15,13 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -117,12 +117,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => { return infoResponse; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { - const { inputs, usernameTemplate, identityName } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { + const { inputs, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); await createRabbitMqUser({ diff --git a/backend/src/ee/services/dynamic-secret/providers/redis.ts b/backend/src/ee/services/dynamic-secret/providers/redis.ts index 8584891091..989ed96dc0 100644 --- a/backend/src/ee/services/dynamic-secret/providers/redis.ts +++ b/backend/src/ee/services/dynamic-secret/providers/redis.ts @@ -16,13 +16,13 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -126,13 +126,13 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { name: string }; }) => { - const { inputs, expireAt, usernameTemplate, identityName } = data; + const { inputs, expireAt, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); const expiration = new Date(expireAt).toISOString(); diff --git a/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts b/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts index f8b89f8b9b..9c13d3efc4 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts @@ -16,13 +16,13 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = `inf_${alphaNumericNanoId(25)}`; // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -88,11 +88,11 @@ export const SapAseProvider = (): TDynamicProviderFns => { return true; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { - const { inputs, usernameTemplate, identityName } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { + const { inputs, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); const client = await $getClient(providerInputs); diff --git a/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts b/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts index 1c7357faca..5c8a755551 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts @@ -22,13 +22,13 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -102,12 +102,12 @@ export const SapHanaProvider = (): TDynamicProviderFns => { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { name: string }; }) => { - const { inputs, expireAt, usernameTemplate, identityName } = data; + const { inputs, expireAt, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); const expiration = new Date(expireAt).toISOString(); diff --git a/backend/src/ee/services/dynamic-secret/providers/snowflake.ts b/backend/src/ee/services/dynamic-secret/providers/snowflake.ts index 110679ed9f..9e97ecd302 100644 --- a/backend/src/ee/services/dynamic-secret/providers/snowflake.ts +++ b/backend/src/ee/services/dynamic-secret/providers/snowflake.ts @@ -18,13 +18,13 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = `infisical_${alphaNumericNanoId(32)}`; // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName + identity }); }; @@ -93,14 +93,14 @@ export const SnowflakeProvider = (): TDynamicProviderFns => { inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { name: string }; }) => { - const { inputs, expireAt, usernameTemplate, identityName } = data; + const { inputs, expireAt, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate, identityName); + const username = generateUsername(usernameTemplate, identity); const password = generatePassword(); try { diff --git a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts index 13fc81b062..d378ee9165 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts @@ -105,7 +105,7 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire } }; -const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null, identityName?: string) => { +const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null, identity?: { name: string }) => { let randomUsername = ""; // For oracle, the client assumes everything is upper case when not using quotes around the password if (provider === SqlProviders.Oracle) { @@ -117,7 +117,7 @@ const generateUsername = (provider: SqlProviders, usernameTemplate?: string | nu return compileUsernameTemplate({ usernameTemplate, randomUsername, - identityName, + identity, options: { toUpperCase: provider === SqlProviders.Oracle } @@ -227,12 +227,12 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) inputs: unknown; expireAt: number; usernameTemplate?: string | null; - identityName?: string; + identity?: { name: string }; }) => { - const { inputs, expireAt, usernameTemplate, identityName } = data; + const { inputs, expireAt, usernameTemplate, identity } = data; const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(providerInputs.client, usernameTemplate, identityName); + const username = generateUsername(providerInputs.client, usernameTemplate, identity); const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements); const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => { diff --git a/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts b/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts index 76d923ad1b..ecff317afa 100644 --- a/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts +++ b/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts @@ -2,73 +2,68 @@ import handlebars from "handlebars"; import RE2 from "re2"; +import { logger } from "@app/lib/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; export const compileUsernameTemplate = ({ usernameTemplate, randomUsername, - identityName, + identity, unixTimestamp, options }: { usernameTemplate: string; randomUsername: string; - identityName?: string; + identity?: { name: string }; unixTimestamp?: number; options?: { toUpperCase?: boolean; }; }): string => { - // Pre-process template to replace {{random-N}} patterns before compiling + // Create isolated handlebars instance + const hbs = handlebars.create(); + + // Pre-process template to replace {{random N}} patterns using RE2 let processedTemplate = usernameTemplate; - const randomPattern = /\{\{random-(\d+)\}\}/g; - let match; - // eslint-disable-next-line no-cond-assign - while ((match = randomPattern.exec(usernameTemplate)) !== null) { - const fullMatch = match[0]; - const length = parseInt(match[1], 10); + try { + const randomPattern = new RE2("\\{\\{random (\\d+)\\}\\}", "g"); - if (length > 0 && length <= 100) { - const randomValue = alphaNumericNanoId(length); - processedTemplate = processedTemplate.replace(fullMatch, randomValue); - } + // Use RE2's replace with callback function + processedTemplate = randomPattern.replace(processedTemplate, (fullMatch: string, lengthStr: string) => { + const length = parseInt(lengthStr, 10); + + if (length > 0 && length <= 100) { + return alphaNumericNanoId(length); + } + + // Return original match if invalid length + return fullMatch; + }); + } catch (error) { + logger.error(error, "RE2 pattern failed, using original template"); } - // Register replace helper - handlebars.registerHelper( - "replace", - function (text: string, searchValue: string, replaceValue: string, limit?: number) { - // Convert to string if it's not already - const textStr = String(text || ""); - if (!textStr) { - return textStr; - } - - try { - const re2Pattern = new RE2(searchValue, "g"); - - if (limit && limit > 0) { - // Replace only up to the specified limit - let count = 0; - return textStr.replace(re2Pattern, (textMatch) => { - if (count < limit) { - count += 1; - return replaceValue; - } - return textMatch; - }); - } - // Replace all occurrences - return textStr.replace(re2Pattern, replaceValue); - } catch (error) { - return textStr; - } + // Register replace helper on local instance + hbs.registerHelper("replace", function (text: string, searchValue: string, replaceValue: string) { + // Convert to string if it's not already + const textStr = String(text || ""); + if (!textStr) { + return textStr; } - ); - // Register truncate helper - handlebars.registerHelper("truncate", function (text: string, length: number) { + try { + const re2Pattern = new RE2(searchValue, "g"); + // Replace all occurrences + return textStr.replace(re2Pattern, replaceValue); + } catch (error) { + logger.error(error, "RE2 pattern failed, using original template"); + return textStr; + } + }); + + // Register truncate helper on local instance + hbs.registerHelper("truncate", function (text: string, length: number) { // Convert to string if it's not already const textStr = String(text || ""); if (!textStr) { @@ -79,16 +74,16 @@ export const compileUsernameTemplate = ({ return textStr.substring(0, length); }); - // Compile template with context + // Compile template with context using local instance const context = { randomUsername, unixTimestamp: unixTimestamp || Math.floor(Date.now() / 100), identity: { - name: identityName + name: identity?.name } }; - const result = handlebars.compile(processedTemplate)(context); + const result = hbs.compile(processedTemplate)(context); if (options?.toUpperCase) { return result.toUpperCase(); diff --git a/backend/src/lib/template/validate-handlebars.ts b/backend/src/lib/template/validate-handlebars.ts index e312a1d800..4aa0d1f63a 100644 --- a/backend/src/lib/template/validate-handlebars.ts +++ b/backend/src/lib/template/validate-handlebars.ts @@ -1,5 +1,4 @@ import handlebars from "handlebars"; -import RE2 from "re2"; import { BadRequestError } from "../errors"; import { logger } from "../logger"; @@ -8,16 +7,9 @@ type SanitizationArg = { allowedExpressions?: (arg: string) => boolean; }; -const randomPattern = new RE2("^random-\\d+$"); - const isValidExpression = (expression: string, dto: SanitizationArg): boolean => { - // Check for random-N pattern (e.g., random-16, random-8) - if (expression.startsWith("random-") && randomPattern.test(expression)) { - return true; - } - // Allow helper functions (replace, truncate) - const allowedHelpers = ["replace", "truncate"]; + const allowedHelpers = ["replace", "truncate", "random"]; if (allowedHelpers.includes(expression)) { return true; } diff --git a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx index b7c287d338..6c70612c88 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx @@ -102,7 +102,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx index 5c929feb25..fb3dd755d9 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx @@ -112,7 +112,7 @@ Replace **\** with your AWS account id and **\** w - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/cassandra.mdx b/docs/documentation/platform/dynamic-secrets/cassandra.mdx index 5928e7315f..56b7ec3366 100644 --- a/docs/documentation/platform/dynamic-secrets/cassandra.mdx +++ b/docs/documentation/platform/dynamic-secrets/cassandra.mdx @@ -86,7 +86,7 @@ The above configuration allows user creation and granting permissions. - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx index 560e10d4d9..06d1102e21 100644 --- a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx +++ b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx @@ -94,7 +94,7 @@ The port that your Elasticsearch instance is running on. _(Example: 9200)_ - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/ldap.mdx b/docs/documentation/platform/dynamic-secrets/ldap.mdx index 11557c6c86..ddaaf91019 100644 --- a/docs/documentation/platform/dynamic-secrets/ldap.mdx +++ b/docs/documentation/platform/dynamic-secrets/ldap.mdx @@ -130,7 +130,7 @@ The Infisical LDAP dynamic secret allows you to generate user credentials on dem - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx index 63ad908a02..f167a06419 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx @@ -70,7 +70,7 @@ Create a project scoped API Key with the required permission in your Mongo Atlas - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/mongo-db.mdx b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx index 6815645636..7c4ebb5689 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-db.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx @@ -73,7 +73,7 @@ Create a user with the required permission in your MongoDB instance. This user w - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/mssql.mdx b/docs/documentation/platform/dynamic-secrets/mssql.mdx index 3347ebca48..6b6cef9827 100644 --- a/docs/documentation/platform/dynamic-secrets/mssql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mssql.mdx @@ -78,7 +78,7 @@ Create a user with the required permission in your SQL instance. This user will - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/mysql.mdx b/docs/documentation/platform/dynamic-secrets/mysql.mdx index 82f76bb467..33d11a4fc8 100644 --- a/docs/documentation/platform/dynamic-secrets/mysql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mysql.mdx @@ -76,7 +76,7 @@ Create a user with the required permission in your SQL instance. This user will - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/oracle.mdx b/docs/documentation/platform/dynamic-secrets/oracle.mdx index 0f9c0aff15..3c83f33598 100644 --- a/docs/documentation/platform/dynamic-secrets/oracle.mdx +++ b/docs/documentation/platform/dynamic-secrets/oracle.mdx @@ -78,7 +78,7 @@ Create a user with the required permission in your SQL instance. This user will - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/postgresql.mdx b/docs/documentation/platform/dynamic-secrets/postgresql.mdx index 29cf35c6ae..974066e142 100644 --- a/docs/documentation/platform/dynamic-secrets/postgresql.mdx +++ b/docs/documentation/platform/dynamic-secrets/postgresql.mdx @@ -79,7 +79,7 @@ Create a user with the required permission in your SQL instance. This user will - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx index 9cd82f4991..be41901b76 100644 --- a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx +++ b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx @@ -72,7 +72,7 @@ The port that the RabbitMQ management plugin is listening on. This is `15672` by - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/redis.mdx b/docs/documentation/platform/dynamic-secrets/redis.mdx index 6b3373c6cb..8e28c3cb25 100644 --- a/docs/documentation/platform/dynamic-secrets/redis.mdx +++ b/docs/documentation/platform/dynamic-secrets/redis.mdx @@ -64,7 +64,7 @@ Create a user with the required permission in your Redis instance. This user wil - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx index 313533e5d9..6da572f353 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx @@ -71,7 +71,7 @@ The Infisical SAP ASE dynamic secret allows you to generate SAP ASE database cre - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx index 413e49c78a..8ccd842f21 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx @@ -71,7 +71,7 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length diff --git a/docs/documentation/platform/dynamic-secrets/snowflake.mdx b/docs/documentation/platform/dynamic-secrets/snowflake.mdx index e6cad24326..f0bbaa08ca 100644 --- a/docs/documentation/platform/dynamic-secrets/snowflake.mdx +++ b/docs/documentation/platform/dynamic-secrets/snowflake.mdx @@ -84,7 +84,7 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - `{{identity.name}}`: Name of the identity that is generating the secret - - `{{random-N}}`: Random string of N characters + - `{{random N}}`: Random string of N characters Allowed template functions are - `truncate`: Truncates a string to a specified length