From 419e9ac755beb85fdec4a36989007edb79442450 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Tue, 3 Jun 2025 21:21:36 -0300 Subject: [PATCH 1/5] Add identityName to Dynamic Secrets userName template --- .../src/ee/routes/v1/dynamic-secret-router.ts | 2 +- .../dynamic-secret-lease-service.ts | 31 +++++++++++++++++-- .../providers/aws-elasticache.ts | 16 +++++++--- .../dynamic-secret/providers/aws-iam.ts | 16 +++++++--- .../dynamic-secret/providers/cassandra.ts | 16 +++++++--- .../providers/elastic-search.ts | 11 ++++--- .../services/dynamic-secret/providers/ldap.ts | 11 ++++--- .../dynamic-secret/providers/models.ts | 1 + .../dynamic-secret/providers/mongo-atlas.ts | 16 +++++++--- .../dynamic-secret/providers/mongo-db.ts | 11 ++++--- .../dynamic-secret/providers/rabbit-mq.ts | 11 ++++--- .../dynamic-secret/providers/redis.ts | 16 +++++++--- .../dynamic-secret/providers/sap-ase.ts | 11 ++++--- .../dynamic-secret/providers/sap-hana.ts | 16 +++++++--- .../dynamic-secret/providers/snowflake.ts | 16 +++++++--- .../dynamic-secret/providers/sql-database.ts | 17 ++++++---- backend/src/server/routes/index.ts | 4 ++- .../dynamic-secrets/aws-elasticache.mdx | 1 + .../platform/dynamic-secrets/aws-iam.mdx | 1 + .../platform/dynamic-secrets/cassandra.mdx | 1 + .../dynamic-secrets/elastic-search.mdx | 1 + .../platform/dynamic-secrets/ldap.mdx | 1 + .../platform/dynamic-secrets/mongo-atlas.mdx | 1 + .../platform/dynamic-secrets/mongo-db.mdx | 1 + .../platform/dynamic-secrets/mssql.mdx | 1 + .../platform/dynamic-secrets/mysql.mdx | 1 + .../platform/dynamic-secrets/oracle.mdx | 1 + .../platform/dynamic-secrets/postgresql.mdx | 1 + .../platform/dynamic-secrets/rabbit-mq.mdx | 1 + .../platform/dynamic-secrets/redis.mdx | 1 + .../platform/dynamic-secrets/sap-ase.mdx | 1 + .../platform/dynamic-secrets/sap-hana.mdx | 1 + .../platform/dynamic-secrets/snowflake.mdx | 1 + 33 files changed, 168 insertions(+), 70 deletions(-) diff --git a/backend/src/ee/routes/v1/dynamic-secret-router.ts b/backend/src/ee/routes/v1/dynamic-secret-router.ts index bf5cce7d5..2cff70843 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-router.ts @@ -33,7 +33,7 @@ const userTemplateSchema = z .refine((el) => validateUsernameTemplateCharacters(el)) .refine((el) => isValidHandleBarTemplate(el, { - allowedExpressions: (val) => ["randomUsername", "unixTimestamp"].includes(val) + allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identityName"].includes(val) }) ); 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 f3f3f3acd..367e9265a 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 @@ -1,4 +1,5 @@ import { ForbiddenError, subject } from "@casl/ability"; +import RE2 from "re2"; import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -11,10 +12,13 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { ms } from "@app/lib/ms"; +import { ActorType } from "@app/services/auth/auth-type"; +import { TIdentityDALFactory } from "@app/services/identity/identity-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; +import { TUserDALFactory } from "@app/services/user/user-dal"; import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal"; import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models"; @@ -39,6 +43,8 @@ type TDynamicSecretLeaseServiceFactoryDep = { permissionService: Pick; projectDAL: Pick; kmsService: Pick; + userDAL: Pick; + identityDAL: TIdentityDALFactory; }; export type TDynamicSecretLeaseServiceFactory = ReturnType; @@ -52,8 +58,16 @@ export const dynamicSecretLeaseServiceFactory = ({ dynamicSecretQueueService, projectDAL, licenseService, - kmsService + kmsService, + userDAL, + identityDAL }: TDynamicSecretLeaseServiceFactoryDep) => { + const extractEmailUsername = (email: string) => { + const regex = new RE2(/^([^@]+)/); + const match = email.match(regex); + return match ? match[1] : email; + }; + const create = async ({ environmentSlug, path, @@ -132,10 +146,23 @@ export const dynamicSecretLeaseServiceFactory = ({ let result; try { + let identityName = ""; + if (actor === ActorType.USER) { + const user = await userDAL.findById(actorId); + if (user) { + identityName = extractEmailUsername(user.username); + } + } else if (actor === ActorType.Machine) { + const identity = await identityDAL.findById(actorId); + if (identity) { + identityName = identity.name; + } + } result = await selectedProvider.create({ inputs: decryptedStoredInput, expireAt: expireAt.getTime(), - usernameTemplate: dynamicSecretCfg.usernameTemplate + usernameTemplate: dynamicSecretCfg.usernameTemplate, + identityName }); } 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 56fa110d1..e21d26a9e 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts @@ -132,14 +132,15 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; const randomUsername = `inf-${customAlphabet(charset, 32)()}`; if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -174,14 +175,19 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => { return true; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, expireAt, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, expireAt, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); if (!(await validateConnection(providerInputs))) { throw new BadRequestError({ message: "Failed to establish connection" }); } - const leaseUsername = generateUsername(usernameTemplate); + const leaseUsername = generateUsername(usernameTemplate, identityName); 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 9d8e10f60..6eabd31aa 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -24,13 +24,14 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -60,13 +61,18 @@ export const AwsIamProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 fce23b56f..c0317df4b 100644 --- a/backend/src/ee/services/dynamic-secret/providers/cassandra.ts +++ b/backend/src/ee/services/dynamic-secret/providers/cassandra.ts @@ -14,13 +14,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -75,12 +76,17 @@ export const CassandraProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, expireAt, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, expireAt, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 066822827..c4a9afad3 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,14 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -71,12 +72,12 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => { return infoResponse; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { - const { inputs, usernameTemplate } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { + const { inputs, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 d0e3fbe66..06196a3b0 100644 --- a/backend/src/ee/services/dynamic-secret/providers/ldap.ts +++ b/backend/src/ee/services/dynamic-secret/providers/ldap.ts @@ -22,13 +22,14 @@ const encodePassword = (password?: string) => { return base64Password; }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -196,8 +197,8 @@ export const LdapProvider = (): TDynamicProviderFns => { return dnArray; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { - const { inputs, usernameTemplate } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { + const { inputs, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); @@ -224,7 +225,7 @@ export const LdapProvider = (): TDynamicProviderFns => { }); } } else { - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 ed59f4d67..f3b2a7ab0 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -364,6 +364,7 @@ export type TDynamicProviderFns = { inputs: unknown; expireAt: number; usernameTemplate?: string | null; + identityName?: 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 8f8bf9430..5ac479393 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,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -64,12 +65,17 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, expireAt, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, expireAt, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 0a15209e0..0d1a4fd27 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,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -60,12 +61,12 @@ export const MongoDBProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { - const { inputs, usernameTemplate } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { + const { inputs, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 e7d90d272..48c37179e 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,14 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -117,12 +118,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => { return infoResponse; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { - const { inputs, usernameTemplate } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { + const { inputs, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 855af2e29..6b0e71913 100644 --- a/backend/src/ee/services/dynamic-secret/providers/redis.ts +++ b/backend/src/ee/services/dynamic-secret/providers/redis.ts @@ -15,13 +15,14 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -121,12 +122,17 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => { return pingResponse; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, expireAt, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, expireAt, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 af2431058..18dcbb90d 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts @@ -15,13 +15,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: 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 handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -87,11 +88,11 @@ export const SapAseProvider = (): TDynamicProviderFns => { return true; }; - const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { - const { inputs, usernameTemplate } = data; + const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => { + const { inputs, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 654e2d144..f5ccb222b 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts @@ -21,13 +21,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -97,11 +98,16 @@ export const SapHanaProvider = (): TDynamicProviderFns => { return testResult; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, expireAt, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, expireAt, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 571d488c9..da8670aff 100644 --- a/backend/src/ee/services/dynamic-secret/providers/snowflake.ts +++ b/backend/src/ee/services/dynamic-secret/providers/snowflake.ts @@ -17,13 +17,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = (usernameTemplate?: string | null) => { +const generateUsername = (usernameTemplate?: string | null, identityName?: 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 handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -88,13 +89,18 @@ export const SnowflakeProvider = (): TDynamicProviderFns => { return isValidConnection; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, expireAt, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, expireAt, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(usernameTemplate); + const username = generateUsername(usernameTemplate, identityName); 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 ce16a1237..d20b1dd06 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts @@ -104,9 +104,8 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire } }; -const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null) => { +const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null, identityName?: string) => { let randomUsername = ""; - // For oracle, the client assumes everything is upper case when not using quotes around the password if (provider === SqlProviders.Oracle) { randomUsername = alphaNumericNanoId(32).toUpperCase(); @@ -117,7 +116,8 @@ const generateUsername = (provider: SqlProviders, usernameTemplate?: string | nu return handlebars.compile(usernameTemplate)({ randomUsername, - unixTimestamp: Math.floor(Date.now() / 100) + unixTimestamp: Math.floor(Date.now() / 100), + identityName }); }; @@ -220,11 +220,16 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) return isConnected; }; - const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { - const { inputs, expireAt, usernameTemplate } = data; + const create = async (data: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + identityName?: string; + }) => { + const { inputs, expireAt, usernameTemplate, identityName } = data; const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(providerInputs.client, usernameTemplate); + const username = generateUsername(providerInputs.client, usernameTemplate, identityName); const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements); const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 422f5162b..6d7be07c6 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1508,7 +1508,9 @@ export const registerRoutes = async ( dynamicSecretProviders, folderDAL, licenseService, - kmsService + kmsService, + userDAL, + identityDAL }); const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({ auditLogDAL, diff --git a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx index 66c4c706a..c91b1acf7 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx @@ -101,6 +101,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret If you want to provide specific privileges for the generated dynamic credentials, you can modify the ElastiCache statement to your needs. This is useful if you want to only give access to a specific resource. diff --git a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx index 56a10419c..bc563a52e 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx @@ -111,6 +111,7 @@ Specifies a template for generating usernames. This field allows customization o Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp +- `{{identityName}}`: Name of the identity that is generating the secret ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png) diff --git a/docs/documentation/platform/dynamic-secrets/cassandra.mdx b/docs/documentation/platform/dynamic-secrets/cassandra.mdx index 628432bea..6752faa66 100644 --- a/docs/documentation/platform/dynamic-secrets/cassandra.mdx +++ b/docs/documentation/platform/dynamic-secrets/cassandra.mdx @@ -85,6 +85,7 @@ The above configuration allows user creation and granting permissions. Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret If you want to provide specific privileges for the generated dynamic credentials, you can modify the CQL statement to your needs. This is useful if you want to only give access to a specific key-space(s). diff --git a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx index 6c5028bb2..eec481397 100644 --- a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx +++ b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx @@ -93,6 +93,7 @@ The port that your Elasticsearch instance is running on. _(Example: 9200)_ Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png) diff --git a/docs/documentation/platform/dynamic-secrets/ldap.mdx b/docs/documentation/platform/dynamic-secrets/ldap.mdx index 1a3eca404..bb6fd0e07 100644 --- a/docs/documentation/platform/dynamic-secrets/ldap.mdx +++ b/docs/documentation/platform/dynamic-secrets/ldap.mdx @@ -129,6 +129,7 @@ The Infisical LDAP dynamic secret allows you to generate user credentials on dem Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret diff --git a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx index 5d27d16e2..f619d0fad 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx @@ -69,6 +69,7 @@ Create a project scoped API Key with the required permission in your Mongo Atlas Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret diff --git a/docs/documentation/platform/dynamic-secrets/mongo-db.mdx b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx index f71922473..756195f4e 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-db.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx @@ -72,6 +72,7 @@ Create a user with the required permission in your MongoDB instance. This user w Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-mongodb.png) diff --git a/docs/documentation/platform/dynamic-secrets/mssql.mdx b/docs/documentation/platform/dynamic-secrets/mssql.mdx index 0a73bf129..24d48ee4c 100644 --- a/docs/documentation/platform/dynamic-secrets/mssql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mssql.mdx @@ -78,6 +78,7 @@ Create a user with the required permission in your SQL instance. This user will Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). diff --git a/docs/documentation/platform/dynamic-secrets/mysql.mdx b/docs/documentation/platform/dynamic-secrets/mysql.mdx index 6f708ebba..4003d1eb5 100644 --- a/docs/documentation/platform/dynamic-secrets/mysql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mysql.mdx @@ -75,6 +75,7 @@ Create a user with the required permission in your SQL instance. This user will Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). diff --git a/docs/documentation/platform/dynamic-secrets/oracle.mdx b/docs/documentation/platform/dynamic-secrets/oracle.mdx index 02b379b98..b9527d700 100644 --- a/docs/documentation/platform/dynamic-secrets/oracle.mdx +++ b/docs/documentation/platform/dynamic-secrets/oracle.mdx @@ -77,6 +77,7 @@ Create a user with the required permission in your SQL instance. This user will Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). diff --git a/docs/documentation/platform/dynamic-secrets/postgresql.mdx b/docs/documentation/platform/dynamic-secrets/postgresql.mdx index f13c9c762..dbb73b51a 100644 --- a/docs/documentation/platform/dynamic-secrets/postgresql.mdx +++ b/docs/documentation/platform/dynamic-secrets/postgresql.mdx @@ -78,6 +78,7 @@ Create a user with the required permission in your SQL instance. This user will Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). diff --git a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx index 09c04e61b..ac7ee50a9 100644 --- a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx +++ b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx @@ -71,6 +71,7 @@ Specifies a template for generating usernames. This field allows customization o Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp +- `{{identityName}}`: Name of the identity that is generating the secret diff --git a/docs/documentation/platform/dynamic-secrets/redis.mdx b/docs/documentation/platform/dynamic-secrets/redis.mdx index b3e585204..528cbb56c 100644 --- a/docs/documentation/platform/dynamic-secrets/redis.mdx +++ b/docs/documentation/platform/dynamic-secrets/redis.mdx @@ -63,6 +63,7 @@ Create a user with the required permission in your Redis instance. This user wil Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret If you want to provide specific privileges for the generated dynamic credentials, you can modify the Redis statement to your needs. This is useful if you want to only give access to a specific table(s). diff --git a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx index 2737ab084..d4bbb54ea 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx @@ -70,6 +70,7 @@ The Infisical SAP ASE dynamic secret allows you to generate SAP ASE database cre Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret diff --git a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx index 597d69803..ec224b8b6 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx @@ -70,6 +70,7 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret diff --git a/docs/documentation/platform/dynamic-secrets/snowflake.mdx b/docs/documentation/platform/dynamic-secrets/snowflake.mdx index 86378bbf6..13606a901 100644 --- a/docs/documentation/platform/dynamic-secrets/snowflake.mdx +++ b/docs/documentation/platform/dynamic-secrets/snowflake.mdx @@ -83,6 +83,7 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp + - `{{identityName}}`: Name of the identity that is generating the secret From 5367d1ac2ef8e4b9df82f110dff00c979e2235eb Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Wed, 4 Jun 2025 16:43:17 -0300 Subject: [PATCH 2/5] feat(dynamic-secret): Added new options to username template --- .../src/ee/routes/v1/dynamic-secret-router.ts | 7 +- .../providers/aws-elasticache.ts | 6 +- .../dynamic-secret/providers/aws-iam.ts | 6 +- .../dynamic-secret/providers/cassandra.ts | 6 +- .../providers/elastic-search.ts | 7 +- .../services/dynamic-secret/providers/ldap.ts | 6 +- .../dynamic-secret/providers/mongo-atlas.ts | 7 +- .../dynamic-secret/providers/mongo-db.ts | 7 +- .../dynamic-secret/providers/rabbit-mq.ts | 7 +- .../dynamic-secret/providers/redis.ts | 6 +- .../dynamic-secret/providers/sap-ase.ts | 6 +- .../dynamic-secret/providers/sap-hana.ts | 6 +- .../dynamic-secret/providers/snowflake.ts | 6 +- .../dynamic-secret/providers/sql-database.ts | 11 +- .../dynamic-secret/providers/templateUtils.ts | 98 +++++++++++++ .../src/lib/template/validate-handlebars.ts | 23 +++- .../dynamic-secrets/aws-elasticache.mdx | 27 +++- .../platform/dynamic-secrets/aws-iam.mdx | 29 +++- .../platform/dynamic-secrets/cassandra.mdx | 25 +++- .../dynamic-secrets/elastic-search.mdx | 29 +++- .../platform/dynamic-secrets/ldap.mdx | 21 ++- .../platform/dynamic-secrets/mongo-atlas.mdx | 29 +++- .../platform/dynamic-secrets/mongo-db.mdx | 19 ++- .../platform/dynamic-secrets/mssql.mdx | 129 ++++++++++-------- .../platform/dynamic-secrets/mysql.mdx | 30 ++-- .../platform/dynamic-secrets/oracle.mdx | 30 ++-- .../platform/dynamic-secrets/postgresql.mdx | 27 +++- .../platform/dynamic-secrets/rabbit-mq.mdx | 27 +++- .../platform/dynamic-secrets/redis.mdx | 27 +++- .../platform/dynamic-secrets/sap-ase.mdx | 29 +++- .../platform/dynamic-secrets/sap-hana.mdx | 27 +++- .../platform/dynamic-secrets/snowflake.mdx | 27 +++- 32 files changed, 551 insertions(+), 196 deletions(-) create mode 100644 backend/src/ee/services/dynamic-secret/providers/templateUtils.ts diff --git a/backend/src/ee/routes/v1/dynamic-secret-router.ts b/backend/src/ee/routes/v1/dynamic-secret-router.ts index 2cff70843..b916bab67 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-router.ts @@ -23,7 +23,10 @@ const validateUsernameTemplateCharacters = characterValidator([ CharacterType.CloseBrace, CharacterType.CloseBracket, CharacterType.OpenBracket, - CharacterType.Fullstop + CharacterType.Fullstop, + CharacterType.SingleQuote, + CharacterType.Spaces, + CharacterType.Pipe ]); const userTemplateSchema = z @@ -33,7 +36,7 @@ const userTemplateSchema = z .refine((el) => validateUsernameTemplateCharacters(el)) .refine((el) => isValidHandleBarTemplate(el, { - allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identityName"].includes(val) + allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identity.name"].includes(val) }) ); 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 e21d26a9e..701efc2bd 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts @@ -16,6 +16,7 @@ import { BadRequestError } from "@app/lib/errors"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { DynamicSecretAwsElastiCacheSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const CreateElastiCacheUserSchema = z.object({ UserId: z.string().trim().min(1), @@ -136,10 +137,9 @@ const generateUsername = (usernameTemplate?: string | null, identityName?: strin const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; const randomUsername = `inf-${customAlphabet(charset, 32)()}`; if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 6eabd31aa..57c12485e 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -16,21 +16,21 @@ import { PutUserPolicyCommand, RemoveUserFromGroupCommand } from "@aws-sdk/client-iam"; -import handlebars from "handlebars"; import { z } from "zod"; import { BadRequestError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; diff --git a/backend/src/ee/services/dynamic-secret/providers/cassandra.ts b/backend/src/ee/services/dynamic-secret/providers/cassandra.ts index c0317df4b..cc3f1fa6a 100644 --- a/backend/src/ee/services/dynamic-secret/providers/cassandra.ts +++ b/backend/src/ee/services/dynamic-secret/providers/cassandra.ts @@ -8,6 +8,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretCassandraSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = (size = 48) => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; @@ -17,10 +18,9 @@ const generatePassword = (size = 48) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 c4a9afad3..b8f1ae588 100644 --- a/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts +++ b/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts @@ -1,5 +1,4 @@ import { Client as ElasticSearchClient } from "@elastic/elasticsearch"; -import handlebars from "handlebars"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -7,6 +6,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = () => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; @@ -16,10 +16,9 @@ const generatePassword = () => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; diff --git a/backend/src/ee/services/dynamic-secret/providers/ldap.ts b/backend/src/ee/services/dynamic-secret/providers/ldap.ts index 06196a3b0..447ce4a94 100644 --- a/backend/src/ee/services/dynamic-secret/providers/ldap.ts +++ b/backend/src/ee/services/dynamic-secret/providers/ldap.ts @@ -9,6 +9,7 @@ import { BadRequestError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = () => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#"; @@ -25,10 +26,9 @@ const encodePassword = (password?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 5ac479393..0baee26a8 100644 --- a/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts +++ b/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts @@ -1,5 +1,4 @@ import axios, { AxiosError } from "axios"; -import handlebars from "handlebars"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -7,6 +6,7 @@ import { createDigestAuthRequestInterceptor } from "@app/lib/axios/digest-auth"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = (size = 48) => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; @@ -16,10 +16,9 @@ const generatePassword = (size = 48) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 0d1a4fd27..4a25e8ce8 100644 --- a/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts +++ b/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts @@ -1,4 +1,3 @@ -import handlebars from "handlebars"; import { MongoClient } from "mongodb"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -7,6 +6,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = (size = 48) => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; @@ -16,10 +16,9 @@ const generatePassword = (size = 48) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 48c37179e..91156fa3e 100644 --- a/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts +++ b/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts @@ -1,5 +1,4 @@ import axios, { Axios } from "axios"; -import handlebars from "handlebars"; import https from "https"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -9,6 +8,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = () => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; @@ -18,10 +18,9 @@ const generatePassword = () => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; diff --git a/backend/src/ee/services/dynamic-secret/providers/redis.ts b/backend/src/ee/services/dynamic-secret/providers/redis.ts index 6b0e71913..858489109 100644 --- a/backend/src/ee/services/dynamic-secret/providers/redis.ts +++ b/backend/src/ee/services/dynamic-secret/providers/redis.ts @@ -9,6 +9,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = () => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; @@ -18,10 +19,9 @@ const generatePassword = () => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 18dcbb90d..f8b89f8b9 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts @@ -9,6 +9,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretSapAseSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = (size = 48) => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -18,10 +19,9 @@ const generatePassword = (size = 48) => { const generateUsername = (usernameTemplate?: string | null, identityName?: 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 handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 f5ccb222b..1c7357fac 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts @@ -15,6 +15,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretSapHanaSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generatePassword = (size = 48) => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -24,10 +25,9 @@ const generatePassword = (size = 48) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; diff --git a/backend/src/ee/services/dynamic-secret/providers/snowflake.ts b/backend/src/ee/services/dynamic-secret/providers/snowflake.ts index da8670aff..110679ed9 100644 --- a/backend/src/ee/services/dynamic-secret/providers/snowflake.ts +++ b/backend/src/ee/services/dynamic-secret/providers/snowflake.ts @@ -8,6 +8,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { DynamicSecretSnowflakeSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; // destroy client requires callback... const noop = () => {}; @@ -20,10 +21,9 @@ const generatePassword = (size = 48) => { const generateUsername = (usernameTemplate?: string | null, identityName?: 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 handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), identityName }); }; 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 d20b1dd06..13fc81b06 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts @@ -10,6 +10,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars import { TGatewayServiceFactory } from "../../gateway/gateway-service"; import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { DynamicSecretSqlDBSchema, PasswordRequirements, SqlProviders, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; @@ -113,11 +114,13 @@ const generateUsername = (provider: SqlProviders, usernameTemplate?: string | nu randomUsername = alphaNumericNanoId(32); } if (!usernameTemplate) return randomUsername; - - return handlebars.compile(usernameTemplate)({ + return compileUsernameTemplate({ + usernameTemplate, randomUsername, - unixTimestamp: Math.floor(Date.now() / 100), - identityName + identityName, + options: { + toUpperCase: provider === SqlProviders.Oracle + } }); }; diff --git a/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts b/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts new file mode 100644 index 000000000..76d923ad1 --- /dev/null +++ b/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts @@ -0,0 +1,98 @@ +/* eslint-disable func-names */ +import handlebars from "handlebars"; +import RE2 from "re2"; + +import { alphaNumericNanoId } from "@app/lib/nanoid"; + +export const compileUsernameTemplate = ({ + usernameTemplate, + randomUsername, + identityName, + unixTimestamp, + options +}: { + usernameTemplate: string; + randomUsername: string; + identityName?: string; + unixTimestamp?: number; + options?: { + toUpperCase?: boolean; + }; +}): string => { + // Pre-process template to replace {{random-N}} patterns before compiling + 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); + + if (length > 0 && length <= 100) { + const randomValue = alphaNumericNanoId(length); + processedTemplate = processedTemplate.replace(fullMatch, randomValue); + } + } + + // 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 truncate helper + handlebars.registerHelper("truncate", function (text: string, length: number) { + // Convert to string if it's not already + const textStr = String(text || ""); + if (!textStr) { + return textStr; + } + + if (typeof length !== "number" || length <= 0) return textStr; + return textStr.substring(0, length); + }); + + // Compile template with context + const context = { + randomUsername, + unixTimestamp: unixTimestamp || Math.floor(Date.now() / 100), + identity: { + name: identityName + } + }; + + const result = handlebars.compile(processedTemplate)(context); + + if (options?.toUpperCase) { + return result.toUpperCase(); + } + + return result; +}; diff --git a/backend/src/lib/template/validate-handlebars.ts b/backend/src/lib/template/validate-handlebars.ts index 08343e962..e312a1d80 100644 --- a/backend/src/lib/template/validate-handlebars.ts +++ b/backend/src/lib/template/validate-handlebars.ts @@ -1,4 +1,5 @@ import handlebars from "handlebars"; +import RE2 from "re2"; import { BadRequestError } from "../errors"; import { logger } from "../logger"; @@ -7,13 +8,31 @@ 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"]; + if (allowedHelpers.includes(expression)) { + return true; + } + + // Check regular allowed expressions + return dto?.allowedExpressions?.(expression) || false; +}; + export const validateHandlebarTemplate = (templateName: string, template: string, dto: SanitizationArg) => { const parsedAst = handlebars.parse(template); parsedAst.body.forEach((el) => { if (el.type === "ContentStatement") return; if (el.type === "MustacheStatement" && "path" in el) { const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } }; - if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return; + if (path.type === "PathExpression" && isValidExpression(path.original, dto)) return; } logger.error(el, "Template sanitization failed"); throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` }); @@ -26,7 +45,7 @@ export const isValidHandleBarTemplate = (template: string, dto: SanitizationArg) if (el.type === "ContentStatement") return true; if (el.type === "MustacheStatement" && "path" in el) { const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } }; - if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return true; + if (path.type === "PathExpression" && isValidExpression(path.original, dto)) return true; } return false; }); diff --git a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx index c91b1acf7..b7c287d33 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx @@ -95,13 +95,28 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa ![Modify ElastiCache Statements Modal](/images/platform/dynamic-secrets/modify-elasticache-statement.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` If you want to provide specific privileges for the generated dynamic credentials, you can modify the ElastiCache statement to your needs. This is useful if you want to only give access to a specific resource. diff --git a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx index bc563a52e..5c929feb2 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx @@ -105,14 +105,29 @@ Replace **\** with your AWS account id and **\** w The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas - -Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. -Allowed template variables are -- `{{randomUsername}}`: Random username string -- `{{unixTimestamp}}`: Current Unix timestamp -- `{{identityName}}`: Name of the identity that is generating the secret - + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png) diff --git a/docs/documentation/platform/dynamic-secrets/cassandra.mdx b/docs/documentation/platform/dynamic-secrets/cassandra.mdx index 6752faa66..5928e7315 100644 --- a/docs/documentation/platform/dynamic-secrets/cassandra.mdx +++ b/docs/documentation/platform/dynamic-secrets/cassandra.mdx @@ -80,12 +80,27 @@ The above configuration allows user creation and granting permissions. ![Modify CQL Statements Modal](../../../images/platform/dynamic-secrets/modify-cql-statements.png) - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` If you want to provide specific privileges for the generated dynamic credentials, you can modify the CQL statement to your needs. This is useful if you want to only give access to a specific key-space(s). diff --git a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx index eec481397..560e10d4d 100644 --- a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx +++ b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx @@ -87,14 +87,29 @@ The port that your Elasticsearch instance is running on. _(Example: 9200)_ A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png) diff --git a/docs/documentation/platform/dynamic-secrets/ldap.mdx b/docs/documentation/platform/dynamic-secrets/ldap.mdx index bb6fd0e07..11557c6c8 100644 --- a/docs/documentation/platform/dynamic-secrets/ldap.mdx +++ b/docs/documentation/platform/dynamic-secrets/ldap.mdx @@ -123,14 +123,29 @@ The Infisical LDAP dynamic secret allows you to generate user credentials on dem changetype: delete ``` - + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - + - `{{identity.name}}`: Name of the identity that is generating the secret + - `{{random-N}}`: Random string of N characters + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + diff --git a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx index f619d0fad..63ad908a0 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx @@ -63,14 +63,29 @@ Create a project scoped API Key with the required permission in your Mongo Atlas ![Modify Scope Modal](../../../images/platform/dynamic-secrets/advanced-option-atlas.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + List that contains clusters, MongoDB Atlas Data Lakes, and MongoDB Atlas Streams Instances that this database user can access. If omitted, MongoDB Cloud grants the database user access to all the clusters, MongoDB Atlas Data Lakes, and MongoDB Atlas Streams Instances in the project. diff --git a/docs/documentation/platform/dynamic-secrets/mongo-db.mdx b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx index 756195f4e..681564563 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-db.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx @@ -66,13 +66,28 @@ Create a user with the required permission in your MongoDB instance. This user w A CA may be required if your DB requires it for incoming connections. - + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. Allowed template variables are - `{{randomUsername}}`: Random username string - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret + - `{{identity.name}}`: Name of the identity that is generating the secret + - `{{random-N}}`: Random string of N characters + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-mongodb.png) diff --git a/docs/documentation/platform/dynamic-secrets/mssql.mdx b/docs/documentation/platform/dynamic-secrets/mssql.mdx index 24d48ee4c..3347ebca4 100644 --- a/docs/documentation/platform/dynamic-secrets/mssql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mssql.mdx @@ -9,7 +9,6 @@ The Infisical MS SQL dynamic secret allows you to generate Microsoft SQL server Create a user with the required permission in your SQL instance. This user will be used to create new accounts on-demand. - ## Set up Dynamic Secrets with MS SQL @@ -27,105 +26,123 @@ Create a user with the required permission in your SQL instance. This user will Name by which you want the secret to be referenced - - Default time-to-live for a generated secret (it is possible to modify this value after a secret is generated) - + + Default time-to-live for a generated secret (it is possible to modify this value after a secret is generated) + - - Maximum time-to-live for a generated secret - + + Maximum time-to-live for a generated secret + - - List of key/value metadata pairs - + + List of key/value metadata pairs + - - Choose the service you want to generate dynamic secrets for. This must be selected as **MS SQL**. - + + Choose the service you want to generate dynamic secrets for. This must be selected as **MS SQL**. + - - Database host - + + Database host + - - Database port - + + Database port + - - Username that will be used to create dynamic secrets - + + Username that will be used to create dynamic secrets + - - Password that will be used to create dynamic secrets - + + Password that will be used to create dynamic secrets + - - Name of the database for which you want to create dynamic secrets - + + Name of the database for which you want to create dynamic secrets + - - A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions). - + + A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions). + - ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-mssql.png) + ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-mssql.png) ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements-mssql.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - - - If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). - After submitting the form, you will see a dynamic secret created in the dashboard. - - If this step fails, you may have to add the CA certificate. - + + If this step fails, you may have to add the CA certificate. + + + ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) - ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item. Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section. - ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate.png) - ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.png) - When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. + When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. - ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) - - Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. - + + Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. + - Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you. + Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you. + + ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) - ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) ## Audit or Revoke Leases + Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. This will allow you to see the expiration time of the lease or delete the lease before it's set time to live. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) ## Renew Leases + To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** button as illustrated below. ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) - Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret + Lease renewals cannot exceed the maximum TTL set when configuring the dynamic + secret diff --git a/docs/documentation/platform/dynamic-secrets/mysql.mdx b/docs/documentation/platform/dynamic-secrets/mysql.mdx index 4003d1eb5..82f76bb46 100644 --- a/docs/documentation/platform/dynamic-secrets/mysql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mysql.mdx @@ -69,16 +69,28 @@ Create a user with the required permission in your SQL instance. This user will ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statement-mysql.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - - - If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` diff --git a/docs/documentation/platform/dynamic-secrets/oracle.mdx b/docs/documentation/platform/dynamic-secrets/oracle.mdx index b9527d700..0f9c0aff1 100644 --- a/docs/documentation/platform/dynamic-secrets/oracle.mdx +++ b/docs/documentation/platform/dynamic-secrets/oracle.mdx @@ -71,16 +71,28 @@ Create a user with the required permission in your SQL instance. This user will ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statement-oracle.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - - - If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` diff --git a/docs/documentation/platform/dynamic-secrets/postgresql.mdx b/docs/documentation/platform/dynamic-secrets/postgresql.mdx index dbb73b51a..29cf35c6a 100644 --- a/docs/documentation/platform/dynamic-secrets/postgresql.mdx +++ b/docs/documentation/platform/dynamic-secrets/postgresql.mdx @@ -72,13 +72,28 @@ Create a user with the required permission in your SQL instance. This user will ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). diff --git a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx index ac7ee50a9..9cd82f499 100644 --- a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx +++ b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx @@ -66,13 +66,28 @@ The port that the RabbitMQ management plugin is listening on. This is `15672` by -Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. -Allowed template variables are -- `{{randomUsername}}`: Random username string -- `{{unixTimestamp}}`: Current Unix timestamp -- `{{identityName}}`: Name of the identity that is generating the secret - + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. diff --git a/docs/documentation/platform/dynamic-secrets/redis.mdx b/docs/documentation/platform/dynamic-secrets/redis.mdx index 528cbb56c..6b3373c6c 100644 --- a/docs/documentation/platform/dynamic-secrets/redis.mdx +++ b/docs/documentation/platform/dynamic-secrets/redis.mdx @@ -57,13 +57,28 @@ Create a user with the required permission in your Redis instance. This user wil ![Modify Redis Statements Modal](/images/platform/dynamic-secrets/modify-redis-statement.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` If you want to provide specific privileges for the generated dynamic credentials, you can modify the Redis statement to your needs. This is useful if you want to only give access to a specific table(s). diff --git a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx index d4bbb54ea..313533e5d 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx @@ -64,14 +64,29 @@ The Infisical SAP ASE dynamic secret allows you to generate SAP ASE database cre ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. diff --git a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx index ec224b8b6..413e49c78 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx @@ -64,13 +64,28 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png) - - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` diff --git a/docs/documentation/platform/dynamic-secrets/snowflake.mdx b/docs/documentation/platform/dynamic-secrets/snowflake.mdx index 13606a901..e6cad2432 100644 --- a/docs/documentation/platform/dynamic-secrets/snowflake.mdx +++ b/docs/documentation/platform/dynamic-secrets/snowflake.mdx @@ -78,13 +78,28 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede ![Modify SQL Statements Modal](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png) - Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. + Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - Allowed template variables are - - `{{randomUsername}}`: Random username string - - `{{unixTimestamp}}`: Current Unix timestamp - - `{{identityName}}`: Name of the identity that is generating the secret - + Allowed template variables are + - `{{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 + + Allowed template functions are + - `truncate`: Truncates a string to a specified length + - `replace`: Replaces a substring with another value + + Examples: + ``` + {{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX + {{unixTimestamp}} // 17490641580 + {{identity.name}} // testuser + {{random-5}} // x9k2m + {{truncate identity.name 4}} // test + {{replace identity.name 'user' 'replace'}} // testreplace + ``` + If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL From ac5bfbb6c92ac0b7a72665e64704f167db2e8bb4 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Thu, 5 Jun 2025 17:18:56 -0300 Subject: [PATCH 3/5] feat(dynamic-secret): Minor improvements on usernameTemplate --- .../dynamic-secret-lease-service.ts | 12 +-- .../providers/aws-elasticache.ts | 12 ++- .../dynamic-secret/providers/aws-iam.ts | 12 ++- .../dynamic-secret/providers/cassandra.ts | 10 +- .../providers/elastic-search.ts | 10 +- .../services/dynamic-secret/providers/ldap.ts | 10 +- .../dynamic-secret/providers/models.ts | 4 +- .../dynamic-secret/providers/mongo-atlas.ts | 10 +- .../dynamic-secret/providers/mongo-db.ts | 10 +- .../dynamic-secret/providers/rabbit-mq.ts | 10 +- .../dynamic-secret/providers/redis.ts | 10 +- .../dynamic-secret/providers/sap-ase.ts | 10 +- .../dynamic-secret/providers/sap-hana.ts | 10 +- .../dynamic-secret/providers/snowflake.ts | 10 +- .../dynamic-secret/providers/sql-database.ts | 10 +- .../dynamic-secret/providers/templateUtils.ts | 91 +++++++++---------- .../src/lib/template/validate-handlebars.ts | 10 +- .../dynamic-secrets/aws-elasticache.mdx | 2 +- .../platform/dynamic-secrets/aws-iam.mdx | 2 +- .../platform/dynamic-secrets/cassandra.mdx | 2 +- .../dynamic-secrets/elastic-search.mdx | 2 +- .../platform/dynamic-secrets/ldap.mdx | 2 +- .../platform/dynamic-secrets/mongo-atlas.mdx | 2 +- .../platform/dynamic-secrets/mongo-db.mdx | 2 +- .../platform/dynamic-secrets/mssql.mdx | 2 +- .../platform/dynamic-secrets/mysql.mdx | 2 +- .../platform/dynamic-secrets/oracle.mdx | 2 +- .../platform/dynamic-secrets/postgresql.mdx | 2 +- .../platform/dynamic-secrets/rabbit-mq.mdx | 2 +- .../platform/dynamic-secrets/redis.mdx | 2 +- .../platform/dynamic-secrets/sap-ase.mdx | 2 +- .../platform/dynamic-secrets/sap-hana.mdx | 2 +- .../platform/dynamic-secrets/snowflake.mdx | 2 +- 33 files changed, 138 insertions(+), 145 deletions(-) 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 367e9265a..5aa50f334 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 701efc2bd..89371f1bd 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 57c12485e..205612ac5 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 cc3f1fa6a..b939dcad6 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 b8f1ae588..32d21ee76 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 447ce4a94..1de8aa1e6 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 f3b2a7ab0..5681b8f77 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 0baee26a8..6da8b4b4e 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 4a25e8ce8..331a355a7 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 91156fa3e..76081c86c 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 858489109..989ed96dc 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 f8b89f8b9..9c13d3efc 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 1c7357fac..5c8a75555 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 110679ed9..9e97ecd30 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 13fc81b06..d378ee916 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 76d923ad1..ecff317af 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 e312a1d80..4aa0d1f63 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 b7c287d33..6c70612c8 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 5c929feb2..fb3dd755d 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 5928e7315..56b7ec336 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 560e10d4d..06d1102e2 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 11557c6c8..ddaaf9101 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 63ad908a0..f167a0641 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 681564563..7c4ebb568 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 3347ebca4..6b6cef982 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 82f76bb46..33d11a4fc 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 0f9c0aff1..3c83f3359 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 29cf35c6a..974066e14 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 9cd82f499..be41901b7 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 6b3373c6c..8e28c3cb2 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 313533e5d..6da572f35 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 413e49c78..8ccd842f2 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 e6cad2432..f0bbaa08c 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 From 5406871c302c871235925a0f09c46567ac98afeb Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 6 Jun 2025 14:34:32 -0300 Subject: [PATCH 4/5] feat(dynamic-secret): Minor improvements on usernameTemplate --- .../dynamic-secret/providers/templateUtils.ts | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts b/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts index ecff317af..70a083dbf 100644 --- a/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts +++ b/backend/src/ee/services/dynamic-secret/providers/templateUtils.ts @@ -23,26 +23,13 @@ export const compileUsernameTemplate = ({ // Create isolated handlebars instance const hbs = handlebars.create(); - // Pre-process template to replace {{random N}} patterns using RE2 - let processedTemplate = usernameTemplate; - - try { - const randomPattern = new RE2("\\{\\{random (\\d+)\\}\\}", "g"); - - // 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 random helper on local instance + hbs.registerHelper("random", function (length: number) { + if (typeof length !== "number" || length <= 0 || length > 100) { + return ""; + } + return alphaNumericNanoId(length); + }); // Register replace helper on local instance hbs.registerHelper("replace", function (text: string, searchValue: string, replaceValue: string) { @@ -55,7 +42,7 @@ export const compileUsernameTemplate = ({ try { const re2Pattern = new RE2(searchValue, "g"); // Replace all occurrences - return textStr.replace(re2Pattern, replaceValue); + return re2Pattern.replace(textStr, replaceValue); } catch (error) { logger.error(error, "RE2 pattern failed, using original template"); return textStr; @@ -83,7 +70,7 @@ export const compileUsernameTemplate = ({ } }; - const result = hbs.compile(processedTemplate)(context); + const result = hbs.compile(usernameTemplate)(context); if (options?.toUpperCase) { return result.toUpperCase(); From c4dea2d51f2451221b2d49391f04f892ab633d7e Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 6 Jun 2025 17:34:29 -0300 Subject: [PATCH 5/5] Type fix --- backend/src/ee/services/dynamic-secret/providers/aws-iam.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 f00051dc4..f7383d4ac 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -18,16 +18,14 @@ import { } from "@aws-sdk/client-iam"; import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; import { randomUUID } from "crypto"; -import handlebars from "handlebars"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; -import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; -import { compileUsernameTemplate } from "./templateUtils"; import { AwsIamAuthType, DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; +import { compileUsernameTemplate } from "./templateUtils"; const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => { const randomUsername = alphaNumericNanoId(32);