Merge pull request #3667 from akhilmhdh/feat/dynamic-secret-username-template

Feat/dynamic secret username template
This commit is contained in:
Maidul Islam
2025-06-01 21:59:56 -04:00
committed by GitHub
80 changed files with 1502 additions and 453 deletions
@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
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<void> {
const hasColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "usernameTemplate");
if (hasColumn) {
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
t.dropColumn("usernameTemplate");
});
}
}
+2 -1
View File
@@ -28,7 +28,8 @@ export const DynamicSecretsSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
encryptedInput: zodBuffer, encryptedInput: zodBuffer,
projectGatewayId: z.string().uuid().nullable().optional(), 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<typeof DynamicSecretsSchema>; export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
@@ -6,6 +6,8 @@ import { ApiDocsTags, DYNAMIC_SECRETS } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates"; import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms"; 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 { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; 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 { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema"; 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) => { export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "POST", method: "POST",
@@ -52,7 +76,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash), path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1), environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1),
name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name), name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name),
metadata: ResourceMetadataSchema.optional() metadata: ResourceMetadataSchema.optional(),
usernameTemplate: userTemplateSchema.optional()
}), }),
response: { response: {
200: z.object({ 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({ server.route({
method: "PATCH", method: "PATCH",
url: "/:name", url: "/:name",
@@ -150,7 +142,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
}) })
.nullable(), .nullable(),
newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(), newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(),
metadata: ResourceMetadataSchema.optional() metadata: ResourceMetadataSchema.optional(),
usernameTemplate: userTemplateSchema.nullable().optional()
}) })
}), }),
response: { response: {
@@ -328,4 +321,37 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
return { leases }; 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;
}
});
}; };
@@ -132,7 +132,11 @@ export const dynamicSecretLeaseServiceFactory = ({
let result; let result;
try { try {
result = await selectedProvider.create(decryptedStoredInput, expireAt.getTime()); result = await selectedProvider.create({
inputs: decryptedStoredInput,
expireAt: expireAt.getTime(),
usernameTemplate: dynamicSecretCfg.usernameTemplate
});
} catch (error: unknown) { } catch (error: unknown) {
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) { if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
throw new BadRequestError({ message: error.sqlMessage as string }); throw new BadRequestError({ message: error.sqlMessage as string });
@@ -78,7 +78,8 @@ export const dynamicSecretServiceFactory = ({
actorOrgId, actorOrgId,
defaultTTL, defaultTTL,
actorAuthMethod, actorAuthMethod,
metadata metadata,
usernameTemplate
}: TCreateDynamicSecretDTO) => { }: TCreateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -163,7 +164,8 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
folderId: folder.id, folderId: folder.id,
name, name,
gatewayId: selectedGatewayId gatewayId: selectedGatewayId,
usernameTemplate
}, },
tx tx
); );
@@ -199,7 +201,8 @@ export const dynamicSecretServiceFactory = ({
newName, newName,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
metadata metadata,
usernameTemplate
}: TUpdateDynamicSecretDTO) => { }: TUpdateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -311,7 +314,8 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
name: newName ?? name, name: newName ?? name,
status: null, status: null,
gatewayId: selectedGatewayId gatewayId: selectedGatewayId,
usernameTemplate
}, },
tx tx
); );
@@ -22,6 +22,7 @@ export type TCreateDynamicSecretDTO = {
name: string; name: string;
projectSlug: string; projectSlug: string;
metadata?: ResourceMetadataDTO; metadata?: ResourceMetadataDTO;
usernameTemplate?: string | null;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TUpdateDynamicSecretDTO = { export type TUpdateDynamicSecretDTO = {
@@ -34,6 +35,7 @@ export type TUpdateDynamicSecretDTO = {
inputs?: TProvider["inputs"]; inputs?: TProvider["inputs"];
projectSlug: string; projectSlug: string;
metadata?: ResourceMetadataDTO; metadata?: ResourceMetadataDTO;
usernameTemplate?: string | null;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TDeleteDynamicSecretDTO = { export type TDeleteDynamicSecretDTO = {
@@ -132,9 +132,15 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; 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 => { export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
@@ -168,13 +174,14 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
return true; 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); const providerInputs = await validateProviderInputs(inputs);
if (!(await validateConnection(providerInputs))) { if (!(await validateConnection(providerInputs))) {
throw new BadRequestError({ message: "Failed to establish connection" }); throw new BadRequestError({ message: "Failed to establish connection" });
} }
const leaseUsername = generateUsername(); const leaseUsername = generateUsername(usernameTemplate);
const leasePassword = generatePassword(); const leasePassword = generatePassword();
const leaseExpiration = new Date(expireAt).toISOString(); const leaseExpiration = new Date(expireAt).toISOString();
@@ -16,6 +16,7 @@ import {
PutUserPolicyCommand, PutUserPolicyCommand,
RemoveUserFromGroupCommand RemoveUserFromGroupCommand
} from "@aws-sdk/client-iam"; } from "@aws-sdk/client-iam";
import handlebars from "handlebars";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
@@ -23,8 +24,14 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const AwsIamProvider = (): TDynamicProviderFns => { export const AwsIamProvider = (): TDynamicProviderFns => {
@@ -53,11 +60,13 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
return isConnected; 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 providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs; const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const createUserRes = await client.send( const createUserRes = await client.send(
new CreateUserCommand({ new CreateUserCommand({
@@ -55,7 +55,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
return data.success; return data.success;
}; };
const create = async (inputs: unknown) => { const create = async ({ inputs }: { inputs: unknown }) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret); const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret);
if (!data.success) { if (!data.success) {
@@ -88,7 +88,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
// Creates a new password // Creates a new password
await create(inputs); await create({ inputs });
return { entityId }; return { entityId };
}; };
@@ -14,8 +14,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); 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 => { export const CassandraProvider = (): TDynamicProviderFns => {
@@ -69,11 +75,12 @@ export const CassandraProvider = (): TDynamicProviderFns => {
return isConnected; 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 providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const { keyspace } = providerInputs; const { keyspace } = providerInputs;
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
@@ -1,4 +1,5 @@
import { Client as ElasticSearchClient } from "@elastic/elasticsearch"; import { Client as ElasticSearchClient } from "@elastic/elasticsearch";
import handlebars from "handlebars";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -12,8 +13,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); 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 => { export const ElasticSearchProvider = (): TDynamicProviderFns => {
@@ -64,11 +71,12 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
return infoResponse; 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 providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
await connection.security.putUser({ await connection.security.putUser({
@@ -22,8 +22,14 @@ const encodePassword = (password?: string) => {
return base64Password; return base64Password;
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(20); 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 = ({ const generateLDIF = ({
@@ -190,7 +196,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
return dnArray; 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 providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
@@ -217,7 +224,7 @@ export const LdapProvider = (): TDynamicProviderFns => {
}); });
} }
} else { } else {
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif }); const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
@@ -360,7 +360,11 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
]); ]);
export type TDynamicProviderFns = { 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<boolean>; validateConnection: (inputs: unknown) => Promise<boolean>;
validateProviderInputs: (inputs: object) => Promise<unknown>; validateProviderInputs: (inputs: object) => Promise<unknown>;
revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>; revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>;
@@ -1,4 +1,5 @@
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
import handlebars from "handlebars";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -12,8 +13,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const MongoAtlasProvider = (): TDynamicProviderFns => { export const MongoAtlasProvider = (): TDynamicProviderFns => {
@@ -57,11 +64,12 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
return isConnected; 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 providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
await client({ await client({
@@ -1,3 +1,4 @@
import handlebars from "handlebars";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -12,8 +13,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const MongoDBProvider = (): TDynamicProviderFns => { export const MongoDBProvider = (): TDynamicProviderFns => {
@@ -53,11 +60,12 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
return isConnected; 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 providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const db = client.db(providerInputs.database); const db = client.db(providerInputs.database);
@@ -1,4 +1,5 @@
import axios, { Axios } from "axios"; import axios, { Axios } from "axios";
import handlebars from "handlebars";
import https from "https"; import https from "https";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -14,8 +15,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); 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 = { type TCreateRabbitMQUser = {
@@ -110,11 +117,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
return infoResponse; 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 providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
await createRabbitMqUser({ await createRabbitMqUser({
@@ -15,8 +15,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); 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> => { const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => {
@@ -115,11 +121,12 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
return pingResponse; 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 providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
@@ -15,8 +15,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(25); 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 { enum SapCommands {
@@ -81,11 +87,12 @@ export const SapAseProvider = (): TDynamicProviderFns => {
return true; 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 providerInputs = await validateProviderInputs(inputs);
const username = `inf_${generateUsername()}`; const username = generateUsername(usernameTemplate);
const password = `${generatePassword()}`; const password = generatePassword();
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const masterClient = await $getClient(providerInputs, true); const masterClient = await $getClient(providerInputs, true);
@@ -21,8 +21,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); 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 => { export const SapHanaProvider = (): TDynamicProviderFns => {
@@ -91,10 +97,11 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
return testResult; 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 providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
@@ -17,8 +17,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return `infisical_${alphaNumericNanoId(32)}`; // username must start with alpha character, hence prefix 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) => { const getDaysToExpiry = (expiryDate: Date) => {
@@ -82,12 +88,13 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
return isValidConnection; 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 providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
try { try {
@@ -104,11 +104,21 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire
} }
}; };
const generateUsername = (provider: SqlProviders) => { const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null) => {
// For oracle, the client assumes everything is upper case when not using quotes around the password let randomUsername = "";
if (provider === SqlProviders.Oracle) return alphaNumericNanoId(32).toUpperCase();
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 = { type TSqlDatabaseProviderDTO = {
@@ -210,9 +220,12 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
return isConnected; 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 providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(providerInputs.client); const username = generateUsername(providerInputs.client, usernameTemplate);
const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements); const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements);
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => { const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
const db = await $getClient({ ...providerInputs, port, host }); const db = await $getClient({ ...providerInputs, port, host });
@@ -19,3 +19,15 @@ export const validateHandlebarTemplate = (templateName: string, template: string
throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` }); 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;
});
};
@@ -235,11 +235,9 @@ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
inputIV: true, inputIV: true,
inputTag: true, inputTag: true,
algorithm: true algorithm: true
}).merge( }).extend({
z.object({ metadata: ResourceMetadataSchema.optional()
metadata: ResourceMetadataSchema.optional() });
})
);
export const SanitizedAuditLogStreamSchema = z.object({ export const SanitizedAuditLogStreamSchema = z.object({
id: z.string(), id: z.string(),
@@ -60,7 +60,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa
<Step title="Click on the 'Add Dynamic Secret' button"> <Step title="Click on the 'Add Dynamic Secret' button">
![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png)
</Step> </Step>
<Step title="Select 'AWS ElastiCache'"> <Step title="Select AWS ElastiCache">
![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-aws-elasti-cache.png) ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-aws-elasti-cache.png)
</Step> </Step>
<Step title="Provide the inputs for dynamic secret parameters"> <Step title="Provide the inputs for dynamic secret parameters">
@@ -94,21 +94,29 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa
</Step> </Step>
<Step title="(Optional) Modify ElastiCache Statements"> <Step title="(Optional) Modify ElastiCache Statements">
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)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize ElastiCache Statement" type="string">
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.
</ParamField>
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
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.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate-redis.png)
@@ -123,14 +131,14 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![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
<Warning> <Warning>
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
</Warning> </Warning>
@@ -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::\<account-id\>:user/\<aws-scope-path\> > arn:aws:iam::\<account-id\>:user/\<aws-scope-path\>
@@ -94,28 +94,36 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
</ParamField> </ParamField>
<ParamField path="AWS IAM Groups" type="string"> <ParamField path="AWS IAM Groups" type="string">
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
</ParamField> </ParamField>
<ParamField path="AWS Policy ARNs" type="string"> <ParamField path="AWS Policy ARNs" type="string">
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
</ParamField> </ParamField>
<ParamField path="AWS IAM Policy Document" type="string"> <ParamField path="AWS IAM Policy Document" type="string">
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
</ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField> </ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png)
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -130,14 +138,14 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
</Tip> </Tip>
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) ![Provision Lease](/images/platform/dynamic-secrets/lease-values-aws-iam.png)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
@@ -7,7 +7,7 @@ The Infisical Cassandra dynamic secret allows you to generate Cassandra database
## Prerequisite ## 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. Ensure the user possesses privileges for creating, dropping, and granting permissions to roles for it to be able to create dynamic secrets.
<Tip> <Tip>
@@ -19,7 +19,7 @@ authorizer: CassandraAuthorizer
``` ```
</Tip> </Tip>
The above configuration allows user creation and granting permissions. The above configuration allows user creation and granting permissions.
## Set up Dynamic Secrets with Cassandra ## Set up Dynamic Secrets with Cassandra
@@ -69,31 +69,39 @@ The above configuration allows user creation and granting permissions.
<ParamField path="Keyspace" type="string"> <ParamField path="Keyspace" type="string">
Keyspace name where you want to create dynamic secrets. This ensures that the user is limited to that keyspace. Keyspace name where you want to create dynamic secrets. This ensures that the user is limited to that keyspace.
</ParamField> </ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
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.
</ParamField> </ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-cassandra.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-cassandra.png)
</Step> </Step>
<Step title="(Optional) Modify CQL Statements"> <Step title="(Optional) Modify CQL Statements">
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)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize CQL Statement" type="string">
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).
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
<Note> <Note>
If this step fails, you may have to add the CA certficate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -108,14 +116,14 @@ The above configuration allows user creation and granting permissions.
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
@@ -7,13 +7,14 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch
## Prerequisites ## Prerequisites
1. Create a role with at least `manage_security` and `monitor` permissions. 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. 2. Assign the newly created role to your API key or user that you'll use later in the dynamic secret configuration.
<Note> <Note>
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.
</Note> </Note>
## Set up Dynamic Secrets with Elasticsearch ## 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 Name by which you want the secret to be referenced
</ParamField> </ParamField>
<ParamField path="Default TTL" type="string" required> <ParamField path="Default TTL" type="string" required>
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)
</ParamField> </ParamField>
<ParamField path="Max TTL" type="string" required> <ParamField path="Max TTL" type="string" required>
Maximum time-to-live for a generated secret. Maximum time-to-live for a generated secret.
</ParamField> </ParamField>
<ParamField path="Host" type="string" required> <ParamField path="Host" type="string" required>
Your Elasticsearch host. This is the endpoint that your instance runs on. _(Example: https://your-cluster-ip)_ Your Elasticsearch host. This is the endpoint that your instance runs on. _(Example: https://your-cluster-ip)_
</ParamField> </ParamField>
<ParamField path="Port" type="string" required> <ParamField path="Port" type="string" required>
The port that your Elasticsearch instance is running on. _(Example: 9200)_
</ParamField>
<ParamField path="Roles" type="string[]" required> 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. </ParamField>
</ParamField>
<ParamField path="Authentication Method" type="API Key | Username/Password" required> <ParamField path="Roles" type="string[]" required>
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.
</ParamField>
<ParamField path="Authentication Method" type="API Key | Username/Password" required>
Select the authentication method you want to use to connect to your Elasticsearch instance. Select the authentication method you want to use to connect to your Elasticsearch instance.
</ParamField> </ParamField>
<ParamField path="Username" type="string" required> <ParamField path="Username" type="string" required>
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
</ParamField> leases. Only required if you selected the `Username/Password` authentication
method.
</ParamField>
<ParamField path="Password" type="string" required> <ParamField path="Password" type="string" required>
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
</ParamField> leases. Only required if you selected the `Username/Password` authentication
method.
</ParamField>
<ParamField path="API Key ID" required> <ParamField path="API Key ID" required>
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
</ParamField> leases. Only required if you selected the `API Key` authentication method.
</ParamField>
<ParamField path="API Key" required> <ParamField path="API Key" required>
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
</ParamField> required if you selected the `API Key` authentication method.
</ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
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.
</ParamField> </ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png)
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
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.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-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)
<Tip> <Tip>
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.
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
## Renew Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png)
<Warning> <Warning>
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
</Warning> </Warning>
@@ -123,6 +123,13 @@ The Infisical LDAP dynamic secret allows you to generate user credentials on dem
changetype: delete changetype: delete
``` ```
</ParamField> </ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
@@ -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. The Infisical Mongo Atlas dynamic secret allows you to generate Mongo Atlas Database credentials on demand based on configured role.
## Prerequisite ## 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).
<Info> 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.
</Info> <Info>The API Key must have permission to manage users in the project.</Info>
## Set up Dynamic Secrets with Mongo Atlas ## 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 Name by which you want the secret to be referenced
</ParamField> </ParamField>
<ParamField path="Default TTL" type="string" required> <ParamField path="Default TTL" type="string" required>
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)
</ParamField> </ParamField>
<ParamField path="Max TTL" type="string" required> <ParamField path="Max TTL" type="string" required>
Maximum time-to-live for a generated secret Maximum time-to-live for a generated secret
</ParamField> </ParamField>
<ParamField path="Admin public key" type="string" required> <ParamField path="Admin public key" type="string" required>
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.
</ParamField> </ParamField>
<ParamField path="Admin private key" type="string" required> <ParamField path="Admin private key" type="string" required>
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.
</ParamField> </ParamField>
<ParamField path="Group ID" type="number" required> <ParamField path="Group ID" type="number" required>
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
</ParamField> </ParamField>
<ParamField path="Roles" type="string" required> <ParamField path="Roles" type="string" required>
List that provides the pairings of one role with one applicable database. List that provides the pairings of one role with one applicable database.
- **Database Name**: Database to which the user is granted access privileges. - **Database Name**: Database to which the user is granted access privileges.
- **Collection**: Collection on which this role applies. - **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. - **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` `<a custom role name>`. - Enum: `atlasAdmin` `backup` `clusterMonitor` `dbAdmin` `dbAdminAnyDatabase` `enableSharding` `read` `readAnyDatabase` `readWrite` `readWriteAnyDatabase` `<a custom role name>`.
</ParamField> </ParamField>
![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)
</Step> </Step>
<Step title="(Optional) Modify Access Scope"> <Step title="(Optional) Modify Access Scope">
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) ![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. <ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize Scope" type="string">
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.
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
<Note> <Note>
If this step fails, you may have to add the CA certficate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.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)
<Tip> <Tip>
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.
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
## Renew Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png)
<Warning> <Warning>
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
</Warning> </Warning>
@@ -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. 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 custom role name>`. - Enum: `atlasAdmin` `backup` `clusterMonitor` `dbAdmin` `dbAdminAnyDatabase` `enableSharding` `read` `readAnyDatabase` `readWrite` `readWriteAnyDatabase` `<a custom role name>`.
</ParamField> </ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. A CA may be required if your DB requires it for incoming connections.
</ParamField> </ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-mongodb.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-mongodb.png)
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
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.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -95,14 +102,14 @@ Create a user with the required permission in your MongoDB instance. This user w
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
@@ -62,7 +62,7 @@ Create a user with the required permission in your SQL instance. This user will
<ParamField path="Database Name" type="string" required> <ParamField path="Database Name" type="string" required>
Name of the database for which you want to create dynamic secrets Name of the database for which you want to create dynamic secrets
</ParamField> </ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions). A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions).
</ParamField> </ParamField>
@@ -71,22 +71,30 @@ Create a user with the required permission in your SQL instance. This user will
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
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)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
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).
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
<Note> <Note>
If this step fails, you may have to add the CA certficate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -101,14 +109,14 @@ Create a user with the required permission in your SQL instance. This user will
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
@@ -61,29 +61,37 @@ Create a user with the required permission in your SQL instance. This user will
<ParamField path="Database Name" type="string" required> <ParamField path="Database Name" type="string" required>
Name of the database for which you want to create dynamic secrets Name of the database for which you want to create dynamic secrets
</ParamField> </ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions). A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions).
</ParamField> </ParamField>
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
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)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
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).
</ParamField>
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
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.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -98,14 +106,14 @@ Create a user with the required permission in your SQL instance. This user will
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![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
<Warning> <Warning>
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
</Warning> </Warning>
@@ -61,7 +61,7 @@ Create a user with the required permission in your SQL instance. This user will
<ParamField path="Database Name" type="string" required> <ParamField path="Database Name" type="string" required>
Name of the database for which you want to create dynamic secrets Name of the database for which you want to create dynamic secrets
</ParamField> </ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions). A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions).
</ParamField> </ParamField>
@@ -70,20 +70,30 @@ Create a user with the required permission in your SQL instance. This user will
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statement-oracle.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
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). 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).
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
<Note> <Note>
If this step fails, you may have to add the CA certficate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -98,14 +108,14 @@ Create a user with the required permission in your SQL instance. This user will
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![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
<Warning> <Warning>
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
</Warning> </Warning>
@@ -62,7 +62,7 @@ Create a user with the required permission in your SQL instance. This user will
<ParamField path="Database Name" type="string" required> <ParamField path="Database Name" type="string" required>
Name of the database for which you want to create dynamic secrets Name of the database for which you want to create dynamic secrets
</ParamField> </ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions). A CA may be required if your DB requires it for incoming connections. AWS RDS instances with default settings will requires a CA which can be downloaded [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions).
</ParamField> </ParamField>
@@ -71,22 +71,30 @@ Create a user with the required permission in your SQL instance. This user will
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
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) ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
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).
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
<Note> <Note>
If this step fails, you may have to add the CA certficate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -101,14 +109,14 @@ Create a user with the required permission in your SQL instance. This user will
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
@@ -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. 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 ## Set up Dynamic Secrets with RabbitMQ
<Steps> <Steps>
@@ -19,98 +18,113 @@ The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credential
<Step title="Click on the 'Add Dynamic Secret' button"> <Step title="Click on the 'Add Dynamic Secret' button">
![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png)
</Step> </Step>
<Step title="Select 'RabbitMQ'"> <Step title="Select RabbitMQ">
![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)
</Step> </Step>
<Step title="Provide the inputs for dynamic secret parameters"> <Step title="Provide the inputs for dynamic secret parameters">
<ParamField path="Secret Name" type="string" required> <ParamField path="Secret Name" type="string" required>
Name by which you want the secret to be referenced Name by which you want the secret to be referenced
</ParamField> </ParamField>
<ParamField path="Default TTL" type="string" required> <ParamField path="Default TTL" type="string" required>
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)
</ParamField> </ParamField>
<ParamField path="Max TTL" type="string" required> <ParamField path="Max TTL" type="string" required>
Maximum time-to-live for a generated secret. Maximum time-to-live for a generated secret.
</ParamField> </ParamField>
<ParamField path="Host" type="string" required> <ParamField path="Host" type="string" required>
Your RabbitMQ host. This must be in HTTP format. _(Example: http://your-cluster-ip)_ Your RabbitMQ host. This must be in HTTP format. _(Example: http://your-cluster-ip)_
</ParamField> </ParamField>
<ParamField path="Port" type="string" required> <ParamField path="Port" type="string" required>
The port that the RabbitMQ management plugin is listening on. This is `15672` by default.
</ParamField>
<ParamField path="Virtual host name" type="string" required> 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 `/`. </ParamField>
</ParamField>
<ParamField path="Virtual host name" type="string" required>
The name of the virtual host that the user will be assigned to. This defaults
to `/`.
</ParamField>
<ParamField path="Virtual host permissions (Read/Write/Configure)" type="string" required> <ParamField path="Virtual host permissions (Read/Write/Configure)" type="string" required>
The permissions that the user will have on the virtual host. 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 three permission fields all take a regular expression _(regex)_, that should match resource names for which the user is granted read / write / configuration permissions
</ParamField> </ParamField>
<ParamField path="Username" type="string" required>
The username of the user that will be used to provision new dynamic secret
leases.
</ParamField>
<ParamField path="Username" type="string" required> <ParamField path="Password" type="string" required>
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
</ParamField> leases.
</ParamField>
<ParamField path="Password" type="string" required> <ParamField path="Username Template" type="string" default="{{randomUsername}}">
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.
</ParamField>
<ParamField path="CA(SSL)" type="string"> Allowed template variables are
A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. - `{{randomUsername}}`: Random username string
</ParamField> - `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png) <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service.
</ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png)
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
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.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-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)
<Tip> <Tip>
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.
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
## Renew Leases ## 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. 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) ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png)
<Warning> <Warning>
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
</Warning> </Warning>
@@ -56,21 +56,29 @@ Create a user with the required permission in your Redis instance. This user wil
</Step> </Step>
<Step title="(Optional) Modify Redis Statements"> <Step title="(Optional) Modify Redis Statements">
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)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize Redis Statement" type="string">
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).
</ParamField>
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
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.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certificate.
</Note> </Note>
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate-redis.png)
@@ -85,14 +93,14 @@ Create a user with the required permission in your Redis instance. This user wil
</Tip> </Tip>
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)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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. 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) ![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
<Warning> <Warning>
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
</Warning> </Warning>
@@ -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) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-setup-modal.png)
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SAP SQL Statements">
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) ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
<Warning> Allowed template variables are
Due to SAP ASE limitations, the attached SQL statements are not executed as a transaction. - `{{randomUsername}}`: Random username string
</Warning> - `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs.
<Warning>
Due to SAP ASE limitations, the attached SQL statements are not executed as a transaction.
</Warning>
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -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) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-sap-hana.png)
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SAP SQL Statements">
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) <ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs.
<Warning> <Warning>
Due to SAP HANA limitations, the attached SQL statements are not executed as a transaction. Due to SAP HANA limitations, the attached SQL statements are not executed as a transaction.
</Warning> </Warning>
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
@@ -80,8 +91,8 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. 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. 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. 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-generate.png)
@@ -8,22 +8,27 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede
## Snowflake Prerequisites ## Snowflake Prerequisites
<Note> <Note>
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.
</Note> </Note>
<Steps> <Steps>
<Step title="Navigate to Snowflake's User Dashboard and press the '+ User' button"> <Step title="Navigate to Snowflake's User Dashboard and press the '+ User' button">
![Snowflake User Dashboard](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-users-page.png) ![Snowflake User
</Step> Dashboard](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-users-page.png)
<Step title="Create a Snowflake user with the USERADMIN role for Infisical"> </Step>
<Warning> <Step title="Create a Snowflake user with the USERADMIN role for Infisical">
Be sure to uncheck "Force user to change password on first time login" <Warning>
</Warning> 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) </Warning>
</Step> ![Snowflake Create Service
<Step title="Click on the Account Menu in the bottom left and take note of your Account and Organization identifiers"> 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) </Step>
</Step> <Step title="Click on the Account Menu in the bottom left and take note of your Account and Organization identifiers">
![Snowflake Account And Organization
Identifiers](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-identifiers.png)
</Step>
</Steps> </Steps>
## Set up Dynamic Secrets with Snowflake ## Set up Dynamic Secrets with Snowflake
@@ -71,10 +76,23 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
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) ![Modify SQL Statements Modal](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png)
</Step> <ParamField path="Username Template" type="string" default="{{randomUsername}}">
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
</ParamField>
<ParamField path="Customize Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL
statement to your needs.
</ParamField>
</Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
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.
</Step> </Step>
@@ -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) ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## 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) ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png)
<Warning> <Warning>
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic Lease renewals cannot exceed the maximum TTL set when configuring the dynamic
secret. secret.
</Warning> </Warning>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 KiB

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 KiB

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

After

Width:  |  Height:  |  Size: 546 KiB

@@ -13,6 +13,7 @@ export type TDynamicSecret = {
status?: DynamicSecretStatus; status?: DynamicSecretStatus;
statusDetails?: string; statusDetails?: string;
maxTTL: string; maxTTL: string;
usernameTemplate?: string | null;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
}; };
@@ -287,6 +288,7 @@ export type TCreateDynamicSecretDTO = {
environmentSlug: string; environmentSlug: string;
name: string; name: string;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
usernameTemplate?: string;
}; };
export type TUpdateDynamicSecretDTO = { export type TUpdateDynamicSecretDTO = {
@@ -300,6 +302,7 @@ export type TUpdateDynamicSecretDTO = {
defaultTTL?: string; defaultTTL?: string;
maxTTL?: string | null; maxTTL?: string | null;
inputs?: unknown; inputs?: unknown;
usernameTemplate?: string | null;
}; };
}; };
@@ -53,7 +53,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -93,7 +94,8 @@ export const AwsElastiCacheInputForm = ({
"UserId": "{{username}}" "UserId": "{{username}}"
}` }`
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -104,10 +106,13 @@ export const AwsElastiCacheInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider }, provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider },
@@ -116,7 +121,9 @@ export const AwsElastiCacheInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -270,6 +277,25 @@ export const AwsElastiCacheInputForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify ElastiCache Statements</AccordionTrigger> <AccordionTrigger>Modify ElastiCache Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="provider.creationStatement" name="provider.creationStatement"
@@ -42,7 +42,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -70,7 +71,8 @@ export const AwsIamInputForm = ({
} = useForm<TForm>({ } = useForm<TForm>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -81,12 +83,14 @@ export const AwsIamInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.AwsIam, inputs: provider }, provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
maxTTL, maxTTL,
@@ -94,7 +98,9 @@ export const AwsIamInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -300,6 +306,25 @@ export const AwsIamInputForm = ({
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
{!isSingleEnvironmentMode && ( {!isSingleEnvironmentMode && (
<Controller <Controller
control={control} control={control}
@@ -55,7 +55,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -93,7 +94,8 @@ export const CassandraInputForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
provider: getSqlStatements(), provider: getSqlStatements(),
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -104,11 +106,13 @@ export const CassandraInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, provider: { type: DynamicSecretProviders.Cassandra, inputs: provider },
@@ -117,7 +121,9 @@ export const CassandraInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -298,6 +304,25 @@ export const CassandraInputForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify CQL Statements</AccordionTrigger> <AccordionTrigger>Modify CQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="provider.creationStatement" name="provider.creationStatement"
@@ -76,7 +76,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -113,7 +114,8 @@ export const ElasticSearchInputForm = ({
roles: ["superuser"], roles: ["superuser"],
port: 443 port: 443
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -124,10 +126,12 @@ export const ElasticSearchInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider },
@@ -136,7 +140,9 @@ export const ElasticSearchInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -418,6 +424,25 @@ export const ElasticSearchInputForm = ({
)} )}
/> />
</div> </div>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
{!isSingleEnvironmentMode && ( {!isSingleEnvironmentMode && (
<Controller <Controller
control={control} control={control}
@@ -79,7 +79,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -120,7 +121,8 @@ export const LdapInputForm = ({
rollbackLdif: "", rollbackLdif: "",
credentialType: CredentialType.Dynamic credentialType: CredentialType.Dynamic
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -133,10 +135,13 @@ export const LdapInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, provider: { type: DynamicSecretProviders.Ldap, inputs: provider },
@@ -145,6 +150,8 @@ export const LdapInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate,
environmentSlug: environment.slug environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
@@ -413,6 +420,25 @@ export const LdapInputForm = ({
)} )}
</div> </div>
</div> </div>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -66,7 +66,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -114,7 +115,8 @@ export const MongoAtlasInputForm = ({
provider: { provider: {
roles: [{ databaseName: "", roleName: "" }] roles: [{ databaseName: "", roleName: "" }]
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -135,10 +137,13 @@ export const MongoAtlasInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider },
@@ -147,7 +152,9 @@ export const MongoAtlasInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -312,7 +319,7 @@ export const MongoAtlasInputForm = ({
label="Role" label="Role"
className="text-xs text-mineshaft-400" className="text-xs text-mineshaft-400"
tooltipClassName="max-w-md whitespace-pre-line" 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.`} Built-in: atlasAdmin, backup, clusterMonitor, dbAdmin, dbAdminAnyDatabase, enableSharding, read, readAnyDatabase, readWrite, readWriteAnyDatabase.`}
/> />
)} )}
@@ -362,6 +369,25 @@ export const MongoAtlasInputForm = ({
<AccordionItem value="advance-section"> <AccordionItem value="advance-section">
<AccordionTrigger>Advanced</AccordionTrigger> <AccordionTrigger>Advanced</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<FormLabel <FormLabel
label="Scopes" label="Scopes"
isOptional isOptional
@@ -56,7 +56,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -89,7 +90,8 @@ export const MongoDBDatabaseInputForm = ({
provider: { provider: {
roles: [{ roleName: "readWrite" }] roles: [{ roleName: "readWrite" }]
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -105,10 +107,13 @@ export const MongoDBDatabaseInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { provider: {
@@ -124,7 +129,9 @@ export const MongoDBDatabaseInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -272,7 +279,7 @@ export const MongoDBDatabaseInputForm = ({
<FormLabel <FormLabel
label="Roles" label="Roles"
tooltipClassName="max-w-md whitespace-pre-line" 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.`} Built-in: atlasAdmin, backup, clusterMonitor, dbAdmin, dbAdminAnyDatabase, enableSharding, read, readAnyDatabase, readWrite, readWriteAnyDatabase.`}
/> />
<div className="mb-3 mt-1 flex flex-col space-y-2"> <div className="mb-3 mt-1 flex flex-col space-y-2">
@@ -340,6 +347,25 @@ export const MongoDBDatabaseInputForm = ({
)} )}
/> />
</div> </div>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
{!isSingleEnvironmentMode && ( {!isSingleEnvironmentMode && (
<Controller <Controller
control={control} control={control}
@@ -60,7 +60,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -102,7 +103,8 @@ export const RabbitMqInputForm = ({
}, },
tags: [] tags: []
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -113,10 +115,13 @@ export const RabbitMqInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider },
@@ -125,7 +130,9 @@ export const RabbitMqInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -424,6 +431,25 @@ export const RabbitMqInputForm = ({
)} )}
/> />
</div> </div>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
{!isSingleEnvironmentMode && ( {!isSingleEnvironmentMode && (
<Controller <Controller
control={control} control={control}
@@ -53,7 +53,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -87,7 +88,8 @@ export const RedisInputForm = ({
creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all", creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all",
revocationStatement: "ACL DELUSER {{username}}" revocationStatement: "ACL DELUSER {{username}}"
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -98,11 +100,13 @@ export const RedisInputForm = ({
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
usernameTemplate,
environment environment
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Redis, inputs: provider }, provider: { type: DynamicSecretProviders.Redis, inputs: provider },
maxTTL, maxTTL,
@@ -110,7 +114,9 @@ export const RedisInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -265,6 +271,25 @@ export const RedisInputForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify Redis Statements</AccordionTrigger> <AccordionTrigger>Modify Redis Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="provider.creationStatement" name="provider.creationStatement"
@@ -51,7 +51,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -88,7 +89,8 @@ sp_role 'grant', 'mon_role', '{{username}}';`,
revocationStatement: `sp_dropuser '{{username}}'; revocationStatement: `sp_dropuser '{{username}}';
sp_droplogin '{{username}}';` sp_droplogin '{{username}}';`
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -99,11 +101,13 @@ sp_droplogin '{{username}}';`
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, provider: { type: DynamicSecretProviders.SapAse, inputs: provider },
maxTTL, maxTTL,
@@ -111,7 +115,9 @@ sp_droplogin '{{username}}';`
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -262,6 +268,25 @@ sp_droplogin '{{username}}';`
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger> <AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="provider.creationStatement" name="provider.creationStatement"
@@ -53,7 +53,8 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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"), 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -88,7 +89,8 @@ GRANT "MONITORING" TO {{username}};`,
DROP USER {{username}};`, DROP USER {{username}};`,
renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';" 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 ({ const handleCreateDynamicSecret = async ({
name, name,
usernameTemplate,
maxTTL, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
@@ -104,6 +107,7 @@ DROP USER {{username}};`,
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, provider: { type: DynamicSecretProviders.SapHana, inputs: provider },
maxTTL, maxTTL,
@@ -111,6 +115,8 @@ DROP USER {{username}};`,
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate,
environmentSlug: environment.slug environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
@@ -263,6 +269,25 @@ DROP USER {{username}};`,
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger> <AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="provider.creationStatement" name="provider.creationStatement"
@@ -57,7 +57,8 @@ const formSchema = z.object({
.trim() .trim()
.min(1) .min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase"), .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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -91,7 +92,8 @@ export const SnowflakeInputForm = ({
revocationStatement: "DROP USER {{username}};", revocationStatement: "DROP USER {{username}};",
renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};" 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, maxTTL,
provider, provider,
defaultTTL, defaultTTL,
environment environment,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, provider: { type: DynamicSecretProviders.Snowflake, inputs: provider },
maxTTL, maxTTL,
@@ -114,7 +118,9 @@ export const SnowflakeInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug environmentSlug: environment.slug,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch (err) { } catch (err) {
@@ -266,6 +272,25 @@ export const SnowflakeInputForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger> <AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="provider.creationStatement" name="provider.creationStatement"
@@ -96,7 +96,8 @@ const formSchema = z.object({
value: z.string().trim().default("") value: z.string().trim().default("")
}) })
.array() .array()
.optional() .optional(),
usernameTemplate: z.string().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -191,7 +192,8 @@ export const SqlDatabaseInputForm = ({
allowedSymbols: "-_.~!*" allowedSymbols: "-_.~!*"
} }
}, },
environment: isSingleEnvironmentMode ? environments[0] : undefined environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}"
} }
}); });
@@ -204,11 +206,13 @@ export const SqlDatabaseInputForm = ({
provider, provider,
defaultTTL, defaultTTL,
environment, environment,
metadata metadata,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider },
@@ -218,7 +222,9 @@ export const SqlDatabaseInputForm = ({
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment.slug, environmentSlug: environment.slug,
metadata metadata,
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -474,6 +480,25 @@ export const SqlDatabaseInputForm = ({
Creation, Revocation & Renew Statements (optional) Creation, Revocation & Renew Statements (optional)
</AccordionTrigger> </AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<div className="mb-4 text-sm text-mineshaft-300"> <div className="mb-4 text-sm text-mineshaft-300">
Customize SQL statements for managing database user lifecycle Customize SQL statements for managing database user lifecycle
</div> </div>
@@ -56,7 +56,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -85,6 +86,7 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -93,9 +95,16 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -106,7 +115,8 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -261,6 +271,24 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify ElastiCache Statements</AccordionTrigger> <AccordionTrigger>Modify ElastiCache Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="inputs.creationStatement" name="inputs.creationStatement"
@@ -46,7 +46,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -75,6 +76,7 @@ export const EditDynamicSecretAwsIamForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -83,9 +85,16 @@ export const EditDynamicSecretAwsIamForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -96,7 +105,8 @@ export const EditDynamicSecretAwsIamForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -297,6 +307,24 @@ export const EditDynamicSecretAwsIamForm = ({
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</div> </div>
</div> </div>
<div className="mt-4 flex items-center space-x-4"> <div className="mt-4 flex items-center space-x-4">
@@ -58,7 +58,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -89,15 +90,23 @@ export const EditDynamicSecretCassandraForm = ({
newName: dynamicSecret.name, newName: dynamicSecret.name,
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} },
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}"
} }
}); });
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -108,7 +117,8 @@ export const EditDynamicSecretCassandraForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -292,6 +302,24 @@ export const EditDynamicSecretCassandraForm = ({
<AccordionItem value="modify-sql-statement"> <AccordionItem value="modify-sql-statement">
<AccordionTrigger>Modify CQL Statements</AccordionTrigger> <AccordionTrigger>Modify CQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="inputs.creationStatement" name="inputs.creationStatement"
@@ -76,7 +76,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -107,6 +108,7 @@ export const EditDynamicSecretElasticSearchForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -115,9 +117,16 @@ export const EditDynamicSecretElasticSearchForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -128,7 +137,8 @@ export const EditDynamicSecretElasticSearchForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -390,7 +400,6 @@ export const EditDynamicSecretElasticSearchForm = ({
</Button> </Button>
</div> </div>
</div> </div>
<div> <div>
<Controller <Controller
control={control} control={control}
@@ -410,6 +419,24 @@ export const EditDynamicSecretElasticSearchForm = ({
)} )}
/> />
</div> </div>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -70,7 +70,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -101,6 +102,7 @@ export const EditDynamicSecretLdapForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -110,9 +112,17 @@ export const EditDynamicSecretLdapForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); const updateDynamicSecret = useUpdateDynamicSecret();
const selectedCredentialType = watch("inputs.credentialType"); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -123,7 +133,8 @@ export const EditDynamicSecretLdapForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -329,6 +340,24 @@ export const EditDynamicSecretLdapForm = ({
)} )}
/> />
)} )}
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</div> </div>
<div className="mt-4 flex items-center space-x-4"> <div className="mt-4 flex items-center space-x-4">
<Button type="submit" isLoading={isSubmitting}> <Button type="submit" isLoading={isSubmitting}>
@@ -69,7 +69,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -115,6 +116,7 @@ export const EditDynamicSecretMongoAtlasForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -133,9 +135,17 @@ export const EditDynamicSecretMongoAtlasForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -146,7 +156,8 @@ export const EditDynamicSecretMongoAtlasForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -312,7 +323,7 @@ export const EditDynamicSecretMongoAtlasForm = ({
label="Role" label="Role"
className="text-xs text-mineshaft-400" className="text-xs text-mineshaft-400"
tooltipClassName="max-w-md whitespace-pre-line" 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.`} Built-in: atlasAdmin, backup, clusterMonitor, dbAdmin, dbAdminAnyDatabase, enableSharding, read, readAnyDatabase, readWrite, readWriteAnyDatabase.`}
/> />
)} )}
@@ -362,6 +373,24 @@ export const EditDynamicSecretMongoAtlasForm = ({
<AccordionItem value="advance-section"> <AccordionItem value="advance-section">
<AccordionTrigger>Advanced</AccordionTrigger> <AccordionTrigger>Advanced</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<FormLabel <FormLabel
label="Scopes" label="Scopes"
isOptional isOptional
@@ -52,7 +52,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -83,6 +84,7 @@ export const EditDynamicSecretMongoDBForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]), ...(dynamicSecret.inputs as TForm["inputs"]),
roles: (dynamicSecret.inputs as { roles: string[] }).roles?.map((roleName) => ({ roles: (dynamicSecret.inputs as { roles: string[] }).roles?.map((roleName) => ({
@@ -99,9 +101,17 @@ export const EditDynamicSecretMongoDBForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -116,6 +126,8 @@ export const EditDynamicSecretMongoDBForm = ({
port: inputs?.port ? inputs.port : undefined, port: inputs?.port ? inputs.port : undefined,
roles: inputs?.roles?.map((el) => el.roleName) roles: inputs?.roles?.map((el) => el.roleName)
}, },
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName
} }
}); });
@@ -265,7 +277,7 @@ export const EditDynamicSecretMongoDBForm = ({
<FormLabel <FormLabel
label="Roles" label="Roles"
tooltipClassName="max-w-md whitespace-pre-line" 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.`} Built-in: atlasAdmin, backup, clusterMonitor, dbAdmin, dbAdminAnyDatabase, enableSharding, read, readAnyDatabase, readWrite, readWriteAnyDatabase.`}
/> />
<div className="mb-3 mt-1 flex flex-col space-y-2"> <div className="mb-3 mt-1 flex flex-col space-y-2">
@@ -333,6 +345,26 @@ export const EditDynamicSecretMongoDBForm = ({
)} )}
/> />
</div> </div>
<div>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</div>
</div> </div>
</div> </div>
<div className="mt-4 flex items-center space-x-4"> <div className="mt-4 flex items-center space-x-4">
@@ -50,7 +50,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -80,6 +81,7 @@ export const EditDynamicSecretRabbitMqForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -88,9 +90,17 @@ export const EditDynamicSecretRabbitMqForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -101,7 +111,8 @@ export const EditDynamicSecretRabbitMqForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -405,6 +416,26 @@ export const EditDynamicSecretRabbitMqForm = ({
)} )}
/> />
</div> </div>
<div>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -57,7 +57,8 @@ const formSchema = z.object({
newName: z newName: z
.string() .string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase")
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -86,6 +87,7 @@ export const EditDynamicSecretRedisProviderForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -94,9 +96,16 @@ export const EditDynamicSecretRedisProviderForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -104,6 +113,8 @@ export const EditDynamicSecretRedisProviderForm = ({
projectSlug, projectSlug,
environmentSlug: environment, environmentSlug: environment,
data: { data: {
usernameTemplate:
!usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate,
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
@@ -262,6 +273,24 @@ export const EditDynamicSecretRedisProviderForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify Redis Statements</AccordionTrigger> <AccordionTrigger>Modify Redis Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="inputs.creationStatement" name="inputs.creationStatement"
@@ -40,6 +40,7 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
usernameTemplate: z.string().trim().nullable().optional(),
maxTTL: z maxTTL: z
.string() .string()
.optional() .optional()
@@ -81,6 +82,7 @@ export const EditDynamicSecretSapAseForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -89,9 +91,17 @@ export const EditDynamicSecretSapAseForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -102,7 +112,8 @@ export const EditDynamicSecretSapAseForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -272,6 +283,24 @@ export const EditDynamicSecretSapAseForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger> <AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="inputs.creationStatement" name="inputs.creationStatement"
@@ -52,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); 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<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -81,6 +82,7 @@ export const EditDynamicSecretSapHanaForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -89,9 +91,17 @@ export const EditDynamicSecretSapHanaForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
try { try {
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
@@ -102,7 +112,8 @@ export const EditDynamicSecretSapHanaForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -259,6 +270,24 @@ export const EditDynamicSecretSapHanaForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger> <AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="inputs.creationStatement" name="inputs.creationStatement"
@@ -56,7 +56,8 @@ const formSchema = z.object({
.string() .string()
.trim() .trim()
.min(1) .min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -85,6 +86,7 @@ export const EditDynamicSecretSnowflakeForm = ({
defaultTTL: dynamicSecret.defaultTTL, defaultTTL: dynamicSecret.defaultTTL,
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]) ...(dynamicSecret.inputs as TForm["inputs"])
} }
@@ -93,10 +95,17 @@ export const EditDynamicSecretSnowflakeForm = ({
const updateDynamicSecret = useUpdateDynamicSecret(); 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 // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
try { try {
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
path: secretPath, path: secretPath,
@@ -106,7 +115,8 @@ export const EditDynamicSecretSnowflakeForm = ({
maxTTL: maxTTL || undefined, maxTTL: maxTTL || undefined,
defaultTTL, defaultTTL,
inputs, inputs,
newName: newName === dynamicSecret.name ? undefined : newName newName: newName === dynamicSecret.name ? undefined : newName,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -262,6 +272,24 @@ export const EditDynamicSecretSnowflakeForm = ({
<AccordionItem value="advance-statements"> <AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger> <AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="inputs.creationStatement" name="inputs.creationStatement"
@@ -97,7 +97,8 @@ const formSchema = z.object({
value: z.string().trim().default("") value: z.string().trim().default("")
}) })
.array() .array()
.optional() .optional(),
usernameTemplate: z.string().trim().nullable().optional()
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -139,6 +140,7 @@ export const EditDynamicSecretSqlProviderForm = ({
maxTTL: dynamicSecret.maxTTL, maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name, newName: dynamicSecret.name,
metadata: dynamicSecret.metadata, metadata: dynamicSecret.metadata,
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
inputs: { inputs: {
...(dynamicSecret.inputs as TForm["inputs"]), ...(dynamicSecret.inputs as TForm["inputs"]),
passwordRequirements: passwordRequirements:
@@ -161,11 +163,13 @@ export const EditDynamicSecretSqlProviderForm = ({
maxTTL, maxTTL,
defaultTTL, defaultTTL,
newName, newName,
metadata metadata,
usernameTemplate
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (updateDynamicSecret.isPending) return; if (updateDynamicSecret.isPending) return;
try { try {
const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}";
await updateDynamicSecret.mutateAsync({ await updateDynamicSecret.mutateAsync({
name: dynamicSecret.name, name: dynamicSecret.name,
path: secretPath, path: secretPath,
@@ -179,7 +183,8 @@ export const EditDynamicSecretSqlProviderForm = ({
gatewayId: isGatewayInActive ? null : inputs.gatewayId gatewayId: isGatewayInActive ? null : inputs.gatewayId
}, },
newName: newName === dynamicSecret.name ? undefined : newName, newName: newName === dynamicSecret.name ? undefined : newName,
metadata metadata,
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
} }
}); });
onClose(); onClose();
@@ -427,6 +432,24 @@ export const EditDynamicSecretSqlProviderForm = ({
Creation, Revocation & Renew Statements (optional) Creation, Revocation & Renew Statements (optional)
</AccordionTrigger> </AccordionTrigger>
<AccordionContent> <AccordionContent>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<div className="mb-4 text-sm text-mineshaft-300"> <div className="mb-4 text-sm text-mineshaft-300">
Customize SQL statements for managing database user lifecycle Customize SQL statements for managing database user lifecycle
</div> </div>