diff --git a/backend/src/db/migrations/20250527140639_dynamic-secret-username-template.ts b/backend/src/db/migrations/20250527140639_dynamic-secret-username-template.ts new file mode 100644 index 000000000..2ff493c6f --- /dev/null +++ b/backend/src/db/migrations/20250527140639_dynamic-secret-username-template.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "usernameTemplate"); + if (!hasColumn) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + t.string("usernameTemplate").nullable(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "usernameTemplate"); + if (hasColumn) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + t.dropColumn("usernameTemplate"); + }); + } +} diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index 350a32b7a..637d0c632 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -28,7 +28,8 @@ export const DynamicSecretsSchema = z.object({ updatedAt: z.date(), encryptedInput: zodBuffer, projectGatewayId: z.string().uuid().nullable().optional(), - gatewayId: z.string().uuid().nullable().optional() + gatewayId: z.string().uuid().nullable().optional(), + usernameTemplate: z.string().nullable().optional() }); export type TDynamicSecrets = z.infer; diff --git a/backend/src/ee/routes/v1/dynamic-secret-router.ts b/backend/src/ee/routes/v1/dynamic-secret-router.ts index 6e70effe4..bf5cce7d5 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-router.ts @@ -6,6 +6,8 @@ import { ApiDocsTags, DYNAMIC_SECRETS } from "@app/lib/api-docs"; import { daysToMillisecond } from "@app/lib/dates"; import { removeTrailingSlash } from "@app/lib/fn"; import { ms } from "@app/lib/ms"; +import { isValidHandleBarTemplate } from "@app/lib/template/validate-handlebars"; +import { CharacterType, characterValidator } from "@app/lib/validator/validate-string"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -13,6 +15,28 @@ import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchema import { AuthMode } from "@app/services/auth/auth-type"; import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema"; +const validateUsernameTemplateCharacters = characterValidator([ + CharacterType.AlphaNumeric, + CharacterType.Underscore, + CharacterType.Hyphen, + CharacterType.OpenBrace, + CharacterType.CloseBrace, + CharacterType.CloseBracket, + CharacterType.OpenBracket, + CharacterType.Fullstop +]); + +const userTemplateSchema = z + .string() + .trim() + .max(255) + .refine((el) => validateUsernameTemplateCharacters(el)) + .refine((el) => + isValidHandleBarTemplate(el, { + allowedExpressions: (val) => ["randomUsername", "unixTimestamp"].includes(val) + }) + ); + export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", @@ -52,7 +76,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash), environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1), name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name), - metadata: ResourceMetadataSchema.optional() + metadata: ResourceMetadataSchema.optional(), + usernameTemplate: userTemplateSchema.optional() }), response: { 200: z.object({ @@ -73,39 +98,6 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => } }); - server.route({ - method: "POST", - url: "/entra-id/users", - config: { - rateLimit: readLimit - }, - schema: { - body: z.object({ - tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"), - applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"), - clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration") - }), - response: { - 200: z - .object({ - name: z.string().min(1).describe("The name of the user"), - id: z.string().min(1).describe("The ID of the user"), - email: z.string().min(1).describe("The email of the user") - }) - .array() - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({ - tenantId: req.body.tenantId, - applicationId: req.body.applicationId, - clientSecret: req.body.clientSecret - }); - return data; - } - }); - server.route({ method: "PATCH", url: "/:name", @@ -150,7 +142,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => }) .nullable(), newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(), - metadata: ResourceMetadataSchema.optional() + metadata: ResourceMetadataSchema.optional(), + usernameTemplate: userTemplateSchema.nullable().optional() }) }), response: { @@ -328,4 +321,37 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => return { leases }; } }); + + server.route({ + method: "POST", + url: "/entra-id/users", + config: { + rateLimit: readLimit + }, + schema: { + body: z.object({ + tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"), + applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"), + clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration") + }), + response: { + 200: z + .object({ + name: z.string().min(1).describe("The name of the user"), + id: z.string().min(1).describe("The ID of the user"), + email: z.string().min(1).describe("The email of the user") + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({ + tenantId: req.body.tenantId, + applicationId: req.body.applicationId, + clientSecret: req.body.clientSecret + }); + return data; + } + }); }; 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 4adf8b7e2..f3f3f3acd 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 @@ -132,7 +132,11 @@ export const dynamicSecretLeaseServiceFactory = ({ let result; try { - result = await selectedProvider.create(decryptedStoredInput, expireAt.getTime()); + result = await selectedProvider.create({ + inputs: decryptedStoredInput, + expireAt: expireAt.getTime(), + usernameTemplate: dynamicSecretCfg.usernameTemplate + }); } catch (error: unknown) { if (error && typeof error === "object" && error !== null && "sqlMessage" in error) { throw new BadRequestError({ message: error.sqlMessage as string }); diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index c39f07b5c..16ac10716 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -78,7 +78,8 @@ export const dynamicSecretServiceFactory = ({ actorOrgId, defaultTTL, actorAuthMethod, - metadata + metadata, + usernameTemplate }: TCreateDynamicSecretDTO) => { const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); @@ -163,7 +164,8 @@ export const dynamicSecretServiceFactory = ({ defaultTTL, folderId: folder.id, name, - gatewayId: selectedGatewayId + gatewayId: selectedGatewayId, + usernameTemplate }, tx ); @@ -199,7 +201,8 @@ export const dynamicSecretServiceFactory = ({ newName, actorOrgId, actorAuthMethod, - metadata + metadata, + usernameTemplate }: TUpdateDynamicSecretDTO) => { const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); @@ -311,7 +314,8 @@ export const dynamicSecretServiceFactory = ({ defaultTTL, name: newName ?? name, status: null, - gatewayId: selectedGatewayId + gatewayId: selectedGatewayId, + usernameTemplate }, tx ); diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts index 58fdc2143..6720cf2c8 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts @@ -22,6 +22,7 @@ export type TCreateDynamicSecretDTO = { name: string; projectSlug: string; metadata?: ResourceMetadataDTO; + usernameTemplate?: string | null; } & Omit; export type TUpdateDynamicSecretDTO = { @@ -34,6 +35,7 @@ export type TUpdateDynamicSecretDTO = { inputs?: TProvider["inputs"]; projectSlug: string; metadata?: ResourceMetadataDTO; + usernameTemplate?: string | null; } & Omit; export type TDeleteDynamicSecretDTO = { 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 f2907f7dc..56fa110d1 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-elasticache.ts @@ -132,9 +132,15 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = () => { +const generateUsername = (usernameTemplate?: string | null) => { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; - return `inf-${customAlphabet(charset, 32)()}`; // Username must start with an ascii letter, so we prepend the username with "inf-" + const randomUsername = `inf-${customAlphabet(charset, 32)()}`; + if (!usernameTemplate) return randomUsername; + + return handlebars.compile(usernameTemplate)({ + randomUsername, + unixTimestamp: Math.floor(Date.now() / 100) + }); }; export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => { @@ -168,13 +174,14 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => { return true; }; - const create = async (inputs: unknown, expireAt: number) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, expireAt, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); if (!(await validateConnection(providerInputs))) { throw new BadRequestError({ message: "Failed to establish connection" }); } - const leaseUsername = generateUsername(); + const leaseUsername = generateUsername(usernameTemplate); 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 64ea6a02e..9d8e10f60 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -16,6 +16,7 @@ import { PutUserPolicyCommand, RemoveUserFromGroupCommand } from "@aws-sdk/client-iam"; +import handlebars from "handlebars"; import { z } from "zod"; import { BadRequestError } from "@app/lib/errors"; @@ -23,8 +24,14 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + const randomUsername = alphaNumericNanoId(32); + if (!usernameTemplate) return randomUsername; + + return handlebars.compile(usernameTemplate)({ + randomUsername, + unixTimestamp: Math.floor(Date.now() / 100) + }); }; export const AwsIamProvider = (): TDynamicProviderFns => { @@ -53,11 +60,13 @@ export const AwsIamProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (inputs: unknown) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, usernameTemplate } = data; + const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs; const createUserRes = await client.send( new CreateUserCommand({ diff --git a/backend/src/ee/services/dynamic-secret/providers/azure-entra-id.ts b/backend/src/ee/services/dynamic-secret/providers/azure-entra-id.ts index 17f644601..4b2232bc8 100644 --- a/backend/src/ee/services/dynamic-secret/providers/azure-entra-id.ts +++ b/backend/src/ee/services/dynamic-secret/providers/azure-entra-id.ts @@ -55,7 +55,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & { return data.success; }; - const create = async (inputs: unknown) => { + const create = async ({ inputs }: { inputs: unknown }) => { const providerInputs = await validateProviderInputs(inputs); const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret); if (!data.success) { @@ -88,7 +88,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & { const revoke = async (inputs: unknown, entityId: string) => { // Creates a new password - await create(inputs); + await create({ inputs }); return { entityId }; }; diff --git a/backend/src/ee/services/dynamic-secret/providers/cassandra.ts b/backend/src/ee/services/dynamic-secret/providers/cassandra.ts index 0b6d50146..fce23b56f 100644 --- a/backend/src/ee/services/dynamic-secret/providers/cassandra.ts +++ b/backend/src/ee/services/dynamic-secret/providers/cassandra.ts @@ -14,8 +14,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; export const CassandraProvider = (): TDynamicProviderFns => { @@ -69,11 +75,12 @@ export const CassandraProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (inputs: unknown, expireAt: number) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, expireAt, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 6c1affa39..066822827 100644 --- a/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts +++ b/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts @@ -1,4 +1,5 @@ import { Client as ElasticSearchClient } from "@elastic/elasticsearch"; +import handlebars from "handlebars"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -12,8 +13,14 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; export const ElasticSearchProvider = (): TDynamicProviderFns => { @@ -64,11 +71,12 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => { return infoResponse; }; - const create = async (inputs: unknown) => { + const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { + const { inputs, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 cc68304e0..d0e3fbe66 100644 --- a/backend/src/ee/services/dynamic-secret/providers/ldap.ts +++ b/backend/src/ee/services/dynamic-secret/providers/ldap.ts @@ -22,8 +22,14 @@ const encodePassword = (password?: string) => { return base64Password; }; -const generateUsername = () => { - return alphaNumericNanoId(20); +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; const generateLDIF = ({ @@ -190,7 +196,8 @@ export const LdapProvider = (): TDynamicProviderFns => { return dnArray; }; - const create = async (inputs: unknown) => { + const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { + const { inputs, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); @@ -217,7 +224,7 @@ export const LdapProvider = (): TDynamicProviderFns => { }); } } else { - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 28baac721..ed59f4d67 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -360,7 +360,11 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ ]); export type TDynamicProviderFns = { - create: (inputs: unknown, expireAt: number) => Promise<{ entityId: string; data: unknown }>; + create: (arg: { + inputs: unknown; + expireAt: number; + usernameTemplate?: string | null; + }) => Promise<{ entityId: string; data: unknown }>; validateConnection: (inputs: unknown) => Promise; validateProviderInputs: (inputs: object) => Promise; revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>; 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 6cb414d10..8f8bf9430 100644 --- a/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts +++ b/backend/src/ee/services/dynamic-secret/providers/mongo-atlas.ts @@ -1,4 +1,5 @@ import axios, { AxiosError } from "axios"; +import handlebars from "handlebars"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -12,8 +13,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + const randomUsername = alphaNumericNanoId(32); + if (!usernameTemplate) return randomUsername; + + return handlebars.compile(usernameTemplate)({ + randomUsername, + unixTimestamp: Math.floor(Date.now() / 100) + }); }; export const MongoAtlasProvider = (): TDynamicProviderFns => { @@ -57,11 +64,12 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (inputs: unknown, expireAt: number) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, expireAt, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 bee29bfc4..0a15209e0 100644 --- a/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts +++ b/backend/src/ee/services/dynamic-secret/providers/mongo-db.ts @@ -1,3 +1,4 @@ +import handlebars from "handlebars"; import { MongoClient } from "mongodb"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -12,8 +13,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + const randomUsername = alphaNumericNanoId(32); + if (!usernameTemplate) return randomUsername; + + return handlebars.compile(usernameTemplate)({ + randomUsername, + unixTimestamp: Math.floor(Date.now() / 100) + }); }; export const MongoDBProvider = (): TDynamicProviderFns => { @@ -53,11 +60,12 @@ export const MongoDBProvider = (): TDynamicProviderFns => { return isConnected; }; - const create = async (inputs: unknown) => { + const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { + const { inputs, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 f6c73ba54..e7d90d272 100644 --- a/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts +++ b/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts @@ -1,4 +1,5 @@ import axios, { Axios } from "axios"; +import handlebars from "handlebars"; import https from "https"; import { customAlphabet } from "nanoid"; import { z } from "zod"; @@ -14,8 +15,14 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; type TCreateRabbitMQUser = { @@ -110,11 +117,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => { return infoResponse; }; - const create = async (inputs: unknown) => { + const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { + const { inputs, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 f180dd607..855af2e29 100644 --- a/backend/src/ee/services/dynamic-secret/providers/redis.ts +++ b/backend/src/ee/services/dynamic-secret/providers/redis.ts @@ -15,8 +15,14 @@ const generatePassword = () => { return customAlphabet(charset, 64)(); }; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => { @@ -115,11 +121,12 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => { return pingResponse; }; - const create = async (inputs: unknown, expireAt: number) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, expireAt, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const connection = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 c832e9867..af2431058 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-ase.ts @@ -15,8 +15,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = () => { - return alphaNumericNanoId(25); +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; enum SapCommands { @@ -81,11 +87,12 @@ export const SapAseProvider = (): TDynamicProviderFns => { return true; }; - const create = async (inputs: unknown) => { + const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { + const { inputs, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); - const username = `inf_${generateUsername()}`; - const password = `${generatePassword()}`; + const username = generateUsername(usernameTemplate); + const password = generatePassword(); const client = await $getClient(providerInputs); const masterClient = await $getClient(providerInputs, true); 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 1ad24473c..654e2d144 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sap-hana.ts @@ -21,8 +21,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = () => { - return alphaNumericNanoId(32); +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; export const SapHanaProvider = (): TDynamicProviderFns => { @@ -91,10 +97,11 @@ export const SapHanaProvider = (): TDynamicProviderFns => { return testResult; }; - const create = async (inputs: unknown, expireAt: number) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, expireAt, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 bea7eca89..571d488c9 100644 --- a/backend/src/ee/services/dynamic-secret/providers/snowflake.ts +++ b/backend/src/ee/services/dynamic-secret/providers/snowflake.ts @@ -17,8 +17,14 @@ const generatePassword = (size = 48) => { return customAlphabet(charset, 48)(size); }; -const generateUsername = () => { - return `infisical_${alphaNumericNanoId(32)}`; // username must start with alpha character, hence prefix +const generateUsername = (usernameTemplate?: string | null) => { + 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) + }); }; const getDaysToExpiry = (expiryDate: Date) => { @@ -82,12 +88,13 @@ export const SnowflakeProvider = (): TDynamicProviderFns => { return isValidConnection; }; - const create = async (inputs: unknown, expireAt: number) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, expireAt, usernameTemplate } = data; const providerInputs = await validateProviderInputs(inputs); const client = await $getClient(providerInputs); - const username = generateUsername(); + const username = generateUsername(usernameTemplate); 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 3ae85ed7b..ce16a1237 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts @@ -104,11 +104,21 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire } }; -const generateUsername = (provider: SqlProviders) => { - // For oracle, the client assumes everything is upper case when not using quotes around the password - if (provider === SqlProviders.Oracle) return alphaNumericNanoId(32).toUpperCase(); +const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null) => { + let randomUsername = ""; - return alphaNumericNanoId(32); + // For oracle, the client assumes everything is upper case when not using quotes around the password + if (provider === SqlProviders.Oracle) { + randomUsername = alphaNumericNanoId(32).toUpperCase(); + } else { + randomUsername = alphaNumericNanoId(32); + } + if (!usernameTemplate) return randomUsername; + + return handlebars.compile(usernameTemplate)({ + randomUsername, + unixTimestamp: Math.floor(Date.now() / 100) + }); }; type TSqlDatabaseProviderDTO = { @@ -210,9 +220,12 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) return isConnected; }; - const create = async (inputs: unknown, expireAt: number) => { + const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { + const { inputs, expireAt, usernameTemplate } = data; + const providerInputs = await validateProviderInputs(inputs); - const username = generateUsername(providerInputs.client); + const username = generateUsername(providerInputs.client, usernameTemplate); + const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements); const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => { const db = await $getClient({ ...providerInputs, port, host }); diff --git a/backend/src/lib/template/validate-handlebars.ts b/backend/src/lib/template/validate-handlebars.ts index a83c9efc2..08343e962 100644 --- a/backend/src/lib/template/validate-handlebars.ts +++ b/backend/src/lib/template/validate-handlebars.ts @@ -19,3 +19,15 @@ export const validateHandlebarTemplate = (templateName: string, template: string throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` }); }); }; + +export const isValidHandleBarTemplate = (template: string, dto: SanitizationArg) => { + const parsedAst = handlebars.parse(template); + return parsedAst.body.every((el) => { + 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; + } + return false; + }); +}; diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 209044434..a26293ac8 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -235,11 +235,9 @@ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({ inputIV: true, inputTag: true, algorithm: true -}).merge( - z.object({ - metadata: ResourceMetadataSchema.optional() - }) -); +}).extend({ + metadata: ResourceMetadataSchema.optional() +}); export const SanitizedAuditLogStreamSchema = z.object({ id: z.string(), diff --git a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx index 2cf4edc0e..66c4c706a 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx @@ -60,7 +60,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) - + ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-aws-elasti-cache.png) @@ -94,21 +94,29 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa - 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 table(s). + ![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. - ![Modify ElastiCache Statements Modal](/images/platform/dynamic-secrets/modify-elasticache-statement.png) + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + 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. + - After submitting the form, you will see a dynamic secret created in the dashboard. + 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. - 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. + 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-redis.png) @@ -123,14 +131,14 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa - Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. + Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. ![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. +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 a lease before it's set time to live. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) @@ -141,4 +149,4 @@ To extend the life of the generated dynamic secret leases past its initial time Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret - \ No newline at end of file + diff --git a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx index 730e2b287..56a10419c 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx @@ -40,7 +40,7 @@ Infisical needs an initial AWS IAM user with the required permissions to create } ``` -To minimize managing user access you can attach a resource in format +To minimize managing user access you can attach a resource in format > arn:aws:iam::\:user/\ @@ -94,28 +94,36 @@ Replace **\** with your AWS account id and **\** w - The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas + The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas - + - The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas + The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas - The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas + 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. + +Allowed template variables are +- `{{randomUsername}}`: Random username string +- `{{unixTimestamp}}`: Current Unix timestamp ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png) - After submitting the form, you will see a dynamic secret created in the dashboard. + After submitting the form, you will see a dynamic secret created in the dashboard. ![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. + 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) @@ -130,14 +138,14 @@ Replace **\** with your AWS account id and **\** w - 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-aws-iam.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. +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 lease details and delete the lease ahead of its expiration time. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) diff --git a/docs/documentation/platform/dynamic-secrets/cassandra.mdx b/docs/documentation/platform/dynamic-secrets/cassandra.mdx index e7ec4f69d..628432bea 100644 --- a/docs/documentation/platform/dynamic-secrets/cassandra.mdx +++ b/docs/documentation/platform/dynamic-secrets/cassandra.mdx @@ -7,7 +7,7 @@ The Infisical Cassandra dynamic secret allows you to generate Cassandra database ## Prerequisite -Infisical requires a Cassandra user in your instance with the necessary permissions. This user will facilitate the creation of new accounts as needed. +Infisical requires a Cassandra user in your instance with the necessary permissions. This user will facilitate the creation of new accounts as needed. Ensure the user possesses privileges for creating, dropping, and granting permissions to roles for it to be able to create dynamic secrets. @@ -19,7 +19,7 @@ authorizer: CassandraAuthorizer ``` -The above configuration allows user creation and granting permissions. +The above configuration allows user creation and granting permissions. ## Set up Dynamic Secrets with Cassandra @@ -69,31 +69,39 @@ The above configuration allows user creation and granting permissions. Keyspace name where you want to create dynamic secrets. This ensures that the user is limited to that keyspace. - + - A CA may be required if your cassandra requires it for incoming connections. + A CA may be required if your cassandra requires it for incoming connections. ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-cassandra.png) - 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). + ![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. - ![Modify CQL Statements Modal](../../../images/platform/dynamic-secrets/modify-cql-statements.png) + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + 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). + - After submitting the form, you will see a dynamic secret created in the dashboard. + 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 certficate. + If this step fails, you may have to add the CA certificate. ![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. + 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) @@ -108,14 +116,14 @@ The above configuration allows user creation and granting permissions. - 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) ## 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. +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 lease details and delete the lease ahead of its expiration time. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) diff --git a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx index 0e1bc5104..6c5028bb2 100644 --- a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx +++ b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx @@ -7,13 +7,14 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch ## Prerequisites - - 1. Create a role with at least `manage_security` and `monitor` permissions. 2. Assign the newly created role to your API key or user that you'll use later in the dynamic secret configuration. - For testing purposes, you can also use a highly privileged role like `superuser`, that will have full control over the cluster. This is not recommended in production environments following the principle of least privilege. + For testing purposes, you can also use a highly privileged role like + `superuser`, that will have full control over the cluster. This is not + recommended in production environments following the principle of least + privilege. ## Set up Dynamic Secrets with Elasticsearch @@ -33,95 +34,115 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch 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. + - + Your Elasticsearch host. This is the endpoint that your instance runs on. _(Example: https://your-cluster-ip)_ - + - - The port that your Elasticsearch instance is running on. _(Example: 9200)_ - + - - The roles that the new user that is created when a lease is provisioned will be assigned to. This is a required field. This defaults to `superuser`, which is highly privileged. It is recommended to create a new role with the least privileges required for the lease. - +The port that your Elasticsearch instance is running on. _(Example: 9200)_ + - + + The roles that the new user that is created when a lease is provisioned will + be assigned to. This is a required field. This defaults to `superuser`, which + is highly privileged. It is recommended to create a new role with the least + privileges required for the lease. + + + Select the authentication method you want to use to connect to your Elasticsearch instance. - + - - The username of the user that will be used to provision new dynamic secret leases. Only required if you selected the `Username/Password` authentication method. - + + The username of the user that will be used to provision new dynamic secret + leases. Only required if you selected the `Username/Password` authentication + method. + - - The password of the user that will be used to provision new dynamic secret leases. Only required if you selected the `Username/Password` authentication method. - + + The password of the user that will be used to provision new dynamic secret + leases. Only required if you selected the `Username/Password` authentication + method. + - - The ID of the API key that will be used to provision new dynamic secret leases. Only required if you selected the `API Key` authentication method. - + + The ID of the API key that will be used to provision new dynamic secret + leases. Only required if you selected the `API Key` authentication method. + - - The API key that will be used to provision new dynamic secret leases. Only required if you selected the `API Key` authentication method. - + + The API key that will be used to provision new dynamic secret leases. Only + required if you selected the `API Key` authentication method. + - - A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. - + + 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. - ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png) + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + +![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png) - After submitting the form, you will see a dynamic secret created in the dashboard. + 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. + - 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. + 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-redis.png) - ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.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 from it will be shown to you. + Once you click the `Submit` button, a new secret lease will be generated and the credentials from 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. + +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 a 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/ldap.mdx b/docs/documentation/platform/dynamic-secrets/ldap.mdx index a1731432c..1a3eca404 100644 --- a/docs/documentation/platform/dynamic-secrets/ldap.mdx +++ b/docs/documentation/platform/dynamic-secrets/ldap.mdx @@ -123,6 +123,13 @@ 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 + diff --git a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx index 5eda1669e..5d27d16e2 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-atlas.mdx @@ -6,11 +6,10 @@ description: "Learn how to dynamically generate Mongo Atlas Database user creden The Infisical Mongo Atlas dynamic secret allows you to generate Mongo Atlas Database credentials on demand based on configured role. ## Prerequisite -Create a project scopped API Key with the required permission in your Mongo Atlas following the [official doc](https://www.mongodb.com/docs/atlas/configure-api-access/#grant-programmatic-access-to-a-project). - - The API Key must have permission to manage users in the project. - +Create a project scoped API Key with the required permission in your Mongo Atlas following the [official doc](https://www.mongodb.com/docs/atlas/configure-api-access/#grant-programmatic-access-to-a-project). + +The API Key must have permission to manage users in the project. ## Set up Dynamic Secrets with Mongo Atlas @@ -29,86 +28,104 @@ Create a project scopped API Key with the required permission in your Mongo Atla 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 + - - The public key of your generated Atlas API Key. This acts as a username. - + + The public key of your generated Atlas API Key. This acts as a username. + - - The private key of your generated Atlas API Key. This acts as a password. - + + The private key of your generated Atlas API Key. This acts as a password. + - - Unique 24-hexadecimal digit string that identifies your project. This is same as project id - + + Unique 24-hexadecimal digit string that identifies your project. This is same as project id + - - List that provides the pairings of one role with one applicable database. - - **Database Name**: Database to which the user is granted access privileges. - - **Collection**: Collection on which this role applies. - - **Role Name**: Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role. - - Enum: `atlasAdmin` `backup` `clusterMonitor` `dbAdmin` `dbAdminAnyDatabase` `enableSharding` `read` `readAnyDatabase` `readWrite` `readWriteAnyDatabase` ``. - + + List that provides the pairings of one role with one applicable database. + - **Database Name**: Database to which the user is granted access privileges. + - **Collection**: Collection on which this role applies. + - **Role Name**: Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role. + - Enum: `atlasAdmin` `backup` `clusterMonitor` `dbAdmin` `dbAdminAnyDatabase` `enableSharding` `read` `readAnyDatabase` `readWrite` `readWriteAnyDatabase` ``. + - ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-atlas.png) + ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-atlas.png) - 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. - ![Modify Scope Modal](../../../images/platform/dynamic-secrets/advanced-option-atlas.png) - - **Label**: Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access. - - **Type**: Category of resource that this database user can access. +![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. + + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + + 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. + - **Label**: Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access. + - **Type**: Category of resource that this database user can access. + + + - After submitting the form, you will see a dynamic secret created in the dashboard. + 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 certficate. - + + 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. + 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 falls 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. + +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 a 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/mongo-db.mdx b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx index ec384f7a9..f71922473 100644 --- a/docs/documentation/platform/dynamic-secrets/mongo-db.mdx +++ b/docs/documentation/platform/dynamic-secrets/mongo-db.mdx @@ -62,25 +62,32 @@ Create a user with the required permission in your MongoDB instance. This user w Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role. - Enum: `atlasAdmin` `backup` `clusterMonitor` `dbAdmin` `dbAdminAnyDatabase` `enableSharding` `read` `readAnyDatabase` `readWrite` `readWriteAnyDatabase` ``. - + 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 + ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-mongodb.png) - After submitting the form, you will see a dynamic secret created in the dashboard. + 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. - 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. + 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) @@ -95,14 +102,14 @@ Create a user with the required permission in your MongoDB instance. This user w - Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. + Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. ![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. +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 a lease before it's set time to live. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) diff --git a/docs/documentation/platform/dynamic-secrets/mssql.mdx b/docs/documentation/platform/dynamic-secrets/mssql.mdx index 2a279ce90..0a73bf129 100644 --- a/docs/documentation/platform/dynamic-secrets/mssql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mssql.mdx @@ -62,7 +62,7 @@ Create a user with the required permission in your SQL instance. This user will 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). @@ -71,22 +71,30 @@ Create a user with the required permission in your SQL instance. This user will - 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). + ![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. - ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements-mssql.png) + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + 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. + 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 certficate. + If this step fails, you may have to add the CA certificate. ![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. + 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) @@ -101,14 +109,14 @@ Create a user with the required permission in your SQL instance. This user will - 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) ## 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. +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) diff --git a/docs/documentation/platform/dynamic-secrets/mysql.mdx b/docs/documentation/platform/dynamic-secrets/mysql.mdx index f88a88d35..6f708ebba 100644 --- a/docs/documentation/platform/dynamic-secrets/mysql.mdx +++ b/docs/documentation/platform/dynamic-secrets/mysql.mdx @@ -61,29 +61,37 @@ Create a user with the required permission in your SQL instance. This user will 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). - 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). + ![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. - ![Modify SQL Statements Modal](/images/platform/dynamic-secrets/modify-sql-statement-mysql.png) + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + 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. + 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) - 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. + 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) @@ -98,14 +106,14 @@ Create a user with the required permission in your SQL instance. This user will - Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. + Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. ![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. +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 a lease before it's set time to live. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) @@ -116,4 +124,4 @@ To extend the life of the generated dynamic secret leases past its initial time Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret - \ No newline at end of file + diff --git a/docs/documentation/platform/dynamic-secrets/oracle.mdx b/docs/documentation/platform/dynamic-secrets/oracle.mdx index c7b34bec9..02b379b98 100644 --- a/docs/documentation/platform/dynamic-secrets/oracle.mdx +++ b/docs/documentation/platform/dynamic-secrets/oracle.mdx @@ -61,7 +61,7 @@ Create a user with the required permission in your SQL instance. This user will 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). @@ -70,20 +70,30 @@ 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. + + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + 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. + 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 certficate. + If this step fails, you may have to add the CA certificate. ![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. + 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) @@ -98,14 +108,14 @@ Create a user with the required permission in your SQL instance. This user will - 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) ## 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. +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 a lease before it's set time to live. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) @@ -116,4 +126,4 @@ To extend the life of the generated dynamic secret leases past its initial time Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret - \ No newline at end of file + diff --git a/docs/documentation/platform/dynamic-secrets/postgresql.mdx b/docs/documentation/platform/dynamic-secrets/postgresql.mdx index feb81d6d6..f13c9c762 100644 --- a/docs/documentation/platform/dynamic-secrets/postgresql.mdx +++ b/docs/documentation/platform/dynamic-secrets/postgresql.mdx @@ -62,7 +62,7 @@ Create a user with the required permission in your SQL instance. This user will 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). @@ -71,22 +71,30 @@ Create a user with the required permission in your SQL instance. This user will - 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). - ![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. + + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + 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. + 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 certficate. + If this step fails, you may have to add the CA certificate. ![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. + 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) @@ -101,14 +109,14 @@ Create a user with the required permission in your SQL instance. This user will - 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) ## 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. +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) diff --git a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx index 6ac5ac069..09c04e61b 100644 --- a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx +++ b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx @@ -9,7 +9,6 @@ The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credential 1. Ensure that the `management` plugin is enabled on your RabbitMQ instance. This is required for the dynamic secret to work. - ## Set up Dynamic Secrets with RabbitMQ @@ -19,98 +18,113 @@ The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credential ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) - - ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-rabbit-mq.png) + + ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-rabbit-mq-modal.png) 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. + - + Your RabbitMQ host. This must be in HTTP format. _(Example: http://your-cluster-ip)_ - + - - The port that the RabbitMQ management plugin is listening on. This is `15672` by default. - + - - The name of the virtual host that the user will be assigned to. This defaults to `/`. - +The port that the RabbitMQ management plugin is listening on. This is `15672` by default. + + + + The name of the virtual host that the user will be assigned to. This defaults + to `/`. + The permissions that the user will have on the virtual host. This defaults to `.*`. The three permission fields all take a regular expression _(regex)_, that should match resource names for which the user is granted read / write / configuration permissions + + + The username of the user that will be used to provision new dynamic secret + leases. + - - The username of the user that will be used to provision new dynamic secret leases. - + + The password of the user that will be used to provision new dynamic secret + leases. + - - The password of the user that will be used to provision new dynamic secret leases. - + +Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. - - A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. - +Allowed template variables are +- `{{randomUsername}}`: Random username string +- `{{unixTimestamp}}`: Current Unix timestamp + - ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png) + + A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. + +![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png) - After submitting the form, you will see a dynamic secret created in the dashboard. + 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. + - 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. + 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-redis.png) - ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.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 from it will be shown to you. + Once you click the `Submit` button, a new secret lease will be generated and the credentials from 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. + +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 a 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/redis.mdx b/docs/documentation/platform/dynamic-secrets/redis.mdx index 43fbc6b61..b3e585204 100644 --- a/docs/documentation/platform/dynamic-secrets/redis.mdx +++ b/docs/documentation/platform/dynamic-secrets/redis.mdx @@ -56,21 +56,29 @@ Create a user with the required permission in your Redis instance. This user wil - 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). + ![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. - ![Modify Redis Statements Modal](/images/platform/dynamic-secrets/modify-redis-statement.png) + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + 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). + - After submitting the form, you will see a dynamic secret created in the dashboard. + 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. - 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. + 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-redis.png) @@ -85,14 +93,14 @@ Create a user with the required permission in your Redis instance. This user wil - Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. + Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. ![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. +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 a lease before it's set time to live. ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) @@ -103,4 +111,4 @@ To extend the life of the generated dynamic secret leases past its initial time Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret - \ No newline at end of file + diff --git a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx index 3b7a895fb..2737ab084 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-ase.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-ase.mdx @@ -62,21 +62,30 @@ The Infisical SAP ASE dynamic secret allows you to generate SAP ASE database cre ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-setup-modal.png) - - If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. + ![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. - - Due to SAP ASE limitations, the attached SQL statements are not executed as a transaction. - + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + +If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. + +Due to SAP ASE limitations, the attached SQL statements are not executed as a transaction. + + After submitting the form, you will see a dynamic secret created in the dashboard. - 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. + 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) diff --git a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx index 668777549..597d69803 100644 --- a/docs/documentation/platform/dynamic-secrets/sap-hana.mdx +++ b/docs/documentation/platform/dynamic-secrets/sap-hana.mdx @@ -62,14 +62,25 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-sap-hana.png) - - If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. - ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png) + + ![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. + + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + + If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. Due to SAP HANA limitations, the attached SQL statements are not executed as a transaction. + + After submitting the form, you will see a dynamic secret created in the dashboard. @@ -80,8 +91,8 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c - 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. + 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) diff --git a/docs/documentation/platform/dynamic-secrets/snowflake.mdx b/docs/documentation/platform/dynamic-secrets/snowflake.mdx index 75db96c8f..86378bbf6 100644 --- a/docs/documentation/platform/dynamic-secrets/snowflake.mdx +++ b/docs/documentation/platform/dynamic-secrets/snowflake.mdx @@ -8,22 +8,27 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede ## Snowflake Prerequisites - Infisical requires a Snowflake user in your account with the USERADMIN role. This user will act as a service account for Infisical and facilitate the creation of new users as needed. + Infisical requires a Snowflake user in your account with the USERADMIN role. + This user will act as a service account for Infisical and facilitate the + creation of new users as needed. - - ![Snowflake User Dashboard](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-users-page.png) - - - - Be sure to uncheck "Force user to change password on first time login" - - ![Snowflake Create Service User](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-create-service-user.png) - - - ![Snowflake Account And Organization Identifiers](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-identifiers.png) - + + ![Snowflake User + Dashboard](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-users-page.png) + + + + Be sure to uncheck "Force user to change password on first time login" + + ![Snowflake Create Service + User](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-create-service-user.png) + + + ![Snowflake Account And Organization + Identifiers](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-identifiers.png) + ## Set up Dynamic Secrets with Snowflake @@ -71,10 +76,23 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede - If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL - statement to your needs. ![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. + + Allowed template variables are + - `{{randomUsername}}`: Random username string + - `{{unixTimestamp}}`: Current Unix timestamp + + + + If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL + statement to your needs. + + + + + After submitting the form, you will see a dynamic secret created in the dashboard. @@ -104,6 +122,7 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) + ## Audit or Revoke Leases @@ -119,6 +138,6 @@ To extend the life of the generated dynamic secret lease past its initial time t ![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/images/platform/dynamic-secrets/advanced-option-atlas.png b/docs/images/platform/dynamic-secrets/advanced-option-atlas.png index 50c9f89bd..5ddf3920e 100644 Binary files a/docs/images/platform/dynamic-secrets/advanced-option-atlas.png and b/docs/images/platform/dynamic-secrets/advanced-option-atlas.png differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png b/docs/images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png index 14d2d48b2..fd7834302 100644 Binary files a/docs/images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png and b/docs/images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-mongodb.png b/docs/images/platform/dynamic-secrets/dynamic-secret-mongodb.png index d3a804f8f..e978c7d30 100644 Binary files a/docs/images/platform/dynamic-secrets/dynamic-secret-mongodb.png and b/docs/images/platform/dynamic-secrets/dynamic-secret-mongodb.png differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png b/docs/images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png index d412109fa..0ba6aa172 100644 Binary files a/docs/images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png and b/docs/images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-cql-statements.png b/docs/images/platform/dynamic-secrets/modify-cql-statements.png index d1e1b9b98..6bbb44780 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-cql-statements.png and b/docs/images/platform/dynamic-secrets/modify-cql-statements.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-elasticache-statement.png b/docs/images/platform/dynamic-secrets/modify-elasticache-statement.png index c8cd662d0..d9675849d 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-elasticache-statement.png and b/docs/images/platform/dynamic-secrets/modify-elasticache-statement.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-redis-statement.png b/docs/images/platform/dynamic-secrets/modify-redis-statement.png index c9726f752..d37cf9bd0 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-redis-statement.png and b/docs/images/platform/dynamic-secrets/modify-redis-statement.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png b/docs/images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png index 973fcf731..bfff5baa0 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png and b/docs/images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-sql-statement-mysql.png b/docs/images/platform/dynamic-secrets/modify-sql-statement-mysql.png index 8ad9fc0e3..312a63d6c 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-sql-statement-mysql.png and b/docs/images/platform/dynamic-secrets/modify-sql-statement-mysql.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-sql-statement-oracle.png b/docs/images/platform/dynamic-secrets/modify-sql-statement-oracle.png index 0874aa23d..800ef05b1 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-sql-statement-oracle.png and b/docs/images/platform/dynamic-secrets/modify-sql-statement-oracle.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-sql-statements-mssql.png b/docs/images/platform/dynamic-secrets/modify-sql-statements-mssql.png index e399db47d..58c33e655 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-sql-statements-mssql.png and b/docs/images/platform/dynamic-secrets/modify-sql-statements-mssql.png differ diff --git a/docs/images/platform/dynamic-secrets/modify-sql-statements.png b/docs/images/platform/dynamic-secrets/modify-sql-statements.png index d0f3b09da..feda34830 100644 Binary files a/docs/images/platform/dynamic-secrets/modify-sql-statements.png and b/docs/images/platform/dynamic-secrets/modify-sql-statements.png differ diff --git a/docs/images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png b/docs/images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png index 9ac56f456..c133505b7 100644 Binary files a/docs/images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png and b/docs/images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png differ diff --git a/docs/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png b/docs/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png index 44c41bd52..fc7e9f663 100644 Binary files a/docs/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png and b/docs/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png differ diff --git a/frontend/src/hooks/api/dynamicSecret/types.ts b/frontend/src/hooks/api/dynamicSecret/types.ts index c17af7823..45fb20955 100644 --- a/frontend/src/hooks/api/dynamicSecret/types.ts +++ b/frontend/src/hooks/api/dynamicSecret/types.ts @@ -13,6 +13,7 @@ export type TDynamicSecret = { status?: DynamicSecretStatus; statusDetails?: string; maxTTL: string; + usernameTemplate?: string | null; metadata?: { key: string; value: string }[]; }; @@ -287,6 +288,7 @@ export type TCreateDynamicSecretDTO = { environmentSlug: string; name: string; metadata?: { key: string; value: string }[]; + usernameTemplate?: string; }; export type TUpdateDynamicSecretDTO = { @@ -300,6 +302,7 @@ export type TUpdateDynamicSecretDTO = { defaultTTL?: string; maxTTL?: string | null; inputs?: unknown; + usernameTemplate?: string | null; }; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx index 1c513d44a..84cf7f7b5 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx @@ -53,7 +53,8 @@ const formSchema = z.object({ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); }), name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -93,7 +94,8 @@ export const AwsElastiCacheInputForm = ({ "UserId": "{{username}}" }` }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -104,10 +106,13 @@ export const AwsElastiCacheInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider }, @@ -116,7 +121,9 @@ export const AwsElastiCacheInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -270,6 +277,25 @@ export const AwsElastiCacheInputForm = ({ Modify ElastiCache Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -70,7 +71,8 @@ export const AwsIamInputForm = ({ } = useForm({ resolver: zodResolver(formSchema), defaultValues: { - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -81,12 +83,14 @@ export const AwsIamInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; try { + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.AwsIam, inputs: provider }, maxTTL, @@ -94,7 +98,9 @@ export const AwsIamInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -300,6 +306,25 @@ export const AwsIamInputForm = ({ )} /> + ( + + + + )} + /> {!isSingleEnvironmentMode && ( val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -93,7 +94,8 @@ export const CassandraInputForm = ({ resolver: zodResolver(formSchema), defaultValues: { provider: getSqlStatements(), - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -104,11 +106,13 @@ export const CassandraInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, @@ -117,7 +121,9 @@ export const CassandraInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -298,6 +304,25 @@ export const CassandraInputForm = ({ Modify CQL Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -113,7 +114,8 @@ export const ElasticSearchInputForm = ({ roles: ["superuser"], port: 443 }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -124,10 +126,12 @@ export const ElasticSearchInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, @@ -136,7 +140,9 @@ export const ElasticSearchInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -418,6 +424,25 @@ export const ElasticSearchInputForm = ({ )} /> + ( + + + + )} + /> {!isSingleEnvironmentMode && ( val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -120,7 +121,8 @@ export const LdapInputForm = ({ rollbackLdif: "", credentialType: CredentialType.Dynamic }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -133,10 +135,13 @@ export const LdapInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, @@ -145,6 +150,8 @@ export const LdapInputForm = ({ path: secretPath, defaultTTL, projectSlug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, environmentSlug: environment.slug }); onCompleted(); @@ -413,6 +420,25 @@ export const LdapInputForm = ({ )} + ( + + + + )} + /> diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx index 821802954..02ab51d66 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx @@ -66,7 +66,8 @@ const formSchema = z.object({ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); }), name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -114,7 +115,8 @@ export const MongoAtlasInputForm = ({ provider: { roles: [{ databaseName: "", roleName: "" }] }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -135,10 +137,13 @@ export const MongoAtlasInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, @@ -147,7 +152,9 @@ export const MongoAtlasInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -312,7 +319,7 @@ export const MongoAtlasInputForm = ({ label="Role" className="text-xs text-mineshaft-400" tooltipClassName="max-w-md whitespace-pre-line" - tooltipText={`Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role. + tooltipText={`Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role. Built-in: atlasAdmin, backup, clusterMonitor, dbAdmin, dbAdminAnyDatabase, enableSharding, read, readAnyDatabase, readWrite, readWriteAnyDatabase.`} /> )} @@ -362,6 +369,25 @@ export const MongoAtlasInputForm = ({ Advanced + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -89,7 +90,8 @@ export const MongoDBDatabaseInputForm = ({ provider: { roles: [{ roleName: "readWrite" }] }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -105,10 +107,13 @@ export const MongoDBDatabaseInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { @@ -124,7 +129,9 @@ export const MongoDBDatabaseInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -272,7 +279,7 @@ export const MongoDBDatabaseInputForm = ({
@@ -340,6 +347,25 @@ export const MongoDBDatabaseInputForm = ({ )} />
+ ( + + + + )} + /> {!isSingleEnvironmentMode && ( val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -102,7 +103,8 @@ export const RabbitMqInputForm = ({ }, tags: [] }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -113,10 +115,13 @@ export const RabbitMqInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, @@ -125,7 +130,9 @@ export const RabbitMqInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -424,6 +431,25 @@ export const RabbitMqInputForm = ({ )} /> + ( + + + + )} + /> {!isSingleEnvironmentMode && ( val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -87,7 +88,8 @@ export const RedisInputForm = ({ creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all", revocationStatement: "ACL DELUSER {{username}}" }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -98,11 +100,13 @@ export const RedisInputForm = ({ maxTTL, provider, defaultTTL, + usernameTemplate, environment }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; try { + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.Redis, inputs: provider }, maxTTL, @@ -110,7 +114,9 @@ export const RedisInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -265,6 +271,25 @@ export const RedisInputForm = ({ Modify Redis Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -88,7 +89,8 @@ sp_role 'grant', 'mon_role', '{{username}}';`, revocationStatement: `sp_dropuser '{{username}}'; sp_droplogin '{{username}}';` }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -99,11 +101,13 @@ sp_droplogin '{{username}}';` maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; try { + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, maxTTL, @@ -111,7 +115,9 @@ sp_droplogin '{{username}}';` path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -262,6 +268,25 @@ sp_droplogin '{{username}}';` Modify SQL Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -88,7 +89,8 @@ GRANT "MONITORING" TO {{username}};`, DROP USER {{username}};`, renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';" }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -96,6 +98,7 @@ DROP USER {{username}};`, const handleCreateDynamicSecret = async ({ name, + usernameTemplate, maxTTL, provider, defaultTTL, @@ -104,6 +107,7 @@ DROP USER {{username}};`, // wait till previous request is finished if (createDynamicSecret.isPending) return; try { + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, maxTTL, @@ -111,6 +115,8 @@ DROP USER {{username}};`, path: secretPath, defaultTTL, projectSlug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, environmentSlug: environment.slug }); onCompleted(); @@ -263,6 +269,25 @@ DROP USER {{username}};`, Modify SQL Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase"), - environment: z.object({ name: z.string(), slug: z.string() }) + environment: z.object({ name: z.string(), slug: z.string() }), + usernameTemplate: z.string().nullable().optional() }); type TForm = z.infer; @@ -91,7 +92,8 @@ export const SnowflakeInputForm = ({ revocationStatement: "DROP USER {{username}};", renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};" }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -102,11 +104,13 @@ export const SnowflakeInputForm = ({ maxTTL, provider, defaultTTL, - environment + environment, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; try { + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, maxTTL, @@ -114,7 +118,9 @@ export const SnowflakeInputForm = ({ path: secretPath, defaultTTL, projectSlug, - environmentSlug: environment.slug + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch (err) { @@ -266,6 +272,25 @@ export const SnowflakeInputForm = ({ Modify SQL Statements + ( + + + + )} + /> ; @@ -191,7 +192,8 @@ export const SqlDatabaseInputForm = ({ allowedSymbols: "-_.~!*" } }, - environment: isSingleEnvironmentMode ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined, + usernameTemplate: "{{randomUsername}}" } }); @@ -204,11 +206,13 @@ export const SqlDatabaseInputForm = ({ provider, defaultTTL, environment, - metadata + metadata, + usernameTemplate }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await createDynamicSecret.mutateAsync({ provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, @@ -218,7 +222,9 @@ export const SqlDatabaseInputForm = ({ defaultTTL, projectSlug, environmentSlug: environment.slug, - metadata + metadata, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate }); onCompleted(); } catch { @@ -474,6 +480,25 @@ export const SqlDatabaseInputForm = ({ Creation, Revocation & Renew Statements (optional) + ( + + + + )} + />
Customize SQL statements for managing database user lifecycle
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx index 47979af06..f589e64d0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx @@ -56,7 +56,8 @@ const formSchema = z.object({ newName: z .string() .refine((val) => val.toLowerCase() === val, "Must be lowercase") - .optional() + .optional(), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -85,6 +86,7 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -93,9 +95,16 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -106,7 +115,8 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -261,6 +271,24 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({ Modify ElastiCache Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase") - .optional() + .optional(), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -75,6 +76,7 @@ export const EditDynamicSecretAwsIamForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -83,9 +85,16 @@ export const EditDynamicSecretAwsIamForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -96,7 +105,8 @@ export const EditDynamicSecretAwsIamForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -297,6 +307,24 @@ export const EditDynamicSecretAwsIamForm = ({ )} /> + ( + + + + )} + />
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx index 2047d924a..aac598674 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx @@ -58,7 +58,8 @@ const formSchema = z.object({ newName: z .string() .refine((val) => val.toLowerCase() === val, "Must be lowercase") - .optional() + .optional(), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -89,15 +90,23 @@ export const EditDynamicSecretCassandraForm = ({ newName: dynamicSecret.name, inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) - } + }, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}" } }); const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -108,7 +117,8 @@ export const EditDynamicSecretCassandraForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -292,6 +302,24 @@ export const EditDynamicSecretCassandraForm = ({ Modify CQL Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase") - .optional() + .optional(), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -107,6 +108,7 @@ export const EditDynamicSecretElasticSearchForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -115,9 +117,16 @@ export const EditDynamicSecretElasticSearchForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -128,7 +137,8 @@ export const EditDynamicSecretElasticSearchForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -390,7 +400,6 @@ export const EditDynamicSecretElasticSearchForm = ({
-
+ ( + + + + )} + /> diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx index 92744c719..a2530a046 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx @@ -70,7 +70,8 @@ const formSchema = z.object({ newName: z .string() .refine((val) => val.toLowerCase() === val, "Must be lowercase") - .optional() + .optional(), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -101,6 +102,7 @@ export const EditDynamicSecretLdapForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -110,9 +112,17 @@ export const EditDynamicSecretLdapForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); const selectedCredentialType = watch("inputs.credentialType"); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -123,7 +133,8 @@ export const EditDynamicSecretLdapForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -329,6 +340,24 @@ export const EditDynamicSecretLdapForm = ({ )} /> )} + ( + + + + )} + />
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx index 3fb90d2f8..a2c14b6c3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx @@ -50,7 +50,8 @@ const formSchema = z.object({ if (valMs > 24 * 60 * 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); }), - newName: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") + newName: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -80,6 +81,7 @@ export const EditDynamicSecretRabbitMqForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -88,9 +90,17 @@ export const EditDynamicSecretRabbitMqForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -101,7 +111,8 @@ export const EditDynamicSecretRabbitMqForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -405,6 +416,26 @@ export const EditDynamicSecretRabbitMqForm = ({ )} />
+
+ ( + + + + )} + /> +
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx index f315f9f7b..c23c1294b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx @@ -57,7 +57,8 @@ const formSchema = z.object({ newName: z .string() .refine((val) => val.toLowerCase() === val, "Must be lowercase") - .optional() + .optional(), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -86,6 +87,7 @@ export const EditDynamicSecretRedisProviderForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -94,9 +96,16 @@ export const EditDynamicSecretRedisProviderForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -104,6 +113,8 @@ export const EditDynamicSecretRedisProviderForm = ({ projectSlug, environmentSlug: environment, data: { + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, maxTTL: maxTTL || undefined, defaultTTL, inputs, @@ -262,6 +273,24 @@ export const EditDynamicSecretRedisProviderForm = ({ Modify Redis Statements + ( + + + + )} + /> 24 * 60 * 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); }), + usernameTemplate: z.string().trim().nullable().optional(), maxTTL: z .string() .optional() @@ -81,6 +82,7 @@ export const EditDynamicSecretSapAseForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -89,9 +91,17 @@ export const EditDynamicSecretSapAseForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -102,7 +112,8 @@ export const EditDynamicSecretSapAseForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -272,6 +283,24 @@ export const EditDynamicSecretSapAseForm = ({ Modify SQL Statements + ( + + + + )} + /> 24 * 60 * 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); }), - newName: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") + newName: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -81,6 +82,7 @@ export const EditDynamicSecretSapHanaForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -89,9 +91,17 @@ export const EditDynamicSecretSapHanaForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; + + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; try { await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, @@ -102,7 +112,8 @@ export const EditDynamicSecretSapHanaForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -259,6 +270,24 @@ export const EditDynamicSecretSapHanaForm = ({ Modify SQL Statements + ( + + + + )} + /> val.toLowerCase() === val, "Must be lowercase") + .refine((val) => val.toLowerCase() === val, "Must be lowercase"), + usernameTemplate: z.string().trim().nullable().optional() }); type TForm = z.infer; @@ -85,6 +86,7 @@ export const EditDynamicSecretSnowflakeForm = ({ defaultTTL: dynamicSecret.defaultTTL, maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) } @@ -93,10 +95,17 @@ export const EditDynamicSecretSnowflakeForm = ({ const updateDynamicSecret = useUpdateDynamicSecret(); - const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + const handleUpdateDynamicSecret = async ({ + inputs, + maxTTL, + defaultTTL, + newName, + usernameTemplate + }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; try { + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, path: secretPath, @@ -106,7 +115,8 @@ export const EditDynamicSecretSnowflakeForm = ({ maxTTL: maxTTL || undefined, defaultTTL, inputs, - newName: newName === dynamicSecret.name ? undefined : newName + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -262,6 +272,24 @@ export const EditDynamicSecretSnowflakeForm = ({ Modify SQL Statements + ( + + + + )} + /> ; @@ -139,6 +140,7 @@ export const EditDynamicSecretSqlProviderForm = ({ maxTTL: dynamicSecret.maxTTL, newName: dynamicSecret.name, metadata: dynamicSecret.metadata, + usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]), passwordRequirements: @@ -161,11 +163,13 @@ export const EditDynamicSecretSqlProviderForm = ({ maxTTL, defaultTTL, newName, - metadata + metadata, + usernameTemplate }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; try { + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; await updateDynamicSecret.mutateAsync({ name: dynamicSecret.name, path: secretPath, @@ -179,7 +183,8 @@ export const EditDynamicSecretSqlProviderForm = ({ gatewayId: isGatewayInActive ? null : inputs.gatewayId }, newName: newName === dynamicSecret.name ? undefined : newName, - metadata + metadata, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -427,6 +432,24 @@ export const EditDynamicSecretSqlProviderForm = ({ Creation, Revocation & Renew Statements (optional) + ( + + + + )} + />
Customize SQL statements for managing database user lifecycle