diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 542471fac..d51826aae 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -122,6 +122,7 @@ export enum EventType { UPDATE_WEBHOOK_STATUS = "update-webhook-status", DELETE_WEBHOOK = "delete-webhook", GET_SECRET_IMPORTS = "get-secret-imports", + GET_SECRET_IMPORT = "get-secret-import", CREATE_SECRET_IMPORT = "create-secret-import", UPDATE_SECRET_IMPORT = "update-secret-import", DELETE_SECRET_IMPORT = "delete-secret-import", @@ -1004,6 +1005,14 @@ interface GetSecretImportsEvent { }; } +interface GetSecretImportEvent { + type: EventType.GET_SECRET_IMPORT; + metadata: { + secretImportId: string; + folderId: string; + }; +} + interface CreateSecretImportEvent { type: EventType.CREATE_SECRET_IMPORT; metadata: { @@ -1620,6 +1629,7 @@ export type Event = | UpdateWebhookStatusEvent | DeleteWebhookEvent | GetSecretImportsEvent + | GetSecretImportEvent | CreateSecretImportEvent | UpdateSecretImportEvent | DeleteSecretImportEvent diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v1/secret-import-router.ts index ec48803f6..aa6efdf36 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v1/secret-import-router.ts @@ -312,6 +312,64 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => } }); + server.route({ + url: "/:secretImportId", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + description: "Get single secret import", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.GET.secretImportId) + }), + response: { + 200: z.object({ + secretImport: SecretImportsSchema.omit({ importEnv: true }).extend({ + environment: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + projectId: z.string(), + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }), + secretPath: z.string() + }) + }) + } + }, + + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.getImportById({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: secretImport.projectId, + event: { + type: EventType.GET_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId + } + } + }); + + return { secretImport }; + } + }); + server.route({ url: "/secrets", method: "GET", diff --git a/backend/src/services/secret-import/secret-import-dal.ts b/backend/src/services/secret-import/secret-import-dal.ts index 893a6d6b4..da25f4d30 100644 --- a/backend/src/services/secret-import/secret-import-dal.ts +++ b/backend/src/services/secret-import/secret-import-dal.ts @@ -97,6 +97,34 @@ export const secretImportDALFactory = (db: TDbClient) => { } }; + const findById = async (id: string, tx?: Knex) => { + try { + const doc = await (tx || db.replicaNode())(TableName.SecretImport) + .where({ [`${TableName.SecretImport}.id` as "id"]: id }) + .join(TableName.Environment, `${TableName.SecretImport}.importEnv`, `${TableName.Environment}.id`) + .select( + db.ref("*").withSchema(TableName.SecretImport) as unknown as keyof TSecretImports, + db.ref("slug").withSchema(TableName.Environment), + db.ref("name").withSchema(TableName.Environment), + db.ref("id").withSchema(TableName.Environment).as("envId") + ) + .first(); + + if (!doc) { + return null; + } + + const { envId, slug, name, ...el } = doc; + + return { + ...el, + importEnv: { id: envId, slug, name } + }; + } catch (error) { + throw new DatabaseError({ error, name: "Find secret imports" }); + } + }; + const getProjectImportCount = async ( { search, ...filter }: Partial, tx?: Knex @@ -144,6 +172,7 @@ export const secretImportDALFactory = (db: TDbClient) => { return { ...secretImportOrm, find, + findById, findByFolderIds, findLastImportPosition, updateAllPosition, diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 9f40c9702..5551b0180 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -24,6 +24,7 @@ import { fnSecretsFromImports, fnSecretsV2FromImports } from "./secret-import-fn import { TCreateSecretImportDTO, TDeleteSecretImportDTO, + TGetSecretImportByIdDTO, TGetSecretImportsDTO, TGetSecretsFromImportDTO, TResyncSecretImportReplicationDTO, @@ -455,6 +456,64 @@ export const secretImportServiceFactory = ({ return secImports; }; + const getImportById = async ({ + actor, + actorId, + actorAuthMethod, + actorOrgId, + id: importId + }: TGetSecretImportByIdDTO) => { + const importDoc = await secretImportDAL.findById(importId); + + if (!importDoc) { + throw new NotFoundError({ message: "Secret import not found" }); + } + + // the folder to import into + const folder = await folderDAL.findById(importDoc.folderId); + + if (!folder) throw new NotFoundError({ message: "Secret import folder not found" }); + + // the folder to import into, with path + const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(folder.projectId, [folder.id]); + + if (!folderWithPath) throw new NotFoundError({ message: "Folder path not found" }); + + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + folder.projectId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { + environment: folder.environment.envSlug, + secretPath: folderWithPath.path + }) + ); + + const importIntoEnv = await projectEnvDAL.findOne({ + projectId: folder.projectId, + slug: folder.environment.envSlug + }); + + if (!importIntoEnv) throw new NotFoundError({ message: "Environment to import into not found" }); + + return { + ...importDoc, + projectId: folder.projectId, + secretPath: folderWithPath.path, + environment: { + id: importIntoEnv.id, + slug: importIntoEnv.slug, + name: importIntoEnv.name + } + }; + }; + const getSecretsFromImports = async ({ path: secretPath, environment, @@ -565,6 +624,7 @@ export const secretImportServiceFactory = ({ updateImport, deleteImport, getImports, + getImportById, getSecretsFromImports, getRawSecretsFromImports, resyncSecretImportReplication, diff --git a/backend/src/services/secret-import/secret-import-types.ts b/backend/src/services/secret-import/secret-import-types.ts index 0a72c4da2..638e36cb1 100644 --- a/backend/src/services/secret-import/secret-import-types.ts +++ b/backend/src/services/secret-import/secret-import-types.ts @@ -37,6 +37,10 @@ export type TGetSecretImportsDTO = { offset?: number; } & TProjectPermission; +export type TGetSecretImportByIdDTO = { + id: string; +} & Omit; + export type TGetSecretsFromImportDTO = { environment: string; path: string;