Add identityName to Dynamic Secrets userName template

This commit is contained in:
carlosmonastyrski
2025-06-03 21:21:36 -03:00
parent dcb77bbdd4
commit 419e9ac755
33 changed files with 168 additions and 70 deletions
@@ -33,7 +33,7 @@ const userTemplateSchema = z
.refine((el) => validateUsernameTemplateCharacters(el))
.refine((el) =>
isValidHandleBarTemplate(el, {
allowedExpressions: (val) => ["randomUsername", "unixTimestamp"].includes(val)
allowedExpressions: (val) => ["randomUsername", "unixTimestamp", "identityName"].includes(val)
})
);
@@ -1,4 +1,5 @@
import { ForbiddenError, subject } from "@casl/ability";
import RE2 from "re2";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -11,10 +12,13 @@ import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { ms } from "@app/lib/ms";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
@@ -39,6 +43,8 @@ type TDynamicSecretLeaseServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
userDAL: Pick<TUserDALFactory, "findById">;
identityDAL: TIdentityDALFactory;
};
export type TDynamicSecretLeaseServiceFactory = ReturnType<typeof dynamicSecretLeaseServiceFactory>;
@@ -52,8 +58,16 @@ export const dynamicSecretLeaseServiceFactory = ({
dynamicSecretQueueService,
projectDAL,
licenseService,
kmsService
kmsService,
userDAL,
identityDAL
}: TDynamicSecretLeaseServiceFactoryDep) => {
const extractEmailUsername = (email: string) => {
const regex = new RE2(/^([^@]+)/);
const match = email.match(regex);
return match ? match[1] : email;
};
const create = async ({
environmentSlug,
path,
@@ -132,10 +146,23 @@ export const dynamicSecretLeaseServiceFactory = ({
let result;
try {
let identityName = "";
if (actor === ActorType.USER) {
const user = await userDAL.findById(actorId);
if (user) {
identityName = extractEmailUsername(user.username);
}
} else if (actor === ActorType.Machine) {
const identity = await identityDAL.findById(actorId);
if (identity) {
identityName = identity.name;
}
}
result = await selectedProvider.create({
inputs: decryptedStoredInput,
expireAt: expireAt.getTime(),
usernameTemplate: dynamicSecretCfg.usernameTemplate
usernameTemplate: dynamicSecretCfg.usernameTemplate,
identityName
});
} catch (error: unknown) {
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
@@ -132,14 +132,15 @@ const generatePassword = () => {
return customAlphabet(charset, 64)();
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
const randomUsername = `inf-${customAlphabet(charset, 32)()}`;
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -174,14 +175,19 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
return true;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, expireAt, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
if (!(await validateConnection(providerInputs))) {
throw new BadRequestError({ message: "Failed to establish connection" });
}
const leaseUsername = generateUsername(usernameTemplate);
const leaseUsername = generateUsername(usernameTemplate, identityName);
const leasePassword = generatePassword();
const leaseExpiration = new Date(expireAt).toISOString();
@@ -24,13 +24,14 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -60,13 +61,18 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
return isConnected;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const createUserRes = await client.send(
new CreateUserCommand({
@@ -14,13 +14,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size);
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -75,12 +76,17 @@ export const CassandraProvider = (): TDynamicProviderFns => {
return isConnected;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, expireAt, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
const { keyspace } = providerInputs;
const expiration = new Date(expireAt).toISOString();
@@ -13,13 +13,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)();
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -71,12 +72,12 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
return infoResponse;
};
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => {
const { inputs, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
await connection.security.putUser({
@@ -22,13 +22,14 @@ const encodePassword = (password?: string) => {
return base64Password;
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -196,8 +197,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
return dnArray;
};
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => {
const { inputs, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs);
@@ -224,7 +225,7 @@ export const LdapProvider = (): TDynamicProviderFns => {
});
}
} else {
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
@@ -364,6 +364,7 @@ export type TDynamicProviderFns = {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => Promise<{ entityId: string; data: unknown }>;
validateConnection: (inputs: unknown) => Promise<boolean>;
validateProviderInputs: (inputs: object) => Promise<unknown>;
@@ -13,13 +13,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size);
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -64,12 +65,17 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
return isConnected;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, expireAt, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
const expiration = new Date(expireAt).toISOString();
await client({
@@ -13,13 +13,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size);
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -60,12 +61,12 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
return isConnected;
};
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => {
const { inputs, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
const db = client.db(providerInputs.database);
@@ -15,13 +15,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)();
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -117,12 +118,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
return infoResponse;
};
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => {
const { inputs, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
await createRabbitMqUser({
@@ -15,13 +15,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)();
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -121,12 +122,17 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
return pingResponse;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, expireAt, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
const expiration = new Date(expireAt).toISOString();
@@ -15,13 +15,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size);
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = `inf_${alphaNumericNanoId(25)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -87,11 +88,11 @@ export const SapAseProvider = (): TDynamicProviderFns => {
return true;
};
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identityName?: string }) => {
const { inputs, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
const client = await $getClient(providerInputs);
@@ -21,13 +21,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size);
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -97,11 +98,16 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
return testResult;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, expireAt, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
const expiration = new Date(expireAt).toISOString();
@@ -17,13 +17,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size);
};
const generateUsername = (usernameTemplate?: string | null) => {
const generateUsername = (usernameTemplate?: string | null, identityName?: string) => {
const randomUsername = `infisical_${alphaNumericNanoId(32)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -88,13 +89,18 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
return isValidConnection;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, expireAt, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate);
const username = generateUsername(usernameTemplate, identityName);
const password = generatePassword();
try {
@@ -104,9 +104,8 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire
}
};
const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null) => {
const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null, identityName?: string) => {
let randomUsername = "";
// For oracle, the client assumes everything is upper case when not using quotes around the password
if (provider === SqlProviders.Oracle) {
randomUsername = alphaNumericNanoId(32).toUpperCase();
@@ -117,7 +116,8 @@ const generateUsername = (provider: SqlProviders, usernameTemplate?: string | nu
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
unixTimestamp: Math.floor(Date.now() / 100),
identityName
});
};
@@ -220,11 +220,16 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
return isConnected;
};
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const create = async (data: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
identityName?: string;
}) => {
const { inputs, expireAt, usernameTemplate, identityName } = data;
const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(providerInputs.client, usernameTemplate);
const username = generateUsername(providerInputs.client, usernameTemplate, identityName);
const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements);
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
+3 -1
View File
@@ -1508,7 +1508,9 @@ export const registerRoutes = async (
dynamicSecretProviders,
folderDAL,
licenseService,
kmsService
kmsService,
userDAL,
identityDAL
});
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
auditLogDAL,