From 7ed08182792387f9c2332bb0867cfc977e0adaf3 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 3 Oct 2024 12:09:41 +0530 Subject: [PATCH] feat: updated folder, secret import partially and dynamic secret service --- .../dynamic-secret-lease-service.ts | 11 +-- .../dynamic-secret/dynamic-secret-service.ts | 16 ++-- .../services/permission/project-permission.ts | 30 +++++++- .../secret-folder/secret-folder-fns.ts | 6 -- .../secret-folder/secret-folder-service.ts | 73 ++++--------------- .../secret-import/secret-import-service.ts | 61 +++++++--------- 6 files changed, 84 insertions(+), 113 deletions(-) delete mode 100644 backend/src/services/secret-folder/secret-folder-fns.ts diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index 306c7ca26..fe133520d 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -38,6 +38,7 @@ type TDynamicSecretLeaseServiceFactoryDep = { export type TDynamicSecretLeaseServiceFactory = ReturnType; +// TODO(casl): change the lease to different permission export const dynamicSecretLeaseServiceFactory = ({ dynamicSecretLeaseDAL, dynamicSecretProviders, @@ -73,7 +74,7 @@ export const dynamicSecretLeaseServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const plan = await licenseService.getPlan(actorOrgId); @@ -146,7 +147,7 @@ export const dynamicSecretLeaseServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const plan = await licenseService.getPlan(actorOrgId); @@ -220,7 +221,7 @@ export const dynamicSecretLeaseServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -285,7 +286,7 @@ export const dynamicSecretLeaseServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -321,7 +322,7 @@ export const dynamicSecretLeaseServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index eec9094cb..5de76feed 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -78,7 +78,7 @@ export const dynamicSecretServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const plan = await licenseService.getPlan(actorOrgId); @@ -147,7 +147,7 @@ export const dynamicSecretServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const plan = await licenseService.getPlan(actorOrgId); @@ -226,7 +226,7 @@ export const dynamicSecretServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -283,7 +283,7 @@ export const dynamicSecretServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -329,7 +329,7 @@ export const dynamicSecretServiceFactory = ({ environmentSlugs.forEach((environmentSlug) => ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ) ); } @@ -365,7 +365,7 @@ export const dynamicSecretServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -411,7 +411,7 @@ export const dynamicSecretServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -453,7 +453,7 @@ export const dynamicSecretServiceFactory = ({ environmentSlugs.forEach((environmentSlug) => ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path }) + subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ) ); } diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index b2b34e488..28f7e40a2 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -38,6 +38,8 @@ export enum ProjectPermissionSub { Project = "workspace", Secrets = "secrets", SecretFolders = "secret-folders", + SecretImports = "secret-imports", + DynamicSecrets = "dynamic-secrets", SecretRollback = "secret-rollback", SecretApproval = "secret-approval", SecretRotation = "secret-rotation", @@ -54,8 +56,8 @@ export enum ProjectPermissionSub { export type SecretSubjectFields = { environment: string; secretPath: string; - // secretName: string; - // secretTags: string[]; + secretName: string; + secretTags: string[]; }; export const CaslSecretsV2SubjectKnexMapper = (field: string) => { @@ -74,6 +76,16 @@ export type SecretFolderSubjectFields = { secretPath: string; }; +export type DynamicSecretSubjectFields = { + environment: string; + secretPath: string; +}; + +export type SecretImportSubjectFields = { + environment: string; + secretPath: string; +}; + export type ProjectPermissionSet = | [ ProjectPermissionActions, @@ -86,6 +98,20 @@ export type ProjectPermissionSet = | (ForcedSubject & SecretFolderSubjectFields) ) ] + | [ + ProjectPermissionActions, + ( + | ProjectPermissionSub.DynamicSecrets + | (ForcedSubject & DynamicSecretSubjectFields) + ) + ] + | [ + ProjectPermissionActions, + ( + | ProjectPermissionSub.SecretImports + | (ForcedSubject & SecretImportSubjectFields) + ) + ] | [ProjectPermissionActions, ProjectPermissionSub.Role] | [ProjectPermissionActions, ProjectPermissionSub.Tags] | [ProjectPermissionActions, ProjectPermissionSub.Member] diff --git a/backend/src/services/secret-folder/secret-folder-fns.ts b/backend/src/services/secret-folder/secret-folder-fns.ts deleted file mode 100644 index c8f7d885e..000000000 --- a/backend/src/services/secret-folder/secret-folder-fns.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RawRule } from "@casl/ability"; - -import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; - -export const shouldCheckFolderPermission = (rules: RawRule[]) => - rules.some((rule) => (rule.subject as ProjectPermissionSub[]).includes(ProjectPermissionSub.SecretFolders)); diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index b16d90b6b..6815aaa11 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -12,7 +12,6 @@ import { OrderByDirection } from "@app/lib/types"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TSecretFolderDALFactory } from "./secret-folder-dal"; -import { shouldCheckFolderPermission } from "./secret-folder-fns"; import { TCreateFolderDTO, TDeleteFolderDTO, @@ -60,20 +59,10 @@ export const secretFolderServiceFactory = ({ actorOrgId ); - // we do this because we've split Secret and SecretFolder resources - // previously, if one can create/update/read/delete secrets then they can do the same for folders - // for backwards compatibility, we handle authorization only when SecretFolders subject is used - if (shouldCheckFolderPermission(permission.rules)) { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Create, - subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) - ); - } else { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Create, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) - ); - } + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Create, + subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) + ); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); if (!env) throw new NotFoundError({ message: "Environment not found", name: "Create folder" }); @@ -161,20 +150,10 @@ export const secretFolderServiceFactory = ({ ); folders.forEach(({ environment, path: secretPath }) => { - // we do this because we've split Secret and SecretFolder resources - // previously, if one can create/update/read/delete secrets then they can do the same for folders - // for backwards compatibility, we handle authorization only when SecretFolders subject is used - if (shouldCheckFolderPermission(permission.rules)) { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) - ); - } else { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) - ); - } + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Edit, + subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) + ); }); const result = await folderDAL.transaction(async (tx) => @@ -267,20 +246,10 @@ export const secretFolderServiceFactory = ({ actorOrgId ); - // we do this because we've split Secret and SecretFolder resources - // previously, if one can create/update/read/delete secrets then they can do the same for folders - // for backwards compatibility, we handle authorization differently only when SecretFolders subject is used - if (shouldCheckFolderPermission(permission.rules)) { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) - ); - } else { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) - ); - } + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Edit, + subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) + ); const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath); if (!parentFolder) throw new NotFoundError({ message: "Secret path not found" }); @@ -351,20 +320,10 @@ export const secretFolderServiceFactory = ({ actorOrgId ); - // we do this because we've split Secret and SecretFolder resources - // previously, if one can create/update/read/delete secrets then they can do the same for folders - // for backwards compatibility, we handle authorization differently only when SecretFolders subject is used - if (shouldCheckFolderPermission(permission.rules)) { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Delete, - subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) - ); - } else { - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Delete, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) - ); - } + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Delete, + subject(ProjectPermissionSub.SecretFolders, { environment, secretPath }) + ); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); if (!env) throw new NotFoundError({ message: "Environment not found", name: "Create folder" }); diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 5551b0180..8a0fdabc9 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -84,15 +84,17 @@ export const secretImportServiceFactory = ({ // check if user has permission to import into destination path ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); // check if user has permission to import from target path ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Create, + ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment: data.environment, - secretPath: data.path + secretPath: data.path, + secretName: "", + secretTags: [] }) ); if (isReplication) { @@ -191,7 +193,7 @@ export const secretImportServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -277,7 +279,7 @@ export const secretImportServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -342,8 +344,8 @@ export const secretImportServiceFactory = ({ // check if user has permission to import into destination path ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Create, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + ProjectPermissionActions.Edit, + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); const plan = await licenseService.getPlan(actorOrgId); @@ -366,10 +368,12 @@ export const secretImportServiceFactory = ({ // check if user has permission to import from target path ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Create, + ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment: secretImportDoc.importEnv.slug, - secretPath: secretImportDoc.importPath + secretPath: secretImportDoc.importPath, + secretName: "", + secretTags: [] }) ); @@ -414,7 +418,7 @@ export const secretImportServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -446,7 +450,7 @@ export const secretImportServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -532,7 +536,7 @@ export const secretImportServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); if (!folder) return []; @@ -540,16 +544,8 @@ export const secretImportServiceFactory = ({ // so anything based on this order will also be in right position const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false }); - const allowedImports = secretImports.filter(({ importEnv, importPath }) => - permission.can( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { - environment: importEnv.slug, - secretPath: importPath - }) - ) - ); - return fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL }); + // TODO(casl): update here + return fnSecretsFromImports({ allowedImports: secretImports, folderDAL, secretDAL, secretImportDAL }); }; const getRawSecretsFromImports = async ({ @@ -570,7 +566,7 @@ export const secretImportServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) ); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); if (!folder) return []; @@ -578,16 +574,6 @@ export const secretImportServiceFactory = ({ // so anything based on this order will also be in right position const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false }); - const allowedImports = secretImports.filter(({ importEnv, importPath }) => - permission.can( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { - environment: importEnv.slug, - secretPath: importPath - }) - ) - ); - const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); if (shouldUseSecretV2Bridge) { const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ @@ -595,7 +581,7 @@ export const secretImportServiceFactory = ({ projectId }); const importedSecrets = await fnSecretsV2FromImports({ - allowedImports, + allowedImports: secretImports, folderDAL, secretDAL: secretV2BridgeDAL, secretImportDAL, @@ -610,7 +596,12 @@ export const secretImportServiceFactory = ({ name: "bot_not_found_error" }); - const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL }); + const importedSecrets = await fnSecretsFromImports({ + allowedImports: secretImports, + folderDAL, + secretDAL, + secretImportDAL + }); return importedSecrets.map((el) => ({ ...el, secrets: el.secrets.map((encryptedSecret) =>