mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 20:51:50 +00:00
feat(dynamic-secret): Added new options to username template
This commit is contained in:
@@ -23,7 +23,10 @@ const validateUsernameTemplateCharacters = characterValidator([
|
||||
CharacterType.CloseBrace,
|
||||
CharacterType.CloseBracket,
|
||||
CharacterType.OpenBracket,
|
||||
CharacterType.Fullstop
|
||||
CharacterType.Fullstop,
|
||||
CharacterType.SingleQuote,
|
||||
CharacterType.Spaces,
|
||||
CharacterType.Pipe
|
||||
]);
|
||||
|
||||
const userTemplateSchema = z
|
||||
@@ -33,7 +36,7 @@ const userTemplateSchema = z
|
||||
.refine((el) => validateUsernameTemplateCharacters(el))
|
||||
.refine((el) =>
|
||||
isValidHandleBarTemplate(el, {
|
||||
allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identityName"].includes(val)
|
||||
allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identity.name"].includes(val)
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
|
||||
import { DynamicSecretAwsElastiCacheSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const CreateElastiCacheUserSchema = z.object({
|
||||
UserId: z.string().trim().min(1),
|
||||
@@ -136,10 +137,9 @@ const generateUsername = (usernameTemplate?: string | null, identityName?: strin
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
|
||||
const randomUsername = `inf-${customAlphabet(charset, 32)()}`;
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -16,21 +16,21 @@ import {
|
||||
PutUserPolicyCommand,
|
||||
RemoveUserFromGroupCommand
|
||||
} from "@aws-sdk/client-iam";
|
||||
import handlebars from "handlebars";
|
||||
import { z } from "zod";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32);
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretCassandraSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = (size = 48) => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||
@@ -17,10 +18,9 @@ const generatePassword = (size = 48) => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Client as ElasticSearchClient } from "@elastic/elasticsearch";
|
||||
import handlebars from "handlebars";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -7,6 +6,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = () => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||
@@ -16,10 +16,9 @@ const generatePassword = () => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = () => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||
@@ -25,10 +26,9 @@ const encodePassword = (password?: string) => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import axios, { AxiosError } from "axios";
|
||||
import handlebars from "handlebars";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -7,6 +6,7 @@ import { createDigestAuthRequestInterceptor } from "@app/lib/axios/digest-auth";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = (size = 48) => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||
@@ -16,10 +16,9 @@ const generatePassword = (size = 48) => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32);
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import handlebars from "handlebars";
|
||||
import { MongoClient } from "mongodb";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@@ -7,6 +6,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = (size = 48) => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||
@@ -16,10 +16,9 @@ const generatePassword = (size = 48) => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32);
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import axios, { Axios } from "axios";
|
||||
import handlebars from "handlebars";
|
||||
import https from "https";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@@ -9,6 +8,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = () => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||
@@ -18,10 +18,9 @@ const generatePassword = () => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = () => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||
@@ -18,10 +19,9 @@ const generatePassword = () => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretSapAseSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = (size = 48) => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
@@ -18,10 +19,9 @@ const generatePassword = (size = 48) => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = `inf_${alphaNumericNanoId(25)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretSapHanaSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const generatePassword = (size = 48) => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
@@ -24,10 +25,9 @@ const generatePassword = (size = 48) => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
|
||||
import { DynamicSecretSnowflakeSchema, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
// destroy client requires callback...
|
||||
const noop = () => {};
|
||||
@@ -20,10 +21,9 @@ const generatePassword = (size = 48) => {
|
||||
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
|
||||
const randomUsername = `infisical_${alphaNumericNanoId(32)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars
|
||||
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretSqlDBSchema, PasswordRequirements, SqlProviders, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
|
||||
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||
|
||||
@@ -113,11 +114,13 @@ const generateUsername = (provider: SqlProviders, usernameTemplate?: string | nu
|
||||
randomUsername = alphaNumericNanoId(32);
|
||||
}
|
||||
if (!usernameTemplate) return randomUsername;
|
||||
|
||||
return handlebars.compile(usernameTemplate)({
|
||||
return compileUsernameTemplate({
|
||||
usernameTemplate,
|
||||
randomUsername,
|
||||
unixTimestamp: Math.floor(Date.now() / 100),
|
||||
identityName
|
||||
identityName,
|
||||
options: {
|
||||
toUpperCase: provider === SqlProviders.Oracle
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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 RE2 from "re2";
|
||||
|
||||
import { BadRequestError } from "../errors";
|
||||
import { logger } from "../logger";
|
||||
@@ -7,13 +8,31 @@ type SanitizationArg = {
|
||||
allowedExpressions?: (arg: string) => boolean;
|
||||
};
|
||||
|
||||
const randomPattern = new RE2("^random-\\d+$");
|
||||
|
||||
const isValidExpression = (expression: string, dto: SanitizationArg): boolean => {
|
||||
// Check for random-N pattern (e.g., random-16, random-8)
|
||||
if (expression.startsWith("random-") && randomPattern.test(expression)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow helper functions (replace, truncate)
|
||||
const allowedHelpers = ["replace", "truncate"];
|
||||
if (allowedHelpers.includes(expression)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check regular allowed expressions
|
||||
return dto?.allowedExpressions?.(expression) || false;
|
||||
};
|
||||
|
||||
export const validateHandlebarTemplate = (templateName: string, template: string, dto: SanitizationArg) => {
|
||||
const parsedAst = handlebars.parse(template);
|
||||
parsedAst.body.forEach((el) => {
|
||||
if (el.type === "ContentStatement") return;
|
||||
if (el.type === "MustacheStatement" && "path" in el) {
|
||||
const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } };
|
||||
if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return;
|
||||
if (path.type === "PathExpression" && isValidExpression(path.original, dto)) return;
|
||||
}
|
||||
logger.error(el, "Template sanitization failed");
|
||||
throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` });
|
||||
@@ -26,7 +45,7 @@ export const isValidHandleBarTemplate = (template: string, dto: SanitizationArg)
|
||||
if (el.type === "ContentStatement") return true;
|
||||
if (el.type === "MustacheStatement" && "path" in el) {
|
||||
const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } };
|
||||
if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return true;
|
||||
if (path.type === "PathExpression" && isValidExpression(path.original, dto)) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user