mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Feat: Recursively get all secrets from inside path
This commit is contained in:
@ -157,11 +157,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),
|
||||
recursive: z
|
||||
deep: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true")
|
||||
.describe(RAW_SECRETS.LIST.recursive),
|
||||
.describe(RAW_SECRETS.LIST.deep),
|
||||
include_imports: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
@ -230,7 +230,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
projectId: workspaceId,
|
||||
path: secretPath,
|
||||
includeImports: req.query.include_imports,
|
||||
recursive: req.query.recursive
|
||||
deep: req.query.deep
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
@ -608,6 +608,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
deep: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true"),
|
||||
include_imports: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
@ -621,6 +625,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
_id: z.string(),
|
||||
workspace: z.string(),
|
||||
environment: z.string(),
|
||||
secretPath: z.string().optional(),
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
@ -660,7 +665,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
environment: req.query.environment,
|
||||
projectId: req.query.workspaceId,
|
||||
path: req.query.secretPath,
|
||||
includeImports: req.query.include_imports
|
||||
includeImports: req.query.include_imports,
|
||||
deep: req.query.deep
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { subject } from "@casl/ability";
|
||||
import path from "path";
|
||||
|
||||
import {
|
||||
@ -10,6 +11,7 @@ import {
|
||||
TSecrets
|
||||
} from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import {
|
||||
buildSecretBlindIndexFromName,
|
||||
@ -20,6 +22,7 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
import { groupBy, unique } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||
import { getBotKeyFnFactory } from "../project-bot/project-bot-fns";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
@ -54,8 +57,25 @@ type TRecursivelyFetchSecretsFromFoldersArg = {
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "find">;
|
||||
};
|
||||
|
||||
export const recursivelyGetSecretPaths = ({ folderDAL, projectEnvDAL }: TRecursivelyFetchSecretsFromFoldersArg) => {
|
||||
const getPaths = async (projectId: string, environment: string, currentPath: string) => {
|
||||
type TGetPathsDTO = {
|
||||
projectId: string;
|
||||
environment: string;
|
||||
currentPath: string;
|
||||
|
||||
auth: {
|
||||
actor: ActorType;
|
||||
actorId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string | undefined;
|
||||
};
|
||||
};
|
||||
|
||||
export const recursivelyGetSecretPaths = ({
|
||||
folderDAL,
|
||||
projectEnvDAL,
|
||||
permissionService
|
||||
}: TRecursivelyFetchSecretsFromFoldersArg) => {
|
||||
const getPaths = async ({ projectId, environment, currentPath }: Omit<TGetPathsDTO, "auth">) => {
|
||||
let secretPaths: string[] = [];
|
||||
|
||||
// Get secrets in the current folder.
|
||||
@ -93,7 +113,7 @@ export const recursivelyGetSecretPaths = ({ folderDAL, projectEnvDAL }: TRecursi
|
||||
// Ensure the path is correctly formatted for the next level.
|
||||
const subFolderPath = `${currentPath}${currentPath !== "/" ? "/" : ""}${folder.name}`;
|
||||
|
||||
return getPaths(projectId, environment, subFolderPath);
|
||||
return getPaths({ projectId, environment, currentPath: subFolderPath });
|
||||
})
|
||||
);
|
||||
|
||||
@ -107,7 +127,32 @@ export const recursivelyGetSecretPaths = ({ folderDAL, projectEnvDAL }: TRecursi
|
||||
return secretPaths;
|
||||
};
|
||||
|
||||
return getPaths;
|
||||
return async ({ projectId, environment, currentPath, auth }: TGetPathsDTO) => {
|
||||
const paths = await getPaths({ projectId, environment, currentPath });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
auth.actor,
|
||||
auth.actorId,
|
||||
projectId,
|
||||
auth.actorAuthMethod,
|
||||
auth.actorOrgId
|
||||
);
|
||||
|
||||
const allowedPaths = paths.filter((p) =>
|
||||
// if its service token allow full access over imported one
|
||||
auth.actor === ActorType.SERVICE
|
||||
? true
|
||||
: permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath: p
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return allowedPaths;
|
||||
};
|
||||
};
|
||||
|
||||
type TInterpolateSecretArg = {
|
||||
@ -275,7 +320,10 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
|
||||
return expandSecrets;
|
||||
};
|
||||
|
||||
export const decryptSecretRaw = (secret: TSecrets & { workspace: string; environment: string }, key: string) => {
|
||||
export const decryptSecretRaw = (
|
||||
secret: TSecrets & { workspace: string; environment: string; secretPath?: string },
|
||||
key: string
|
||||
) => {
|
||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretKeyCiphertext,
|
||||
iv: secret.secretKeyIV,
|
||||
@ -303,6 +351,7 @@ export const decryptSecretRaw = (secret: TSecrets & { workspace: string; environ
|
||||
|
||||
return {
|
||||
secretKey,
|
||||
secretPath: secret.secretPath,
|
||||
workspace: secret.workspace,
|
||||
environment: secret.environment,
|
||||
secretValue,
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-unreachable-loop */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretsSchema, SecretType } from "@app/db/schemas";
|
||||
@ -437,51 +439,98 @@ export const secretServiceFactory = ({
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
includeImports
|
||||
includeImports,
|
||||
deep
|
||||
}: TGetSecretsDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
const importsArray: Awaited<ReturnType<typeof fnSecretsFromImports>> = [];
|
||||
const secretsArray: (Awaited<ReturnType<typeof secretDAL.findByFolderId>>[number] & {
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
workspace: string;
|
||||
})[] = [];
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) return { secrets: [], imports: [] };
|
||||
const folderId = folder.id;
|
||||
let paths = [path];
|
||||
|
||||
const secrets = await secretDAL.findByFolderId(folderId, actorId);
|
||||
if (deep) {
|
||||
const getPaths = recursivelyGetSecretPaths({
|
||||
permissionService,
|
||||
folderDAL,
|
||||
projectEnvDAL
|
||||
});
|
||||
|
||||
const deepPaths = await getPaths({
|
||||
projectId,
|
||||
environment,
|
||||
currentPath: path,
|
||||
auth: {
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}
|
||||
});
|
||||
|
||||
paths = deepPaths.length === 0 ? paths : deepPaths;
|
||||
}
|
||||
|
||||
for (const currentPath of paths) {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: currentPath })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, currentPath);
|
||||
if (!folder) return { secrets: [], imports: [] };
|
||||
const folderId = folder.id;
|
||||
|
||||
const secrets = await secretDAL.findByFolderId(folderId, actorId);
|
||||
if (includeImports) {
|
||||
const secretImports = await secretImportDAL.find({ folderId });
|
||||
const allowedImports = secretImports.filter(({ importEnv, importPath }) =>
|
||||
// if its service token allow full access over imported one
|
||||
actor === ActorType.SERVICE
|
||||
? true
|
||||
: permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: importEnv.slug,
|
||||
secretPath: importPath
|
||||
})
|
||||
)
|
||||
);
|
||||
const importedSecrets = await fnSecretsFromImports({
|
||||
allowedImports,
|
||||
secretDAL,
|
||||
folderDAL
|
||||
});
|
||||
|
||||
secretsArray.push(
|
||||
...secrets.map((secret) => ({ ...secret, secretPath: currentPath, workspace: projectId, environment }))
|
||||
);
|
||||
importsArray.push(...importedSecrets);
|
||||
}
|
||||
secretsArray.push(
|
||||
...secrets.map((secret) => ({ ...secret, secretPath: currentPath, workspace: projectId, environment }))
|
||||
);
|
||||
}
|
||||
|
||||
if (includeImports) {
|
||||
const secretImports = await secretImportDAL.find({ folderId });
|
||||
const allowedImports = secretImports.filter(({ importEnv, importPath }) =>
|
||||
// if its service token allow full access over imported one
|
||||
actor === ActorType.SERVICE
|
||||
? true
|
||||
: permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: importEnv.slug,
|
||||
secretPath: importPath
|
||||
})
|
||||
)
|
||||
);
|
||||
const importedSecrets = await fnSecretsFromImports({
|
||||
allowedImports,
|
||||
secretDAL,
|
||||
folderDAL
|
||||
});
|
||||
return {
|
||||
secrets: secrets.map((el) => ({ ...el, workspace: projectId, environment })),
|
||||
imports: importedSecrets
|
||||
secrets: secretsArray,
|
||||
imports: importsArray
|
||||
};
|
||||
}
|
||||
return { secrets: secrets.map((el) => ({ ...el, workspace: projectId, environment })) };
|
||||
|
||||
return {
|
||||
secrets: secretsArray
|
||||
};
|
||||
};
|
||||
|
||||
const getSecretByName = async ({
|
||||
@ -802,70 +851,32 @@ export const secretServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
environment,
|
||||
includeImports,
|
||||
recursive
|
||||
deep
|
||||
}: TGetSecretsRawDTO) => {
|
||||
const botKey = await projectBotService.getBotKey(projectId);
|
||||
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||
|
||||
let secrets: Awaited<ReturnType<typeof getSecrets>>["secrets"];
|
||||
let imports: Awaited<ReturnType<typeof getSecrets>>["imports"];
|
||||
|
||||
if (recursive) {
|
||||
const getPaths = recursivelyGetSecretPaths({
|
||||
permissionService,
|
||||
folderDAL,
|
||||
projectEnvDAL
|
||||
});
|
||||
|
||||
const paths = await getPaths(projectId, environment, path);
|
||||
|
||||
const result = await Promise.all(
|
||||
paths.map(async (currentPath) => {
|
||||
const secs = await getSecrets({
|
||||
actorId,
|
||||
projectId,
|
||||
environment,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
path: currentPath,
|
||||
includeImports
|
||||
});
|
||||
|
||||
return {
|
||||
secrets: {
|
||||
...secs.secrets,
|
||||
secretPath: currentPath
|
||||
},
|
||||
imports: secs.imports
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
secrets = result.flatMap((el) => el.secrets);
|
||||
imports = result.flatMap((el) => el.imports || []);
|
||||
} else {
|
||||
const result = await getSecrets({
|
||||
actorId,
|
||||
projectId,
|
||||
environment,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
path,
|
||||
includeImports
|
||||
});
|
||||
|
||||
secrets = result.secrets;
|
||||
imports = result.imports;
|
||||
}
|
||||
const { secrets, imports } = await getSecrets({
|
||||
actorId,
|
||||
projectId,
|
||||
environment,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
path,
|
||||
includeImports,
|
||||
deep
|
||||
});
|
||||
|
||||
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 }, botKey)
|
||||
decryptSecretRaw(
|
||||
{ ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath },
|
||||
botKey
|
||||
)
|
||||
)
|
||||
}))
|
||||
};
|
||||
|
Reference in New Issue
Block a user