diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 39728ba7b7..efcb03bd32 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -272,6 +272,7 @@ export const SECRETS = { export const RAW_SECRETS = { LIST: { + expand: "Whether or not to expand secret references", recursive: "Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.", workspaceId: "The ID of the project to list secrets from.", diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 955aa01be6..cae51f8583 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -166,6 +166,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug), environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment), secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath), + expandSecretReferences: z + .enum(["true", "false"]) + .default("false") + .transform((value) => value === "true") + .describe(RAW_SECRETS.LIST.expand), recursive: z .enum(["true", "false"]) .default("false") @@ -233,6 +238,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId, environment, + expandSecretReferences: req.query.expandSecretReferences, actorAuthMethod: req.permission.authMethod, projectId: workspaceId, path: secretPath, diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 5c7b9bef9a..3f647d8a9b 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -27,6 +27,7 @@ import { fnSecretBlindIndexCheck, fnSecretBulkInsert, fnSecretBulkUpdate, + interpolateSecrets, recursivelyGetSecretPaths } from "./secret-fns"; import { TSecretQueueFactory } from "./secret-queue"; @@ -885,6 +886,7 @@ export const secretServiceFactory = ({ actorAuthMethod, environment, includeImports, + expandSecretReferences, recursive }: TGetSecretsRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); @@ -902,17 +904,66 @@ export const secretServiceFactory = ({ recursive }); - return { - secrets: secrets.map((el) => decryptSecretRaw(el, botKey)), - imports: (imports || [])?.map(({ secrets: importedSecrets, ...el }) => ({ - ...el, - secrets: importedSecrets.map((sec) => - decryptSecretRaw( - { ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath }, - botKey - ) + const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey)); + const decryptedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => ({ + ...el, + secrets: importedSecrets.map((sec) => + decryptSecretRaw( + { ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath }, + botKey ) - })) + ) + })); + + if (expandSecretReferences) { + const expandSecrets = interpolateSecrets({ + folderDAL, + projectId, + secretDAL, + secretEncKey: botKey + }); + + const batchSecretsExpand = async ( + secretBatch: { + secretKey: string; + secretValue: string; + secretComment?: string; + }[] + ) => { + const secretRecord: Record< + string, + { + value: string; + comment?: string; + skipMultilineEncoding?: boolean; + } + > = {}; + + secretBatch.forEach((decryptedSecret) => { + secretRecord[decryptedSecret.secretKey] = { + value: decryptedSecret.secretValue, + comment: decryptedSecret.secretComment + }; + }); + + await expandSecrets(secretRecord); + + secretBatch.forEach((decryptedSecret, index) => { + // eslint-disable-next-line no-param-reassign + secretBatch[index].secretValue = secretRecord[decryptedSecret.secretKey].value; + }); + }; + + // expand secrets + await batchSecretsExpand(decryptedSecrets); + + // expand imports by batch + await Promise.all(decryptedImports.map((decryptedImport) => batchSecretsExpand(decryptedImport.secrets))); + } + + return { + secrets: decryptedSecrets, + imports: decryptedImports }; }; diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index c2a0d5cf64..df0af5b5d8 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -138,6 +138,7 @@ export type TDeleteBulkSecretDTO = { } & TProjectPermission; export type TGetSecretsRawDTO = { + expandSecretReferences?: boolean; path: string; environment: string; includeImports?: boolean;