feat(dynamic-secret): Added new options to username template

This commit is contained in:
carlosmonastyrski
2025-06-04 16:43:17 -03:00
parent 419e9ac755
commit 5367d1ac2e
32 changed files with 551 additions and 196 deletions
@@ -23,7 +23,10 @@ const validateUsernameTemplateCharacters = characterValidator([
CharacterType.CloseBrace, CharacterType.CloseBrace,
CharacterType.CloseBracket, CharacterType.CloseBracket,
CharacterType.OpenBracket, CharacterType.OpenBracket,
CharacterType.Fullstop CharacterType.Fullstop,
CharacterType.SingleQuote,
CharacterType.Spaces,
CharacterType.Pipe
]); ]);
const userTemplateSchema = z const userTemplateSchema = z
@@ -33,7 +36,7 @@ const userTemplateSchema = z
.refine((el) => validateUsernameTemplateCharacters(el)) .refine((el) => validateUsernameTemplateCharacters(el))
.refine((el) => .refine((el) =>
isValidHandleBarTemplate(el, { isValidHandleBarTemplate(el, {
allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identityName"].includes(val) allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identity.name"].includes(val)
}) })
); );
@@ -16,6 +16,7 @@ import { BadRequestError } from "@app/lib/errors";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { DynamicSecretAwsElastiCacheSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretAwsElastiCacheSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const CreateElastiCacheUserSchema = z.object({ const CreateElastiCacheUserSchema = z.object({
UserId: z.string().trim().min(1), UserId: z.string().trim().min(1),
@@ -136,10 +137,9 @@ const generateUsername = (usernameTemplate?: string | null, identityName?: strin
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
const randomUsername = `inf-${customAlphabet(charset, 32)()}`; const randomUsername = `inf-${customAlphabet(charset, 32)()}`;
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -16,21 +16,21 @@ 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";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({ return compileUsernameTemplate({
usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -8,6 +8,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretCassandraSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretCassandraSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = (size = 48) => { const generatePassword = (size = 48) => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
@@ -17,10 +18,9 @@ const generatePassword = (size = 48) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -1,5 +1,4 @@
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";
@@ -7,6 +6,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models"; import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = () => { const generatePassword = () => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
@@ -16,10 +16,9 @@ const generatePassword = () => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -9,6 +9,7 @@ import { BadRequestError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models"; import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = () => { const generatePassword = () => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
@@ -25,10 +26,9 @@ const encodePassword = (password?: string) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -1,5 +1,4 @@
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";
@@ -7,6 +6,7 @@ import { createDigestAuthRequestInterceptor } from "@app/lib/axios/digest-auth";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = (size = 48) => { const generatePassword = (size = 48) => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
@@ -16,10 +16,9 @@ const generatePassword = (size = 48) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -1,4 +1,3 @@
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";
@@ -7,6 +6,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = (size = 48) => { const generatePassword = (size = 48) => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
@@ -16,10 +16,9 @@ const generatePassword = (size = 48) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -1,5 +1,4 @@
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";
@@ -9,6 +8,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = () => { const generatePassword = () => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
@@ -18,10 +18,9 @@ const generatePassword = () => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -9,6 +9,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = () => { const generatePassword = () => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
@@ -18,10 +19,9 @@ const generatePassword = () => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -9,6 +9,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretSapAseSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretSapAseSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = (size = 48) => { const generatePassword = (size = 48) => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -18,10 +19,9 @@ const generatePassword = (size = 48) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = `inf_${alphaNumericNanoId(25)}`; // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = `inf_${alphaNumericNanoId(25)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -15,6 +15,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretSapHanaSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretSapHanaSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const generatePassword = (size = 48) => { const generatePassword = (size = 48) => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -24,10 +25,9 @@ const generatePassword = (size = 48) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -8,6 +8,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { DynamicSecretSnowflakeSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretSnowflakeSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
// destroy client requires callback... // destroy client requires callback...
const noop = () => {}; const noop = () => {};
@@ -20,10 +21,9 @@ const generatePassword = (size = 48) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => { const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = `infisical_${alphaNumericNanoId(32)}`; // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = `infisical_${alphaNumericNanoId(32)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100),
identityName identityName
}); });
}; };
@@ -10,6 +10,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
import { TGatewayServiceFactory } from "../../gateway/gateway-service"; import { TGatewayServiceFactory } from "../../gateway/gateway-service";
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretSqlDBSchema, PasswordRequirements, SqlProviders, TDynamicProviderFns } from "./models"; import { DynamicSecretSqlDBSchema, PasswordRequirements, SqlProviders, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
@@ -113,11 +114,13 @@ const generateUsername = (provider: SqlProviders, usernameTemplate?: string | nu
randomUsername = alphaNumericNanoId(32); randomUsername = alphaNumericNanoId(32);
} }
if (!usernameTemplate) return randomUsername; if (!usernameTemplate) return randomUsername;
return compileUsernameTemplate({
return handlebars.compile(usernameTemplate)({ usernameTemplate,
randomUsername, randomUsername,
unixTimestamp: Math.floor(Date.now() / 100), identityName,
identityName options: {
toUpperCase: provider === SqlProviders.Oracle
}
}); });
}; };
@@ -0,0 +1,98 @@
/* eslint-disable func-names */
import handlebars from "handlebars";
import RE2 from "re2";
import { alphaNumericNanoId } from "@app/lib/nanoid";
export const compileUsernameTemplate = ({
usernameTemplate,
randomUsername,
identityName,
unixTimestamp,
options
}: {
usernameTemplate: string;
randomUsername: string;
identityName?: string;
unixTimestamp?: number;
options?: {
toUpperCase?: boolean;
};
}): string => {
// Pre-process template to replace {{random-N}} patterns before compiling
let processedTemplate = usernameTemplate;
const randomPattern = /\{\{random-(\d+)\}\}/g;
let match;
// eslint-disable-next-line no-cond-assign
while ((match = randomPattern.exec(usernameTemplate)) !== null) {
const fullMatch = match[0];
const length = parseInt(match[1], 10);
if (length > 0 && length <= 100) {
const randomValue = alphaNumericNanoId(length);
processedTemplate = processedTemplate.replace(fullMatch, randomValue);
}
}
// Register replace helper
handlebars.registerHelper(
"replace",
function (text: string, searchValue: string, replaceValue: string, limit?: number) {
// Convert to string if it's not already
const textStr = String(text || "");
if (!textStr) {
return textStr;
}
try {
const re2Pattern = new RE2(searchValue, "g");
if (limit && limit > 0) {
// Replace only up to the specified limit
let count = 0;
return textStr.replace(re2Pattern, (textMatch) => {
if (count < limit) {
count += 1;
return replaceValue;
}
return textMatch;
});
}
// Replace all occurrences
return textStr.replace(re2Pattern, replaceValue);
} catch (error) {
return textStr;
}
}
);
// Register truncate helper
handlebars.registerHelper("truncate", function (text: string, length: number) {
// Convert to string if it's not already
const textStr = String(text || "");
if (!textStr) {
return textStr;
}
if (typeof length !== "number" || length <= 0) return textStr;
return textStr.substring(0, length);
});
// Compile template with context
const context = {
randomUsername,
unixTimestamp: unixTimestamp || Math.floor(Date.now() / 100),
identity: {
name: identityName
}
};
const result = handlebars.compile(processedTemplate)(context);
if (options?.toUpperCase) {
return result.toUpperCase();
}
return result;
};
@@ -1,4 +1,5 @@
import handlebars from "handlebars"; import handlebars from "handlebars";
import RE2 from "re2";
import { BadRequestError } from "../errors"; import { BadRequestError } from "../errors";
import { logger } from "../logger"; import { logger } from "../logger";
@@ -7,13 +8,31 @@ type SanitizationArg = {
allowedExpressions?: (arg: string) => boolean; allowedExpressions?: (arg: string) => boolean;
}; };
const randomPattern = new RE2("^random-\\d+$");
const isValidExpression = (expression: string, dto: SanitizationArg): boolean => {
// Check for random-N pattern (e.g., random-16, random-8)
if (expression.startsWith("random-") && randomPattern.test(expression)) {
return true;
}
// Allow helper functions (replace, truncate)
const allowedHelpers = ["replace", "truncate"];
if (allowedHelpers.includes(expression)) {
return true;
}
// Check regular allowed expressions
return dto?.allowedExpressions?.(expression) || false;
};
export const validateHandlebarTemplate = (templateName: string, template: string, dto: SanitizationArg) => { export const validateHandlebarTemplate = (templateName: string, template: string, dto: SanitizationArg) => {
const parsedAst = handlebars.parse(template); const parsedAst = handlebars.parse(template);
parsedAst.body.forEach((el) => { parsedAst.body.forEach((el) => {
if (el.type === "ContentStatement") return; if (el.type === "ContentStatement") return;
if (el.type === "MustacheStatement" && "path" in el) { if (el.type === "MustacheStatement" && "path" in el) {
const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } }; const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } };
if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return; if (path.type === "PathExpression" && isValidExpression(path.original, dto)) return;
} }
logger.error(el, "Template sanitization failed"); logger.error(el, "Template sanitization failed");
throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` }); throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` });
@@ -26,7 +45,7 @@ export const isValidHandleBarTemplate = (template: string, dto: SanitizationArg)
if (el.type === "ContentStatement") return true; if (el.type === "ContentStatement") return true;
if (el.type === "MustacheStatement" && "path" in el) { if (el.type === "MustacheStatement" && "path" in el) {
const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } }; const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } };
if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return true; if (path.type === "PathExpression" && isValidExpression(path.original, dto)) return true;
} }
return false; return false;
}); });
@@ -101,7 +101,22 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize ElastiCache Statement" type="string"> <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. 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.
@@ -111,7 +111,22 @@ Specifies a template for generating usernames. This field allows customization o
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</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)
@@ -85,7 +85,22 @@ The above configuration allows user creation and granting permissions.
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize CQL Statement" type="string"> <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). 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).
@@ -93,7 +93,22 @@ The port that your Elasticsearch instance is running on. _(Example: 9200)_
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png)
@@ -129,7 +129,22 @@ The Infisical LDAP dynamic secret allows you to generate user credentials on dem
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
</Step> </Step>
@@ -69,7 +69,22 @@ Create a project scoped API Key with the required permission in your Mongo Atlas
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize Scope" type="string"> <ParamField path="Customize Scope" type="string">
@@ -72,7 +72,22 @@ Create a user with the required permission in your MongoDB instance. This user w
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </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)
@@ -9,7 +9,6 @@ The Infisical MS SQL dynamic secret allows you to generate Microsoft SQL server
Create a user with the required permission in your SQL instance. This user will be used to create new accounts on-demand. Create a user with the required permission in your SQL instance. This user will be used to create new accounts on-demand.
## Set up Dynamic Secrets with MS SQL ## Set up Dynamic Secrets with MS SQL
<Steps> <Steps>
@@ -78,11 +77,24 @@ Create a user with the required permission in your SQL instance. This user will
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
</ParamField> - `{{random-N}}`: Random string of N characters
<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). Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </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.
@@ -92,6 +104,7 @@ Create a user with the required permission in your SQL instance. This user will
</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.
@@ -113,19 +126,23 @@ Create a user with the required permission in your SQL instance. This user will
Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you. Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you.
![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) ![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)
## 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>
@@ -75,10 +75,22 @@ Create a user with the required permission in your SQL instance. This user will
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
</ParamField> - `{{random-N}}`: Random string of N characters
<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). Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
@@ -77,10 +77,22 @@ Create a user with the required permission in your SQL instance. This user will
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
</ParamField> - `{{random-N}}`: Random string of N characters
<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). Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
@@ -78,7 +78,22 @@ Create a user with the required permission in your SQL instance. This user will
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize SQL Statement" type="string"> <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).
@@ -71,7 +71,22 @@ Specifies a template for generating usernames. This field allows customization o
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
@@ -63,7 +63,22 @@ Create a user with the required permission in your Redis instance. This user wil
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize Redis Statement" type="string"> <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). 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).
@@ -70,7 +70,22 @@ The Infisical SAP ASE dynamic secret allows you to generate SAP ASE database cre
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize Statement" type="string"> <ParamField path="Customize Statement" type="string">
@@ -70,7 +70,22 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize Statement" type="string"> <ParamField path="Customize Statement" type="string">
@@ -83,7 +83,22 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede
Allowed template variables are Allowed template variables are
- `{{randomUsername}}`: Random username string - `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp - `{{unixTimestamp}}`: Current Unix timestamp
- `{{identityName}}`: Name of the identity that is generating the secret - `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random-N}}`: Random string of N characters
Allowed template functions are
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
Examples:
```
{{randomUsername}} // 3POnzeFyK9gW2nioK0q2gMjr6CZqsRiX
{{unixTimestamp}} // 17490641580
{{identity.name}} // testuser
{{random-5}} // x9k2m
{{truncate identity.name 4}} // test
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField> </ParamField>
<ParamField path="Customize Statement" type="string"> <ParamField path="Customize Statement" type="string">