diff --git a/backend/src/db/migrations/20250517002225_secret-scanning-v2.ts b/backend/src/db/migrations/20250517002225_secret-scanning-v2.ts index 660c623e2..71b5f9810 100644 --- a/backend/src/db/migrations/20250517002225_secret-scanning-v2.ts +++ b/backend/src/db/migrations/20250517002225_secret-scanning-v2.ts @@ -23,6 +23,7 @@ export async function up(knex: Knex): Promise { t.string("projectId").notNullable(); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.timestamps(true, true, true); + t.boolean("isDisconnected").notNullable().defaultTo(false); t.unique(["projectId", "name"]); }); await createOnUpdateTrigger(knex, TableName.SecretScanningDataSource); diff --git a/backend/src/db/schemas/secret-scanning-data-sources.ts b/backend/src/db/schemas/secret-scanning-data-sources.ts index 90f997216..d79b45e79 100644 --- a/backend/src/db/schemas/secret-scanning-data-sources.ts +++ b/backend/src/db/schemas/secret-scanning-data-sources.ts @@ -21,7 +21,8 @@ export const SecretScanningDataSourcesSchema = z.object({ isAutoScanEnabled: z.boolean().default(true).nullable().optional(), projectId: z.string(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + isDisconnected: z.boolean().default(false) }); export type TSecretScanningDataSources = z.infer; diff --git a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints.ts b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints.ts index 2a606e034..3a5c6b4d7 100644 --- a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints.ts +++ b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints.ts @@ -298,7 +298,7 @@ export const registerSecretScanningEndpoints = < handler: async (req) => { const { dataSourceId } = req.params; - const dataSource = (await server.services.secretScanningV2.deleteSecretScanningResource( + const dataSource = (await server.services.secretScanningV2.deleteSecretScanningDataSource( { type, dataSourceId }, req.permission )) as T; @@ -349,7 +349,7 @@ export const registerSecretScanningEndpoints = < ...req.auditLogInfo, projectId: dataSource.projectId, event: { - type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + type: EventType.SECRET_SCANNING_DATA_SOURCE_TRIGGER_SCAN, metadata: { type, dataSourceId @@ -392,7 +392,7 @@ export const registerSecretScanningEndpoints = < ...req.auditLogInfo, projectId: dataSource.projectId, event: { - type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + type: EventType.SECRET_SCANNING_DATA_SOURCE_TRIGGER_SCAN, metadata: { type, dataSourceId, diff --git a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts index a7222875a..ed6b55006 100644 --- a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts +++ b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts @@ -140,12 +140,12 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) schema: { hide: false, tags: [ApiDocsTags.SecretScanning], - description: "Update the resolve status of the specified Secret Scanning Finding.", + description: "Update the specified Secret Scanning Finding.", params: z.object({ findingId: z.string().trim().min(1, "Finding ID required").describe(SecretScanningFindings.UPDATE.findingId) }), body: z.object({ - status: z.nativeEnum(SecretScanningFindingStatus).describe(SecretScanningFindings.UPDATE.status), + status: z.nativeEnum(SecretScanningFindingStatus).optional().describe(SecretScanningFindings.UPDATE.status), remarks: z.string().nullish().describe(SecretScanningFindings.UPDATE.remarks) }), response: { 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 cdebf78ff..21b8a93db 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -9,7 +9,11 @@ import { TSecretRotationV2Raw, TUpdateSecretRotationV2DTO } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types"; -import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; +import { + SecretScanningDataSource, + SecretScanningScanStatus, + SecretScanningScanType +} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; import { TCreateSecretScanningDataSourceDTO, TDeleteSecretScanningDataSourceDTO, @@ -390,6 +394,7 @@ export enum EventType { SECRET_SCANNING_DATA_SOURCE_UPDATE = "secret-scanning-data-source-update", SECRET_SCANNING_DATA_SOURCE_DELETE = "secret-scanning-data-source-delete", SECRET_SCANNING_DATA_SOURCE_GET = "secret-scanning-data-source-get", + SECRET_SCANNING_DATA_SOURCE_TRIGGER_SCAN = "secret-scanning-data-source-trigger-scan", SECRET_SCANNING_DATA_SOURCE_SCAN = "secret-scanning-data-source-scan", SECRET_SCANNING_RESOURCE_LIST = "secret-scanning-resource-list", SECRET_SCANNING_SCAN_LIST = "secret-scanning-scan-list", @@ -2964,9 +2969,23 @@ interface SecretScanningDataSourceDeleteEvent { metadata: TDeleteSecretScanningDataSourceDTO; } +interface SecretScanningDataSourceTriggerScanEvent { + type: EventType.SECRET_SCANNING_DATA_SOURCE_TRIGGER_SCAN; + metadata: TTriggerSecretScanningDataSourceDTO; +} + interface SecretScanningDataSourceScanEvent { type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN; - metadata: TTriggerSecretScanningDataSourceDTO; + metadata: { + scanId: string; + resourceId: string; + resourceType: string; + dataSourceId: string; + dataSourceType: string; + scanStatus: SecretScanningScanStatus; + scanType: SecretScanningScanType; + numberOfSecretsDetected?: number; + }; } interface SecretScanningResourceListEvent { @@ -3273,6 +3292,7 @@ export type Event = | SecretScanningDataSourceCreateEvent | SecretScanningDataSourceUpdateEvent | SecretScanningDataSourceDeleteEvent + | SecretScanningDataSourceTriggerScanEvent | SecretScanningDataSourceScanEvent | SecretScanningResourceListEvent | SecretScanningScanListEvent diff --git a/backend/src/ee/services/permission/default-roles.ts b/backend/src/ee/services/permission/default-roles.ts index f95687aed..75e99e834 100644 --- a/backend/src/ee/services/permission/default-roles.ts +++ b/backend/src/ee/services/permission/default-roles.ts @@ -214,7 +214,7 @@ const buildAdminPermissionRules = () => { ); can( - [ProjectPermissionSecretScanningFindingActions.Read, ProjectPermissionSecretScanningFindingActions.Resolve], + [ProjectPermissionSecretScanningFindingActions.Read, ProjectPermissionSecretScanningFindingActions.Update], ProjectPermissionSub.SecretScanningFindings ); @@ -400,9 +400,6 @@ const buildMemberPermissionRules = () => { can( [ - ProjectPermissionSecretScanningDataSourceActions.Create, - ProjectPermissionSecretScanningDataSourceActions.Edit, - ProjectPermissionSecretScanningDataSourceActions.Delete, ProjectPermissionSecretScanningDataSourceActions.Read, ProjectPermissionSecretScanningDataSourceActions.TriggerScans, ProjectPermissionSecretScanningDataSourceActions.ReadScans, @@ -412,7 +409,7 @@ const buildMemberPermissionRules = () => { ); can( - [ProjectPermissionSecretScanningFindingActions.Read, ProjectPermissionSecretScanningFindingActions.Resolve], + [ProjectPermissionSecretScanningFindingActions.Read, ProjectPermissionSecretScanningFindingActions.Update], ProjectPermissionSub.SecretScanningFindings ); diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 683e55000..6c50cc53e 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -135,7 +135,7 @@ export enum ProjectPermissionSecretScanningDataSourceActions { export enum ProjectPermissionSecretScanningFindingActions { Read = "read-findings", - Resolve = "resolve-findings" + Update = "update-findings" } export enum ProjectPermissionSub { diff --git a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-factory.ts b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-factory.ts index 70777ddce..63be91b53 100644 --- a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-factory.ts +++ b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-factory.ts @@ -4,10 +4,11 @@ import { ProbotOctokit } from "probot"; import { scanContentAndGetFindings } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns"; import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types"; import { + SecretScanningDataSource, SecretScanningFindingSeverity, SecretScanningResource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; -import { cloneRepository, titleCaseToCamelCase } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-fns"; +import { cloneRepository } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-fns"; import { TSecretScanningFactoryGetDiffScanFindingsPayload, TSecretScanningFactoryGetDiffScanResourcePayload, @@ -18,6 +19,7 @@ import { } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; +import { titleCaseToCamelCase } from "@app/lib/fn"; import { listGitHubRadarRepositories, TGitHubRadarConnection } from "@app/services/app-connection/github-radar"; import { TGitHubDataSourceWithConnection, TQueueGitHubResourceDiffScan } from "./github-secret-scanning-types"; @@ -30,7 +32,8 @@ export const GitHubSecretScanningFactory = () => { const externalId = connection.credentials.installationId; const existingDataSource = await secretScanningV2DAL.dataSources.findOne({ - externalId + externalId, + type: SecretScanningDataSource.GitHub }); if (existingDataSource) diff --git a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-schemas.ts b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-schemas.ts index fc2fbd241..e1bb88bc7 100644 --- a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-schemas.ts +++ b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-schemas.ts @@ -11,10 +11,15 @@ import { BaseUpdateSecretScanningDataSourceSchema, GitRepositoryScanFindingDetailsSchema } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-schemas"; +import { SecretScanningDataSources } from "@app/lib/api-docs"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; export const GitHubDataSourceConfigSchema = z.object({ - includeRepos: z.array(z.string()).nonempty("One or more repositories required").default(["*"]) + includeRepos: z + .array(z.string()) + .nonempty("One or more repositories required") + .default(["*"]) + .describe(SecretScanningDataSources.CONFIG.GITHUB.includeRepos) }); export const GitHubDataSourceSchema = BaseSecretScanningDataSourceSchema({ diff --git a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-service.ts b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-service.ts index 178edbd51..230dde520 100644 --- a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-service.ts @@ -5,6 +5,8 @@ import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/se import { TSecretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; import { logger } from "@app/lib/logger"; +import { TGitHubDataSource } from "./github-secret-scanning-types"; + export const githubSecretScanningService = ( secretScanningV2DAL: TSecretScanningV2DALFactory, secretScanningV2Queue: Pick @@ -21,9 +23,12 @@ export const githubSecretScanningService = ( return; } - // scott: maybe add disabled col instead? - await secretScanningV2DAL.resources.delete({ - dataSourceId: dataSource.id + logger.info( + `secretScanningV2RemoveEvent: GitHub - installation deleted [installationId=${installationId}] [dataSourceId=${dataSource.id}]` + ); + + await secretScanningV2DAL.dataSources.updateById(dataSource.id, { + isDisconnected: true }); }; @@ -37,9 +42,10 @@ export const githubSecretScanningService = ( return; } - const dataSource = await secretScanningV2DAL.dataSources.findOne({ - externalId: String(installation.id) - }); + const dataSource = (await secretScanningV2DAL.dataSources.findOne({ + externalId: String(installation.id), + type: SecretScanningDataSource.GitHub + })) as TGitHubDataSource | undefined; if (!dataSource) { logger.error( @@ -48,15 +54,33 @@ export const githubSecretScanningService = ( return; } - await secretScanningV2Queue.queueResourceDiffScan({ - dataSourceType: SecretScanningDataSource.GitHub, - payload, - dataSourceId: dataSource.id - }); + const { + isAutoScanEnabled, + config: { includeRepos } + } = dataSource; + + if (!isAutoScanEnabled) { + logger.info( + `secretScanningV2PushEvent: GitHub - ignoring due to auto scan disabled [dataSourceId=${dataSource.id}] [installationId=${installation.id}]` + ); + return; + } + + if (includeRepos.includes("*") || includeRepos.includes(repository.full_name)) { + await secretScanningV2Queue.queueResourceDiffScan({ + dataSourceType: SecretScanningDataSource.GitHub, + payload, + dataSourceId: dataSource.id + }); + } else { + logger.info( + `secretScanningV2PushEvent: GitHub - ignoring due to repository not being present in config [installationId=${installation.id}] [dataSourceId=${dataSource.id}]` + ); + } }; return { handlePushEvent, - handleInstallationDeleted: handleInstallationDeletedEvent + handleInstallationDeletedEvent }; }; diff --git a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-types.ts b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-types.ts index e5e481fc8..90b910d44 100644 --- a/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-types.ts +++ b/backend/src/ee/services/secret-scanning-v2/github/github-secret-scanning-types.ts @@ -27,4 +27,6 @@ export type TQueueGitHubResourceDiffScan = { dataSourceType: SecretScanningDataSource.GitHub; payload: PushEvent; dataSourceId: string; + resourceId: string; + scanId: string; }; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts index ac6bcd5b8..b22eb414d 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts @@ -163,7 +163,7 @@ export const secretScanningV2DALFactory = (db: TDbClient) => { }; const deleteDataSourceById = async (dataSourceId: string, tx?: Knex) => { - const secretRotation = (await baseSecretScanningDataSourceQuery({ + const dataSource = (await baseSecretScanningDataSourceQuery({ filter: { id: dataSourceId }, db, tx @@ -171,18 +171,18 @@ export const secretScanningV2DALFactory = (db: TDbClient) => { await dataSourceOrm.deleteById(dataSourceId, tx); - return expandSecretScanningDataSource(secretRotation); + return expandSecretScanningDataSource(dataSource); }; const findOneDataSource = async (filter: Parameters<(typeof dataSourceOrm)["findOne"]>[0], tx?: Knex) => { try { - const secretRotation = await baseSecretScanningDataSourceQuery({ filter, db, tx }).first(); + const dataSource = await baseSecretScanningDataSourceQuery({ filter, db, tx }).first(); - if (secretRotation) { - return expandSecretScanningDataSource(secretRotation); + if (dataSource) { + return expandSecretScanningDataSource(dataSource); } } catch (error) { - throw new DatabaseError({ error, name: "Find One - Secret Rotation V2" }); + throw new DatabaseError({ error, name: "Find One - Secret Scanning Data Source" }); } }; @@ -264,7 +264,7 @@ export const secretScanningV2DALFactory = (db: TDbClient) => { }; }); } catch (error) { - throw new DatabaseError({ error, name: "Find Data Source with Details - Secret Scanning V2" }); + throw new DatabaseError({ error, name: "Find with Details - Secret Scanning Data Source" }); } }; @@ -348,7 +348,7 @@ export const secretScanningV2DALFactory = (db: TDbClient) => { }; }); } catch (error) { - throw new DatabaseError({ error, name: "Find Resource with Details - Secret Scanning V2" }); + throw new DatabaseError({ error, name: "Find with Details - Secret Scanning Resource" }); } }; @@ -411,7 +411,7 @@ export const secretScanningV2DALFactory = (db: TDbClient) => { }; }); } catch (error) { - throw new DatabaseError({ error, name: "Find Scan with Details By Data Source ID - Secret Scanning V2" }); + throw new DatabaseError({ error, name: "Find with Details By Data Source ID - Secret Scanning Scan" }); } }; @@ -429,7 +429,7 @@ export const secretScanningV2DALFactory = (db: TDbClient) => { return scans; } catch (error) { - throw new DatabaseError({ error, name: "Find Scan By Data Source ID - Secret Scanning V2" }); + throw new DatabaseError({ error, name: "Find By Data Source ID - Secret Scanning Scan" }); } }; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts index 5f0dc7286..22191f4f4 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts @@ -4,6 +4,7 @@ import { exec } from "child_process"; import { readFindingsFile } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns"; import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types"; import { GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/github"; +import { titleCaseToCamelCase } from "@app/lib/fn"; import { SecretScanningDataSource, SecretScanningFindingSeverity } from "./secret-scanning-v2-enums"; import { TCloneRepository, TGetFindingsPayload, TSecretScanningDataSourceListItem } from "./secret-scanning-v2-types"; @@ -42,27 +43,6 @@ export function scanDirectory(inputPath: string, outputPath: string): Promise { - if (typeof obj !== "object" || obj === null) { - return obj; - } - - if (Array.isArray(obj)) { - return obj.map((item: object) => titleCaseToCamelCase(item)); - } - - const result: Record = {}; - - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - const camelKey = key.charAt(0).toLowerCase() + key.slice(1); - result[camelKey] = titleCaseToCamelCase((obj as Record)[key]); - } - } - - return result; -}; - export const scanGitRepositoryAndGetFindings = async (scanPath: string, findingsPath: string): TGetFindingsPayload => { await scanDirectory(scanPath, findingsPath); diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts index 45920ecc7..0863797eb 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts @@ -1,5 +1,8 @@ import { join } from "path"; +import { ProjectMembershipRole } from "@app/db/schemas"; +import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { createTempFolder, deleteTempFolder @@ -8,12 +11,18 @@ import { parseScanErrorMessage, scanGitRepositoryAndGetFindings } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-fns"; +import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; +import { getConfig } from "@app/lib/config/env"; import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { decryptAppConnection } from "@app/services/app-connection/app-connection-fns"; import { TAppConnection } from "@app/services/app-connection/app-connection-types"; +import { ActorType } from "@app/services/auth/auth-type"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; +import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; +import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { TSecretScanningV2DALFactory } from "./secret-scanning-v2-dal"; import { @@ -28,16 +37,19 @@ import { TFindingsPayload, TQueueSecretScanningDataSourceFullScan, TQueueSecretScanningResourceDiffScan, + TQueueSecretScanningSendNotification, TSecretScanningDataSourceWithConnection } from "./secret-scanning-v2-types"; type TSecretRotationV2QueueServiceFactoryDep = { queueService: TQueueServiceFactory; secretScanningV2DAL: TSecretScanningV2DALFactory; - // smtpService: Pick; - // projectMembershipDAL: Pick; - // projectDAL: Pick; + smtpService: Pick; + projectMembershipDAL: Pick; + projectDAL: Pick; kmsService: Pick; + auditLogService: Pick; + keyStore: Pick; }; export type TSecretScanningV2QueueServiceFactory = Awaited>; @@ -45,10 +57,12 @@ export type TSecretScanningV2QueueServiceFactory = Awaited { const queueDataSourceFullScan = async ( dataSource: TSecretScanningDataSourceWithConnection, @@ -63,7 +77,7 @@ export const secretScanningV2QueueServiceFactory = async ({ let filteredRawResources = rawResources; - // TODO: should add indivial resource fetch to factory + // TODO: should add individual resource fetch to factory if (resourceExternalId) { filteredRawResources = rawResources.filter((resource) => resource.externalId === resourceExternalId); } @@ -74,6 +88,13 @@ export const secretScanningV2QueueServiceFactory = async ({ }); } + for (const resource of filteredRawResources) { + // eslint-disable-next-line no-await-in-loop + if (await keyStore.getItem(KeyStorePrefixes.SecretScanningLock(dataSource.id, resource.externalId))) { + throw new BadRequestError({ message: `A scan is already in progress for resource "${resource.name}"` }); + } + } + await secretScanningV2DAL.resources.transaction(async (tx) => { const resources = await secretScanningV2DAL.resources.upsert( filteredRawResources.map((rawResource) => ({ @@ -110,9 +131,6 @@ export const secretScanningV2QueueServiceFactory = async ({ } }; - const queueResourceDiffScan = async (payload: TQueueSecretScanningResourceDiffScan) => - queueService.queuePg(QueueJobs.SecretScanningV2DiffScan, payload); - await queueService.startPg( QueueJobs.SecretScanningV2FullScan, async ([job]) => { @@ -123,7 +141,26 @@ export const secretScanningV2QueueServiceFactory = async ({ const tempFolder = await createTempFolder(); + const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); + + if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); + + const resource = await secretScanningV2DAL.resources.findById(resourceId); + + if (!resource) throw new Error(`Resource with ID "${resourceId}" not found`); + + let lock: Awaited> | undefined; + try { + try { + lock = await keyStore.acquireLock( + [KeyStorePrefixes.SecretScanningLock(dataSource.id, resource.externalId)], + 60 * 1000 * 5 + ); + } catch (e) { + throw new Error("Failed to acquire scanning lock."); + } + await secretScanningV2DAL.scans.update( { id: scanId }, { @@ -131,14 +168,6 @@ export const secretScanningV2QueueServiceFactory = async ({ } ); - const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); - - if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); - - const resource = await secretScanningV2DAL.resources.findById(resourceId); - - if (!resource) throw new Error(`Resource with ID "${resourceId}" not found`); - let connection: TAppConnection | null = null; if (dataSource.connection) connection = await decryptAppConnection(dataSource.connection, kmsService); @@ -165,110 +194,196 @@ export const secretScanningV2QueueServiceFactory = async ({ throw new Error("Unhandled resource type"); } - await secretScanningV2DAL.findings.transaction(async (tx) => { - await secretScanningV2DAL.findings.upsert( - findingsPayload.map((findings) => ({ - ...findings, - projectId: dataSource.projectId, - dataSourceName: dataSource.name, + if (findingsPayload.length) { + await secretScanningV2DAL.findings.transaction(async (tx) => { + await secretScanningV2DAL.findings.upsert( + findingsPayload.map((findings) => ({ + ...findings, + projectId: dataSource.projectId, + dataSourceName: dataSource.name, + dataSourceType: dataSource.type, + resourceName: resource.name, + resourceType: resource.type, + scanId, + status: SecretScanningFindingStatus.Unresolved + })), + ["projectId", "fingerprint"], + tx, + ["resourceName", "dataSourceName", "status"] + ); + + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Completed, + statusMessage: null + } + ); + }); + + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { + status: SecretScanningScanStatus.Completed, + resourceName: resource.name, + isDiffScan: false, + dataSource, + numberOfSecrets: findingsPayload.length, + scanId + }); + } + + await auditLogService.createAuditLog({ + projectId: dataSource.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + metadata: { + dataSourceId: dataSource.id, dataSourceType: dataSource.type, - resourceName: resource.name, + resourceId: resource.id, resourceType: resource.type, scanId, - status: SecretScanningFindingStatus.Unresolved - })), - ["projectId", "fingerprint"], - tx, - ["resourceName", "dataSourceName", "status"] - ); + scanStatus: SecretScanningScanStatus.Completed, + scanType: SecretScanningScanType.FullScan, + numberOfSecretsDetected: findingsPayload.length + } + } + }); + + logger.info(`secretScanningV2Queue: Full Scan Complete ${logDetails} findings=[${findingsPayload.length}]`); + } catch (error) { + if (retryCount === retryLimit) { + const errorMessage = parseScanErrorMessage(error); await secretScanningV2DAL.scans.update( { id: scanId }, { - status: SecretScanningScanStatus.Completed + status: SecretScanningScanStatus.Failed, + statusMessage: errorMessage } ); - }); - // TODO: send notification - - logger.info(`secretScanningV2Queue: Full Scan Complete ${logDetails}`); - } catch (error) { - await secretScanningV2DAL.scans.update( - { id: scanId }, - { + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { status: SecretScanningScanStatus.Failed, - statusMessage: parseScanErrorMessage(error) - } - ); + resourceName: resource.name, + dataSource, + errorMessage + }); - // TODO: send error notification + await auditLogService.createAuditLog({ + projectId: dataSource.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + metadata: { + dataSourceId: dataSource.id, + dataSourceType: dataSource.type, + resourceId: resource.id, + resourceType: resource.type, + scanId, + scanStatus: SecretScanningScanStatus.Failed, + scanType: SecretScanningScanType.FullScan + } + } + }); + } logger.error(error, `secretScanningV2Queue: Full Scan Failed ${logDetails}`); throw error; } finally { await deleteTempFolder(tempFolder); + await lock?.release(); } }, { batchSize: 1, - workerCount: 2, + workerCount: 20, pollingIntervalSeconds: 1 } ); + const queueResourceDiffScan = async ({ + payload, + dataSourceId, + dataSourceType + }: Pick) => { + const factory = SECRET_SCANNING_FACTORY_MAP[dataSourceType as SecretScanningDataSource](); + + const resourcePayload = factory.getDiffScanResourcePayload(payload); + + try { + const { resourceId, scanId } = await secretScanningV2DAL.resources.transaction(async (tx) => { + const [resource] = await secretScanningV2DAL.resources.upsert( + [ + { + ...resourcePayload, + dataSourceId + } + ], + ["externalId", "dataSourceId"], + tx + ); + + const scan = await secretScanningV2DAL.scans.create( + { + resourceId: resource.id, + type: SecretScanningScanType.DiffScan + }, + tx + ); + + return { + resourceId: resource.id, + scanId: scan.id + }; + }); + + await queueService.queuePg(QueueJobs.SecretScanningV2DiffScan, { + payload, + dataSourceId, + dataSourceType, + scanId, + resourceId + }); + } catch (error) { + logger.error( + error, + `secretScanningV2Queue: Failed to queue diff scan [dataSourceId=${dataSourceId}] [resourceExternalId=${resourcePayload.externalId}]` + ); + } + }; + await queueService.startPg( QueueJobs.SecretScanningV2DiffScan, async ([job]) => { - const { payload, dataSourceId } = job.data as TQueueSecretScanningResourceDiffScan; + const { payload, dataSourceId, resourceId, scanId } = job.data as TQueueSecretScanningResourceDiffScan; const { retryCount, retryLimit } = job; - let scanId: string | undefined; - let logDetails = `[dataSourceId=${dataSourceId}] [jobId=${job.id}] retryCount=[${retryCount}/${retryLimit}]`; + const logDetails = `[dataSourceId=${dataSourceId}] [scanId=${scanId}] [resourceId=${resourceId}] [jobId=${job.id}] retryCount=[${retryCount}/${retryLimit}]`; + + const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); + + if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); + + const resource = await secretScanningV2DAL.resources.findById(resourceId); + + if (!resource) throw new Error(`Resource with ID "${resourceId}" not found`); + + const factory = SECRET_SCANNING_FACTORY_MAP[dataSource.type as SecretScanningDataSource](); try { - const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); - - if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); - - const factory = SECRET_SCANNING_FACTORY_MAP[dataSource.type as SecretScanningDataSource](); - - const resourcePayload = factory.getDiffScanResourcePayload(payload); - - const { resourceId, resourceName, resourceType } = await secretScanningV2DAL.resources.transaction( - async (tx) => { - const [resource] = await secretScanningV2DAL.resources.upsert( - [ - { - ...resourcePayload, - dataSourceId - } - ], - ["externalId", "dataSourceId"], - tx - ); - - const scan = await secretScanningV2DAL.scans.create( - { - resourceId: resource.id, - type: SecretScanningScanType.DiffScan, - status: SecretScanningScanStatus.Scanning - }, - tx - ); - - scanId = scan.id; - - return { - resourceId: resource.id, - resourceName: resource.name, - resourceType: resource.type - }; + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Scanning } ); - logDetails += ` [scanId=${scanId}] [resourceId=${resourceId}]`; - let connection: TAppConnection | null = null; if (dataSource.connection) connection = await decryptAppConnection(dataSource.connection, kmsService); @@ -277,49 +392,107 @@ export const secretScanningV2QueueServiceFactory = async ({ ...dataSource, connection } as TSecretScanningDataSourceWithConnection, - resourceName, + resourceName: resource.name, payload }); - await secretScanningV2DAL.findings.transaction(async (tx) => { - await secretScanningV2DAL.findings.upsert( - findingsPayload.map((findings) => ({ - ...findings, - projectId: dataSource.projectId, - dataSourceName: dataSource.name, + if (findingsPayload.length) { + await secretScanningV2DAL.findings.transaction(async (tx) => { + await secretScanningV2DAL.findings.upsert( + findingsPayload.map((findings) => ({ + ...findings, + projectId: dataSource.projectId, + dataSourceName: dataSource.name, + dataSourceType: dataSource.type, + resourceName: resource.name, + resourceType: resource.type, + scanId, + status: SecretScanningFindingStatus.Unresolved + })), + ["projectId", "fingerprint"], + tx, + ["resourceName", "dataSourceName", "status"] + ); + + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Completed + } + ); + }); + + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { + status: SecretScanningScanStatus.Completed, + resourceName: resource.name, + isDiffScan: true, + dataSource, + numberOfSecrets: findingsPayload.length, + scanId + }); + } + + await auditLogService.createAuditLog({ + projectId: dataSource.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + metadata: { + dataSourceId: dataSource.id, dataSourceType: dataSource.type, - resourceName, - resourceType, + resourceId, + resourceType: resource.type, scanId, - status: SecretScanningFindingStatus.Unresolved - })), - ["projectId", "fingerprint"], - tx, - ["resourceName", "dataSourceName", "status"] - ); - - await secretScanningV2DAL.scans.update( - { id: scanId }, - { - status: SecretScanningScanStatus.Completed + scanStatus: SecretScanningScanStatus.Completed, + scanType: SecretScanningScanType.DiffScan, + numberOfSecretsDetected: findingsPayload.length } - ); + } }); - // TODO: send notification - logger.info(`secretScanningV2Queue: Diff Scan Complete ${logDetails}`); } catch (error) { - if (scanId) + if (retryCount === retryLimit) { + const errorMessage = parseScanErrorMessage(error); + await secretScanningV2DAL.scans.update( { id: scanId }, { status: SecretScanningScanStatus.Failed, - statusMessage: parseScanErrorMessage(error) + statusMessage: errorMessage } ); - // TODO: send error notification + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { + status: SecretScanningScanStatus.Failed, + resourceName: resource.name, + dataSource, + errorMessage + }); + + await auditLogService.createAuditLog({ + projectId: dataSource.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + metadata: { + dataSourceId: dataSource.id, + dataSourceType: dataSource.type, + resourceId: resource.id, + resourceType: resource.type, + scanId, + scanStatus: SecretScanningScanStatus.Failed, + scanType: SecretScanningScanType.DiffScan + } + } + }); + } logger.error(error, `secretScanningV2Queue: Diff Scan Failed ${logDetails}`); throw error; @@ -327,67 +500,84 @@ export const secretScanningV2QueueServiceFactory = async ({ }, { batchSize: 1, - workerCount: 2, + workerCount: 20, pollingIntervalSeconds: 1 } ); - // await queueService.startPg( - // QueueJobs.SecretRotationV2SendNotification, - // async ([job]) => { - // const { secretRotation } = job.data as TSecretRotationSendNotificationJobPayload; - // try { - // const { - // name: rotationName, - // type, - // projectId, - // lastRotationAttemptedAt, - // folder, - // environment, - // id: dataSourceId - // } = secretRotation; - // - // logger.info(`secretRotationV2Queue: Sending Status Notification [dataSourceId=${dataSourceId}]`); - // - // const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); - // const project = await projectDAL.findById(projectId); - // - // const projectAdmins = projectMembers.filter((member) => - // member.roles.some((role) => role.role === ProjectMembershipRole.Admin) - // ); - // - // const rotationType = SECRET_ROTATION_NAME_MAP[type as SecretRotation]; - // - // await smtpService.sendMail({ - // recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), - // template: SmtpTemplates.SecretRotationFailed, - // subjectLine: `Secret Rotation Failed`, - // substitutions: { - // rotationName, - // rotationType, - // content: `Your ${rotationType} Rotation failed to rotate during it's scheduled rotation. The last rotation attempt occurred at ${new Date( - // lastRotationAttemptedAt - // ).toISOString()}. Please check the rotation status in Infisical for more details.`, - // secretPath: folder.path, - // environment: environment.name, - // projectName: project.name, - // rotationUrl: encodeURI(`${appCfg.SITE_URL}/secret-manager/${projectId}/secrets/${environment.slug}`) - // } - // }); - // } catch (error) { - // logger.error( - // error, - // `secretRotationV2Queue: Failed to Send Status Notification [dataSourceId=${secretRotation.id}]` - // ); - // throw error; - // } - // }, - // { - // batchSize: 1, - // workerCount: 2, - // pollingIntervalSeconds: 1 - // } - // ); + await queueService.startPg( + QueueJobs.SecretScanningV2SendNotification, + async ([job]) => { + const { dataSource, resourceName, ...payload } = job.data as TQueueSecretScanningSendNotification; + + const appCfg = getConfig(); + + if (!appCfg.isSmtpConfigured) return; + + try { + const { projectId } = dataSource; + + logger.info( + `secretScanningV2Queue: Sending Status Notification [dataSourceId=${dataSource.id}] [resourceName=${resourceName}] [status=${payload.status}]` + ); + + const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); + const project = await projectDAL.findById(projectId); + + const projectAdmins = projectMembers.filter((member) => + member.roles.some((role) => role.role === ProjectMembershipRole.Admin) + ); + + const timestamp = new Date().toISOString(); + + await smtpService.sendMail({ + recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), + template: + payload.status === SecretScanningScanStatus.Completed + ? SmtpTemplates.SecretScanningV2SecretsDetected + : SmtpTemplates.SecretScanningV2ScanFailed, + subjectLine: + payload.status === SecretScanningScanStatus.Completed + ? "Incident Alert: Secret(s) Leaked" + : `Secret Scanning Failed`, + substitutions: + payload.status === SecretScanningScanStatus.Completed + ? { + authorName: "Jim", + authorEmail: "jim@infisical.com", + resourceName, + numberOfSecrets: payload.numberOfSecrets, + isDiffScan: payload.isDiffScan, + url: encodeURI( + `${appCfg.SITE_URL}/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}` + ), + timestamp + } + : { + dataSourceName: dataSource.name, + resourceName, + projectName: project.name, + timestamp, + errorMessage: payload.errorMessage, + url: encodeURI( + `${appCfg.SITE_URL}/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}` + ) + } + }); + } catch (error) { + logger.error( + error, + `secretScanningV2Queue: Failed to Send Status Notification [dataSourceId=${dataSource.id}] [resourceName=${resourceName}] [status=${payload.status}]` + ); + throw error; + } + }, + { + batchSize: 1, + workerCount: 5, + pollingIntervalSeconds: 1 + } + ); return { queueDataSourceFullScan, diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts index d6a9de25e..650f46536 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts @@ -49,10 +49,6 @@ export type TSecretScanningV2ServiceFactoryDep = { appConnectionService: Pick; permissionService: Pick; licenseService: Pick; - // auditLogService: Pick; - // keyStore: Pick; - // queueService: Pick; - // appConnectionDAL: Pick; secretScanningV2Queue: Pick< TSecretScanningV2QueueServiceFactory, "queueDataSourceFullScan" | "queueResourceDiffScan" @@ -67,10 +63,6 @@ export const secretScanningV2ServiceFactory = ({ permissionService, appConnectionService, licenseService, - // auditLogService, - // keyStore, - // queueService, - // appConnectionDAL, secretScanningV2Queue, kmsService }: TSecretScanningV2ServiceFactoryDep) => { @@ -374,7 +366,7 @@ export const secretScanningV2ServiceFactory = ({ } }; - const deleteSecretScanningResource = async ( + const deleteSecretScanningDataSource = async ( { type, dataSourceId }: TDeleteSecretScanningDataSourceDTO, actor: OrgServiceActor ) => { @@ -750,7 +742,7 @@ export const secretScanningV2ServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretScanningFindingActions.Resolve, + ProjectPermissionSecretScanningFindingActions.Update, ProjectPermissionSub.SecretScanningFindings ); @@ -770,7 +762,7 @@ export const secretScanningV2ServiceFactory = ({ findSecretScanningDataSourceByName, createSecretScanningDataSource, updateSecretScanningDataSource, - deleteSecretScanningResource, + deleteSecretScanningDataSource, triggerSecretScanningDataSourceScan, listSecretScanningResourcesByDataSourceId, listSecretScanningScansByDataSourceId, diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts index 6480a3899..d501fe0e5 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts @@ -1,4 +1,9 @@ -import { TSecretScanningFindingsInsert, TSecretScanningResources, TSecretScanningScans } from "@app/db/schemas"; +import { + TSecretScanningDataSources, + TSecretScanningFindingsInsert, + TSecretScanningResources, + TSecretScanningScans +} from "@app/db/schemas"; import { TGitHubDataSource, TGitHubDataSourceInput, @@ -96,6 +101,14 @@ export type TQueueSecretScanningDataSourceFullScan = { export type TQueueSecretScanningResourceDiffScan = TQueueGitHubResourceDiffScan; +export type TQueueSecretScanningSendNotification = { + dataSource: TSecretScanningDataSources; + resourceName: string; +} & ( + | { status: SecretScanningScanStatus.Failed; errorMessage: string } + | { status: SecretScanningScanStatus.Completed; numberOfSecrets: number; scanId: string; isDiffScan: boolean } +); + export type TCloneRepository = { cloneUrl: string; repoPath: string; @@ -163,7 +176,7 @@ export type TFindingsPayload = Pick; export type TUpdateSecretScanningFinding = { - status: SecretScanningFindingStatus; + status?: SecretScanningFindingStatus; remarks?: string | null; findingId: string; }; diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts index 6da6c4fa4..94015bc31 100644 --- a/backend/src/keystore/keystore.ts +++ b/backend/src/keystore/keystore.ts @@ -37,6 +37,8 @@ export const KeyStorePrefixes = { `sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const, SecretSyncLock: (syncId: string) => `secret-sync-mutex-${syncId}` as const, SecretRotationLock: (rotationId: string) => `secret-rotation-v2-mutex-${rotationId}` as const, + SecretScanningLock: (dataSourceId: string, resourceExternalId: string) => + `secret-scanning-v2-mutex-${dataSourceId}-${resourceExternalId}` as const, SecretSyncLastRunTimestamp: (syncId: string) => `secret-sync-last-run-${syncId}` as const, IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) => `identity-access-token-status:${identityAccessTokenId}`, diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index e2fa70f3c..a1d529b82 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2408,7 +2408,12 @@ export const SecretScanningDataSources = { }), LIST_SCANS: (type: SecretScanningDataSource) => ({ dataSourceId: `The ID of the ${SECRET_SCANNING_DATA_SOURCE_NAME_MAP[type]} Data Source to list scans for.` - }) + }), + CONFIG: { + GITHUB: { + includeRepos: 'The repositories to include when scanning. Defaults to all repositories (["*"]).' + } + } }; export const SecretScanningFindings = { @@ -2416,8 +2421,8 @@ export const SecretScanningFindings = { projectId: `The ID of the project to list Secret Scanning Findings from.` }, UPDATE: { - findingId: "The ID of the Secret Scanning Finding to update the resolve status for.", + findingId: "The ID of the Secret Scanning Finding to update.", status: "The updated status of the specified Secret Scanning Finding.", - remarks: "Remarks pertaining to the resolve status of this finding." + remarks: "Remarks pertaining to the status of this finding." } }; diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 161fef9db..41a864679 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -281,6 +281,13 @@ const envSchema = z Boolean(data.SECRET_SCANNING_GIT_APP_ID) && Boolean(data.SECRET_SCANNING_PRIVATE_KEY) && Boolean(data.SECRET_SCANNING_WEBHOOK_SECRET), + isSecretScanningV2Configured: + Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_ID) && + Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_PRIVATE_KEY) && + Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_SLUG) && + Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_ID) && + Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_SECRET) && + Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET), isHsmConfigured: Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined, diff --git a/backend/src/lib/fn/object.ts b/backend/src/lib/fn/object.ts index 87db80343..65d0b7859 100644 --- a/backend/src/lib/fn/object.ts +++ b/backend/src/lib/fn/object.ts @@ -32,3 +32,24 @@ export const shake = ( return acc; }, {} as T); }; + +export const titleCaseToCamelCase = (obj: unknown): unknown => { + if (typeof obj !== "object" || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item: object) => titleCaseToCamelCase(item)); + } + + const result: Record = {}; + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const camelKey = key.charAt(0).toLowerCase() + key.slice(1); + result[camelKey] = titleCaseToCamelCase((obj as Record)[key]); + } + } + + return result; +}; diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index bf70571af..8b0098134 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -14,7 +14,8 @@ import { } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types"; import { TQueueSecretScanningDataSourceFullScan, - TQueueSecretScanningResourceDiffScan + TQueueSecretScanningResourceDiffScan, + TQueueSecretScanningSendNotification } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; @@ -91,7 +92,8 @@ export enum QueueJobs { SecretRotationV2SendNotification = "secret-rotation-v2-send-notification", InvalidateCache = "invalidate-cache", SecretScanningV2FullScan = "secret-scanning-v2-full-scan", - SecretScanningV2DiffScan = "secret-scanning-v2-diff-scan" + SecretScanningV2DiffScan = "secret-scanning-v2-diff-scan", + SecretScanningV2SendNotification = "secret-scanning-v2-notification" } export type TQueueJobTypes = { @@ -260,6 +262,10 @@ export type TQueueJobTypes = { | { name: QueueJobs.SecretScanningV2DiffScan; payload: TQueueSecretScanningResourceDiffScan; + } + | { + name: QueueJobs.SecretScanningV2SendNotification; + payload: TQueueSecretScanningSendNotification; }; }; diff --git a/backend/src/server/plugins/secret-scanner-v2.ts b/backend/src/server/plugins/secret-scanner-v2.ts index 6835309b6..466450180 100644 --- a/backend/src/server/plugins/secret-scanner-v2.ts +++ b/backend/src/server/plugins/secret-scanner-v2.ts @@ -12,7 +12,7 @@ export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvide const { payload } = context; const { installation } = payload; - await server.services.secretScanningV2.github.handleInstallationDeleted(installation.id); + await server.services.secretScanningV2.github.handleInstallationDeletedEvent(installation.id); }); app.on("installation", async (context) => { @@ -28,6 +28,11 @@ export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvide const appCfg = getConfig(); + if (!appCfg.isSecretScanningV2Configured) { + logger.info("Secret Scanning V2 is not configured. Skipping registration of secret scanning v2 webhooks."); + return; + } + const probot = new Probot({ appId: appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_ID as string, privateKey: appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_PRIVATE_KEY as string, @@ -36,6 +41,7 @@ export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvide await probot.load(probotApp); + // github push event webhook server.route({ method: "POST", url: "/github", diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index caa0587b5..5f0a45144 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1703,22 +1703,20 @@ export const registerRoutes = async ( }); const secretScanningV2Queue = await secretScanningV2QueueServiceFactory({ + auditLogService, secretScanningV2DAL, queueService, - // projectDAL, - // projectMembershipDAL, - // smtpService, - kmsService + projectDAL, + projectMembershipDAL, + smtpService, + kmsService, + keyStore }); const secretScanningV2Service = secretScanningV2ServiceFactory({ - // appConnectionDAL, permissionService, appConnectionService, licenseService, - // auditLogService, - // keyStore, - // queueService, secretScanningV2DAL, secretScanningV2Queue, kmsService diff --git a/backend/src/services/smtp/emails/SecretScanningScanFailedTemplate.tsx b/backend/src/services/smtp/emails/SecretScanningScanFailedTemplate.tsx new file mode 100644 index 000000000..2e212cb82 --- /dev/null +++ b/backend/src/services/smtp/emails/SecretScanningScanFailedTemplate.tsx @@ -0,0 +1,67 @@ +import { Button, Heading, Section, Text } from "@react-email/components"; +import React from "react"; + +import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper"; + +interface SecretScanningScanFailedTemplateProps extends Omit { + dataSourceName: string; + resourceName: string; + projectName: string; + timestamp: string; + url: string; + errorMessage: string; +} + +export const SecretScanningScanFailedTemplate = ({ + dataSourceName, + resourceName, + projectName, + siteUrl, + errorMessage, + url, + timestamp +}: SecretScanningScanFailedTemplateProps) => { + return ( + + + Infisical encountered an error while attempting to scan the resource {resourceName} + +
+ Resource + {resourceName} + Data Source + {dataSourceName} + Project + {projectName} + Timestamp + {timestamp} + Error + {errorMessage} +
+
+ +
+
+ ); +}; + +export default SecretScanningScanFailedTemplate; + +SecretScanningScanFailedTemplate.PreviewProps = { + dataSourceName: "my-data-source", + resourceName: "my-resource", + projectName: "my-project", + timestamp: "May 3rd 2025, 5:42 pm", + url: "https://infisical.com", + errorMessage: "401 Unauthorized", + siteUrl: "https://infisical.com" +} as SecretScanningScanFailedTemplateProps; diff --git a/backend/src/services/smtp/emails/SecretScanningSecretsDetectedTemplate.tsx b/backend/src/services/smtp/emails/SecretScanningSecretsDetectedTemplate.tsx new file mode 100644 index 000000000..b7c0d8a14 --- /dev/null +++ b/backend/src/services/smtp/emails/SecretScanningSecretsDetectedTemplate.tsx @@ -0,0 +1,101 @@ +import { Button, Heading, Link, Section, Text } from "@react-email/components"; +import React from "react"; + +import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper"; + +interface SecretScanningSecretsDetectedTemplateProps + extends Omit { + numberOfSecrets: number; + isDiffScan: boolean; + authorName?: string; + authorEmail?: string; + resourceName: string; + url: string; +} + +export const SecretScanningSecretsDetectedTemplate = ({ + numberOfSecrets, + siteUrl, + authorName, + authorEmail, + isDiffScan, + resourceName, + url +}: SecretScanningSecretsDetectedTemplateProps) => { + return ( + + + Infisical has uncovered {numberOfSecrets} secret(s) + {isDiffScan ? " from a recent commit to" : " in"} {resourceName} + +
+ + You are receiving this notification because one or more leaked secrets have been detected + {isDiffScan && " in a recent commit"} + {isDiffScan ? ( + (authorName || authorEmail) && ( + <> + {" "} + pushed by {authorName ?? "Unknown Pusher"}{" "} + {authorEmail && ( + <> + ( + + {authorEmail} + + ) + + )} + + ) + ) : ( + <> + {" "} + in your resource {resourceName} + + )} + . + + + If these are test secrets, please add `infisical-scan:ignore` at the end of the line containing the secret as + a comment in the given programming language. This will prevent future notifications from being sent out for + these secrets. + + + If these are production secrets, please rotate them immediately. + + + Once you have taken action, be sure to update the finding status in the{" "} + + Infisical Dashboard + + . + +
+
+ +
+
+ ); +}; + +export default SecretScanningSecretsDetectedTemplate; + +SecretScanningSecretsDetectedTemplate.PreviewProps = { + authorName: "Jim", + authorEmail: "jim@infisical.com", + resourceName: "my-resource", + numberOfSecrets: 3, + url: "https://infisical.com", + isDiffScan: true, + siteUrl: "https://infisical.com" +} as SecretScanningSecretsDetectedTemplateProps; diff --git a/backend/src/services/smtp/emails/index.ts b/backend/src/services/smtp/emails/index.ts index 29738dbb2..840a98cad 100644 --- a/backend/src/services/smtp/emails/index.ts +++ b/backend/src/services/smtp/emails/index.ts @@ -21,6 +21,8 @@ export * from "./SecretLeakIncidentTemplate"; export * from "./SecretReminderTemplate"; export * from "./SecretRequestCompletedTemplate"; export * from "./SecretRotationFailedTemplate"; +export * from "./SecretScanningScanFailedTemplate"; +export * from "./SecretScanningSecretsDetectedTemplate"; export * from "./SecretSyncFailedTemplate"; export * from "./ServiceTokenExpiryNoticeTemplate"; export * from "./SignupEmailVerificationTemplate"; diff --git a/backend/src/services/smtp/smtp-service.ts b/backend/src/services/smtp/smtp-service.ts index 12b38ebb2..ac56f0ee4 100644 --- a/backend/src/services/smtp/smtp-service.ts +++ b/backend/src/services/smtp/smtp-service.ts @@ -30,6 +30,8 @@ import { SecretReminderTemplate, SecretRequestCompletedTemplate, SecretRotationFailedTemplate, + SecretScanningScanFailedTemplate, + SecretScanningSecretsDetectedTemplate, SecretSyncFailedTemplate, ServiceTokenExpiryNoticeTemplate, SignupEmailVerificationTemplate, @@ -73,7 +75,9 @@ export enum SmtpTemplates { ProjectAccessRequest = "projectAccess", OrgAdminProjectDirectAccess = "orgAdminProjectGrantAccess", OrgAdminBreakglassAccess = "orgAdminBreakglassAccess", - ServiceTokenExpired = "serviceTokenExpired" + ServiceTokenExpired = "serviceTokenExpired", + SecretScanningV2ScanFailed = "secretScanningV2ScanFailed", + SecretScanningV2SecretsDetected = "secretScanningV2SecretsDetected" } export enum SmtpHost { @@ -113,7 +117,9 @@ const EmailTemplateMap: Record> = { [SmtpTemplates.SecretApprovalRequestNeedsReview]: SecretApprovalRequestNeedsReviewTemplate, [SmtpTemplates.ResetPassword]: PasswordResetTemplate, [SmtpTemplates.SetupPassword]: PasswordSetupTemplate, - [SmtpTemplates.PkiExpirationAlert]: PkiExpirationAlertTemplate + [SmtpTemplates.PkiExpirationAlert]: PkiExpirationAlertTemplate, + [SmtpTemplates.SecretScanningV2ScanFailed]: SecretScanningScanFailedTemplate, + [SmtpTemplates.SecretScanningV2SecretsDetected]: SecretScanningSecretsDetectedTemplate }; export const smtpServiceFactory = (cfg: TSmtpConfig) => { diff --git a/docs/documentation/platform/secret-scanning/github.mdx b/docs/documentation/platform/secret-scanning/github.mdx new file mode 100644 index 000000000..8cc179292 --- /dev/null +++ b/docs/documentation/platform/secret-scanning/github.mdx @@ -0,0 +1,91 @@ +import {Tabs} from "../../../../frontend/src/components/v2";## Prerequisites + +- Create a [GitHub Radar Connection](/integrations/app-connections/github-radar) + +## Create a GitHub Data Source in Infisical + + + + 1. Navigate to your Secret Scanning Project's Dashboard and click the **Add Data Source** button. + ![Secret Scanning Dashboard](/images/platform/secret-scanning/github/github-data-source-step-1.png) + + 2. Select the **GitHub** option. + ![Select GitHub Option](/images/platform/secret-scanning/github/github-data-source-step-2.png) + + 3. Select the **GitHub Radar Connection** to use and configure which repositories you would like to scan. Then click **Next**. + ![Data Source Configuration](/images/platform/secret-scanning/github/github-data-source-step-3.png) + + - **GitHub Radar Connection** - the connection that has access to the repositories you want to scan. + - **Scan Repositories** - select which repositories you would like to scan. + - **All Repositories** - Infisical will scan all repositories associated with your connection. + - **Select Repositories** - Infisical will scan the selected repositories. + - **Auto-Scan Enabled** - whether Infisical should automatically perform a scan when a push is made to configured repositories. + + 4. Give your data source a name and description (optional). Then click **Next**. + ![Data Source Details](/images/platform/secret-scanning/github/github-data-source-step-4.png) + + - **Name** - the name of the data source. Must be slug-friendly. + - **Description** (optional) - a description of this rotation configuration. + + 5. Review your data source, then click **Create Data Source**. + ![Data Source Review](/images/platform/secret-scanning/github/github-data-source-step-5.png) + + 6. Your **GitHub Data Source** is now available and will begin a full scan if **Auto-Scan** is enabled. + ![Data Source Created](/images/platform/secret-scanning/github/github-data-source-step-6.png) + + 7. You can view repositories and scan results by clicking on your data source. + ![Data Source Page](/images/platform/secret-scanning/github/github-data-source-step-7.png) + + 8. In addition, you can review any findings from the **Findings Page**. + ![Findings Page](/images/platform/secret-scanning/github/github-data-source-step-8.png) + + + To create a GitHub Data Source, make an API request to the [Create GitHub Data Source](/api-reference/endpoints/secret-scanning/data-sources/github/create) API endpoint. + + ### Sample request + + ```bash Request + curl --request POST \ + --url https://us.infisical.com/api/v2/secret-scanning/data-sources/github \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "my-github-source", + "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "description": "my github data source", + "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "isAutoScanEnabled": true, + "rotationInterval": 30, + "config": { + "includeRepos": ["*"], + } + }' + ``` + + ### Sample response + + ```bash Response + { + "dataSource": { + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "externalId": "1234567890", + "name": "my-github-source", + "description": "my github data source", + "isAutoScanEnabled": true, + "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "createdAt": "2023-11-07T05:31:56Z", + "updatedAt": "2023-11-07T05:31:56Z", + "type": "github", + "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "connection": { + "app": "github-radar", + "name": "my-radar-app", + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" + }, + "config": { + "includeRepos": ["*"] + } + } + } + ``` + + diff --git a/docs/documentation/platform/secret-scanning/overview.mdx b/docs/documentation/platform/secret-scanning/overview.mdx index 81d1b6171..936b5e4c4 100644 --- a/docs/documentation/platform/secret-scanning/overview.mdx +++ b/docs/documentation/platform/secret-scanning/overview.mdx @@ -29,7 +29,7 @@ Data sources are configured integrations with external platforms, such as a GitH A data source acts as a secure intermediary between the external system and the scanner engine. It manages a collection of scannable resources (such as repositories) and handles the authentication and communication required for scanning operations. -[data source page image] +![data sources](/images/platform/secret-scanning/secret-scanning-data-sources.png) ### Resources @@ -37,15 +37,13 @@ Resources are the atomic, scannable units, such as a repository, that can be mon Each resource maintains its own scanning history and status, allowing for granular monitoring and management of secret scanning across your organization. -[resource table image] +![resources](/images/platform/secret-scanning/secret-scanning-resources.png) ### Scans Scans can be initiated in two ways: -1. **Full Scan** - Manually triggered scan that comprehensively checks either: - - All resources associated with a data source - - A single selected resource +1. **Full Scan** - Manually triggered scan that comprehensively checks either all resources associated with a data source or a single selected resource. 2. **Diff Scan** - Automatically executed when **Auto-Scan** is enabled on a data source. This scan type specifically focuses on updates to existing resources. @@ -55,42 +53,23 @@ All scan activities can be monitored in real-time through the Infisical UI, whic - Resource(s) being scanned - Detection results (whether any secrets were found) -[scan table image] +![scans](/images/platform/secret-scanning/secret-scanning-scans.png) -## [In Progress - old below] +### Findings -## Code Scanning +Findings are automatically generated when secret leaks are detected during scanning operations. Each finding contains comprehensive information including: +- The specific scanning rule that identified the leak +- File location and line number where the secret was found +- Resource-specific details (e.g., commit hash and author for Git repositories) -![Scanning Overview](/images/platform/secret-scanning/overview.png) +Findings are initially marked as **Unresolved** and can be updated to one of the following statuses with additional remarks: +- **Resolved** - The issue has been addressed +- **False Positive** - The detection was incorrect +- **Ignore** - The finding can be safely disregarded -Secret scans are built on event-driven architecture. This means that every time a push is made to one of your selected repositories, Infisical will scan the modified files for any exposed secrets. +These status options help teams effectively track and manage the lifecycle of detected secret leaks. -If one or more exposed secrets are detected, it will be displayed in your Infisical dashboard. An exposed secret is known as a **"Risk"**. Each risk has the following data associated with it: -- **Date**: When the risk was first detected. -- **Secret Type**: Which type of secret was detected. -- **Info**: Information about the secret, such as the repository, file name, and the committer who made the change. - -Once an exposed secret is detected, all organization admins will be sent an e-mail notification containing details about the exposed secret. - - - Each risk also contains a "View Exposed Secret" button, which will take you directly to the GitHub commit and to the line where the secret was exposed. - - - - -![Exposed Secret](/images/platform/secret-scanning/exposed-secret.png) - - -## Responding to Exposed Secrets - -After an exposed secret is detected, it will be marked as `Needs Attention`. When there are risks marked as needs attention, it's important to address them as soon as possible. - -You can mark the risk as `Resolved` by changing the status to one of the following states: -- **This Is a False Positive**: The secret was not exposed, but was detected by the scanner. -- **I Have Rotated The Secret**: The secret was exposed, but it has now been removed. -- **No Rotation Needed**: You are choosing to ignore this risk. You may choose to do this if the risk is non-sensitive or otherwise not a security risk. - -![Needs Attention](/images/platform/secret-scanning/needs-attention.png) +![findings](/images/platform/secret-scanning/secret-scanning-findings.png) ## Ignoring Known Secrets If you're intentionally committing a test secret that the secret scanner might flag, you can instruct Infisical to overlook that secret with the methods listed below. diff --git a/docs/images/platform/secret-scanning/exposed-secret.png b/docs/images/platform/secret-scanning/exposed-secret.png deleted file mode 100644 index 727765292..000000000 Binary files a/docs/images/platform/secret-scanning/exposed-secret.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-app-copy-app-id.png b/docs/images/platform/secret-scanning/github-app-copy-app-id.png deleted file mode 100644 index a94cb5ece..000000000 Binary files a/docs/images/platform/secret-scanning/github-app-copy-app-id.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-app-copy-slug.png b/docs/images/platform/secret-scanning/github-app-copy-slug.png deleted file mode 100644 index c555dcd41..000000000 Binary files a/docs/images/platform/secret-scanning/github-app-copy-slug.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-app-create-private-key.png b/docs/images/platform/secret-scanning/github-app-create-private-key.png deleted file mode 100644 index 50f602a36..000000000 Binary files a/docs/images/platform/secret-scanning/github-app-create-private-key.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-configure-app.png b/docs/images/platform/secret-scanning/github-configure-app.png deleted file mode 100644 index df64eeb18..000000000 Binary files a/docs/images/platform/secret-scanning/github-configure-app.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-create-app-button.png b/docs/images/platform/secret-scanning/github-create-app-button.png deleted file mode 100644 index 3ea4b2d38..000000000 Binary files a/docs/images/platform/secret-scanning/github-create-app-button.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-create-app.png b/docs/images/platform/secret-scanning/github-create-app.png deleted file mode 100644 index f4d1cdb8c..000000000 Binary files a/docs/images/platform/secret-scanning/github-create-app.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-register-app.png b/docs/images/platform/secret-scanning/github-register-app.png deleted file mode 100644 index 904c07bf2..000000000 Binary files a/docs/images/platform/secret-scanning/github-register-app.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-repo-permissions.png b/docs/images/platform/secret-scanning/github-repo-permissions.png deleted file mode 100644 index 53eae9a41..000000000 Binary files a/docs/images/platform/secret-scanning/github-repo-permissions.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-select-org-2.png b/docs/images/platform/secret-scanning/github-select-org-2.png deleted file mode 100644 index 55b945c18..000000000 Binary files a/docs/images/platform/secret-scanning/github-select-org-2.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-select-org.png b/docs/images/platform/secret-scanning/github-select-org.png deleted file mode 100644 index 7d6e5abc5..000000000 Binary files a/docs/images/platform/secret-scanning/github-select-org.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-select-repos.png b/docs/images/platform/secret-scanning/github-select-repos.png deleted file mode 100644 index 51a6648d2..000000000 Binary files a/docs/images/platform/secret-scanning/github-select-repos.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github-subscribed-events.png b/docs/images/platform/secret-scanning/github-subscribed-events.png deleted file mode 100644 index 7aa6b431f..000000000 Binary files a/docs/images/platform/secret-scanning/github-subscribed-events.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-1.png b/docs/images/platform/secret-scanning/github/github-data-source-step-1.png new file mode 100644 index 000000000..62fc84459 Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-1.png differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-2.png b/docs/images/platform/secret-scanning/github/github-data-source-step-2.png new file mode 100644 index 000000000..5962406e4 Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-2.png differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-3.png b/docs/images/platform/secret-scanning/github/github-data-source-step-3.png new file mode 100644 index 000000000..d1a0e81a0 Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-3.png differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-4.png b/docs/images/platform/secret-scanning/github/github-data-source-step-4.png new file mode 100644 index 000000000..f46045250 Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-4.png differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-5.png b/docs/images/platform/secret-scanning/github/github-data-source-step-5.png new file mode 100644 index 000000000..9f0891888 Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-5.png differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-6.png b/docs/images/platform/secret-scanning/github/github-data-source-step-6.png new file mode 100644 index 000000000..886c0ca45 Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-6.png differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-7.png b/docs/images/platform/secret-scanning/github/github-data-source-step-7.png new file mode 100644 index 000000000..bc34c5fdb Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-7.png differ diff --git a/docs/images/platform/secret-scanning/github/github-data-source-step-8.png b/docs/images/platform/secret-scanning/github/github-data-source-step-8.png new file mode 100644 index 000000000..75b9f8175 Binary files /dev/null and b/docs/images/platform/secret-scanning/github/github-data-source-step-8.png differ diff --git a/docs/images/platform/secret-scanning/infisical-connect-secret-scanner.png b/docs/images/platform/secret-scanning/infisical-connect-secret-scanner.png deleted file mode 100644 index 11f24fd74..000000000 Binary files a/docs/images/platform/secret-scanning/infisical-connect-secret-scanner.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/needs-attention.png b/docs/images/platform/secret-scanning/needs-attention.png deleted file mode 100644 index 6ac664ead..000000000 Binary files a/docs/images/platform/secret-scanning/needs-attention.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/overview.png b/docs/images/platform/secret-scanning/overview.png deleted file mode 100644 index 19981fa11..000000000 Binary files a/docs/images/platform/secret-scanning/overview.png and /dev/null differ diff --git a/docs/images/platform/secret-scanning/secret-scanning-data-sources.png b/docs/images/platform/secret-scanning/secret-scanning-data-sources.png new file mode 100644 index 000000000..344178f54 Binary files /dev/null and b/docs/images/platform/secret-scanning/secret-scanning-data-sources.png differ diff --git a/docs/images/platform/secret-scanning/secret-scanning-findings.png b/docs/images/platform/secret-scanning/secret-scanning-findings.png new file mode 100644 index 000000000..2f0dde504 Binary files /dev/null and b/docs/images/platform/secret-scanning/secret-scanning-findings.png differ diff --git a/docs/images/platform/secret-scanning/secret-scanning-resources.png b/docs/images/platform/secret-scanning/secret-scanning-resources.png new file mode 100644 index 000000000..c47b252fc Binary files /dev/null and b/docs/images/platform/secret-scanning/secret-scanning-resources.png differ diff --git a/docs/images/platform/secret-scanning/secret-scanning-scans.png b/docs/images/platform/secret-scanning/secret-scanning-scans.png new file mode 100644 index 000000000..f710dbc20 Binary files /dev/null and b/docs/images/platform/secret-scanning/secret-scanning-scans.png differ diff --git a/docs/mint.json b/docs/mint.json index 8f488f0ec..46751cbab 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -245,7 +245,8 @@ { "group": "Secret Scanning", "pages": [ - "documentation/platform/secret-scanning/overview" + "documentation/platform/secret-scanning/overview", + "documentation/platform/secret-scanning/github" ] } ] diff --git a/frontend/public/lotties/blocks.json b/frontend/public/lotties/blocks.json new file mode 100644 index 000000000..93a6ad0cb --- /dev/null +++ b/frontend/public/lotties/blocks.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":60,"w":500,"h":500,"nm":"system-regular-40-add-card","ddd":0,"assets":[{"id":"comp_1","nm":"hover-add-card","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[354.165,145.831,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":1,"s":[{"i":[[11.506,0],[0,0],[0,-11.505],[0,0],[-11.506,0],[0,0],[0,11.506],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,11.506],[0,0],[11.506,0],[0,0],[0,-11.505]],"v":[[46.875,-67.709],[-46.875,-67.709],[-67.709,-46.875],[-67.709,46.875],[-46.875,67.709],[46.875,67.709],[67.709,46.875],[67.709,-46.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[11.506,0],[0,0],[0,-11.505],[0,0],[-11.506,0],[0,0],[0,11.506],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,11.506],[0,0],[11.506,0],[0,0],[0,-11.505]],"v":[[46.581,26.291],[-47.169,26.291],[-68.003,47.125],[-67.709,46.875],[-46.875,67.709],[46.875,67.709],[67.709,46.875],[67.415,47.125]],"c":true}]},{"t":18,"s":[{"i":[[11.506,0],[0,0],[0,0.034],[0,0],[-11.506,0],[0,0],[0,-0.034],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,-0.034],[0,0],[11.506,0],[0,0],[0,0.034]],"v":[[46.581,67.83],[-47.169,67.83],[-68.003,67.769],[-67.709,67.77],[-46.875,67.709],[46.875,67.709],[67.709,67.77],[67.415,67.769]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":18,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[0]},{"t":35,"s":[90]}],"ix":10},"p":{"a":0,"k":[354.171,354.168,0],"ix":2,"l":2},"a":{"a":0,"k":[354.171,354.168,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":1,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[67.709,0],[-67.709,0]],"c":false}]},{"i":{"x":0.4,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[43.168,0],[-43.168,0]],"c":false}]},{"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[67.709,0],[-67.709,0]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":1,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-67.709],[0,67.709]],"c":false}]},{"i":{"x":0.4,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-43.168],[0,43.168]],"c":false}]},{"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-67.709],[0,67.709]],"c":false}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[354.171,354.168],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":60,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[145.831,145.831,0],"to":[34.722,0,0],"ti":[-34.722,0,0]},{"t":42,"s":[354.165,145.831,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[11.506,0],[0,0],[0,-11.506],[0,0],[-11.506,0],[0,0],[0,11.505],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,11.505],[0,0],[11.506,0],[0,0],[0,-11.506]],"v":[[46.875,-67.709],[-46.875,-67.709],[-67.709,-46.875],[-67.709,46.875],[-46.875,67.709],[46.875,67.709],[67.709,46.875],[67.709,-46.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":60,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":15,"s":[145.831,354.17,0],"to":[0,-34.723,0],"ti":[0,34.723,0]},{"t":49,"s":[145.831,145.831,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[11.506,0],[0,0],[0,-11.505],[0,0],[-11.506,0],[0,0],[0,11.506],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,11.506],[0,0],[11.506,0],[0,0],[0,-11.505]],"v":[[46.875,-67.709],[-46.875,-67.709],[-67.709,-46.875],[-67.709,46.875],[-46.875,67.709],[46.875,67.709],[67.709,46.875],[67.709,-46.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":60,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[145.831,354.17,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[11.506,0],[0,0],[0,0.034],[0,0],[-11.506,0],[0,0],[0,-0.034],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,-0.034],[0,0],[11.506,0],[0,0],[0,0.034]],"v":[[46.581,67.83],[-47.169,67.83],[-68.003,67.769],[-67.709,67.77],[-46.875,67.709],[46.875,67.709],[67.709,67.77],[67.415,67.769]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[11.506,0],[0,0],[0,-11.505],[0,0],[-11.506,0],[0,0],[0,11.506],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,11.506],[0,0],[11.506,0],[0,0],[0,-11.505]],"v":[[46.581,26.291],[-47.169,26.291],[-68.003,47.125],[-67.709,46.875],[-46.875,67.709],[46.875,67.709],[67.709,46.875],[67.415,47.125]],"c":true}]},{"t":57,"s":[{"i":[[11.506,0],[0,0],[0,-11.505],[0,0],[-11.506,0],[0,0],[0,11.506],[0,0]],"o":[[0,0],[-11.506,0],[0,0],[0,11.506],[0,0],[11.506,0],[0,0],[0,-11.505]],"v":[[46.875,-67.709],[-46.875,-67.709],[-67.709,-46.875],[-67.709,46.875],[-46.875,67.709],[46.875,67.709],[67.709,46.875],[67.709,-46.875]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":23,"op":60,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250.002,250.002,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[2083,2083,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.41,0],[0,0],[0,0],[0.41,0],[0,-0.41],[0,0],[0,0],[0,-0.41],[-0.41,0],[0,0],[0,0],[-0.41,0],[0,0.41],[0,0],[0,0],[0,0.41]],"o":[[0,0],[0,0],[0,-0.41],[-0.41,0],[0,0],[0,0],[-0.41,0],[0,0.41],[0,0],[0,0],[0,0.41],[0.41,0],[0,0],[0,0],[0.41,0],[0,-0.41]],"v":[[3.25,-0.75],[0.75,-0.75],[0.75,-3.25],[0,-4],[-0.75,-3.25],[-0.75,-0.75],[-3.25,-0.75],[-4,0],[-3.25,0.75],[-0.75,0.75],[-0.75,3.25],[0,4],[0.75,3.25],[0.75,0.75],[3.25,0.75],[4,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[255,255],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.14,0],[0,0],[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14]],"o":[[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14],[0,0],[0.14,0],[0,0]],"v":[[2.5,2.25],[2.25,2.5],[-2.25,2.5],[-2.5,2.25],[-2.5,-2.25],[-2.25,-2.5],[2.25,-2.5],[2.5,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.96,0],[0,0],[0,-0.96],[0,0],[-0.96,0],[0,0],[0,0.96],[0,0]],"o":[[0,0],[-0.97,0],[0,0],[0,0.96],[0,0],[0.96,0],[0,0],[0,-0.96]],"v":[[2.25,-4],[-2.25,-4],[-4,-2.25],[-4,2.25],[-2.25,4],[2.25,4],[4,2.25],[4,-2.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[255,245],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.14,0],[0,0],[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14]],"o":[[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14],[0,0],[0.14,0],[0,0]],"v":[[2.5,2.25],[2.25,2.5],[-2.25,2.5],[-2.5,2.25],[-2.5,-2.25],[-2.25,-2.5],[2.25,-2.5],[2.5,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.96,0],[0,0],[0,-0.96],[0,0],[-0.97,0],[0,0],[0,0.96],[0,0]],"o":[[0,0],[-0.97,0],[0,0],[0,0.96],[0,0],[0.96,0],[0,0],[0,-0.96]],"v":[[2.25,-4],[-2.25,-4],[-4,-2.25],[-4,2.25],[-2.25,4],[2.25,4],[4,2.25],[4,-2.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[245,245],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.14,0],[0,0],[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14]],"o":[[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14],[0,0],[0.14,0],[0,0]],"v":[[2.5,2.25],[2.25,2.5],[-2.25,2.5],[-2.5,2.25],[-2.5,-2.25],[-2.25,-2.5],[2.25,-2.5],[2.5,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.96,0],[0,0],[0,-0.96],[0,0],[-0.97,0],[0,0],[0,0.96],[0,0]],"o":[[0,0],[-0.97,0],[0,0],[0,0.96],[0,0],[0.96,0],[0,0],[0,-0.96]],"v":[[2.25,-4],[-2.25,-4],[-4,-2.25],[-4,2.25],[-2.25,4],[2.25,4],[4,2.25],[4,-2.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[245,255],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":300,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250.002,250.002,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[2083,2083,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.41,0],[0,0],[0,0],[0.41,0],[0,-0.41],[0,0],[0,0],[0,-0.41],[-0.41,0],[0,0],[0,0],[-0.41,0],[0,0.41],[0,0],[0,0],[0,0.41]],"o":[[0,0],[0,0],[0,-0.41],[-0.41,0],[0,0],[0,0],[-0.41,0],[0,0.41],[0,0],[0,0],[0,0.41],[0.41,0],[0,0],[0,0],[0.41,0],[0,-0.41]],"v":[[3.25,-0.75],[0.75,-0.75],[0.75,-3.25],[0,-4],[-0.75,-3.25],[-0.75,-0.75],[-3.25,-0.75],[-4,0],[-3.25,0.75],[-0.75,0.75],[-0.75,3.25],[0,4],[0.75,3.25],[0.75,0.75],[3.25,0.75],[4,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[255,255],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.14,0],[0,0],[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14]],"o":[[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14],[0,0],[0.14,0],[0,0]],"v":[[2.5,2.25],[2.25,2.5],[-2.25,2.5],[-2.5,2.25],[-2.5,-2.25],[-2.25,-2.5],[2.25,-2.5],[2.5,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.96,0],[0,0],[0,-0.96],[0,0],[-0.96,0],[0,0],[0,0.96],[0,0]],"o":[[0,0],[-0.97,0],[0,0],[0,0.96],[0,0],[0.96,0],[0,0],[0,-0.96]],"v":[[2.25,-4],[-2.25,-4],[-4,-2.25],[-4,2.25],[-2.25,4],[2.25,4],[4,2.25],[4,-2.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[255,245],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.14,0],[0,0],[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14]],"o":[[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14],[0,0],[0.14,0],[0,0]],"v":[[2.5,2.25],[2.25,2.5],[-2.25,2.5],[-2.5,2.25],[-2.5,-2.25],[-2.25,-2.5],[2.25,-2.5],[2.5,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.96,0],[0,0],[0,-0.96],[0,0],[-0.97,0],[0,0],[0,0.96],[0,0]],"o":[[0,0],[-0.97,0],[0,0],[0,0.96],[0,0],[0.96,0],[0,0],[0,-0.96]],"v":[[2.25,-4],[-2.25,-4],[-4,-2.25],[-4,2.25],[-2.25,4],[2.25,4],[4,2.25],[4,-2.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[245,245],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.14,0],[0,0],[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14]],"o":[[0,0.14],[0,0],[-0.14,0],[0,0],[0,-0.14],[0,0],[0.14,0],[0,0]],"v":[[2.5,2.25],[2.25,2.5],[-2.25,2.5],[-2.5,2.25],[-2.5,-2.25],[-2.25,-2.5],[2.25,-2.5],[2.5,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.96,0],[0,0],[0,-0.96],[0,0],[-0.97,0],[0,0],[0,0.96],[0,0]],"o":[[0,0],[-0.97,0],[0,0],[0,0.96],[0,0],[0.96,0],[0,0],[0,-0.96]],"v":[[2.25,-4],[-2.25,-4],[-4,-2.25],[-4,2.25],[-2.25,4],[2.25,4],[4,2.25],[4,-2.25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-40-add-card').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[245,255],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"control","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"primary","np":3,"mn":"ADBE Color Control","ix":1,"en":1,"ef":[{"ty":2,"nm":"Color","mn":"ADBE Color Control-0001","ix":1,"v":{"a":0,"k":[0.914,0.91,0.91],"ix":1}}]}],"ip":0,"op":131,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"hover-add-card","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":0,"op":70,"st":0,"bm":0}],"markers":[{"tm":0,"cm":"default:hover-add-card","dr":60}],"props":{}} \ No newline at end of file diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index ea91edcbc..4438113d2 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -125,7 +125,7 @@ export enum ProjectPermissionSecretScanningDataSourceActions { export enum ProjectPermissionSecretScanningFindingActions { Read = "read-findings", - Resolve = "resolve-findings" + Update = "update-findings" } export enum PermissionConditionOperators { diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index 12c8ada68..79a0224c8 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -196,7 +196,8 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.SECRET_SCANNING_DATA_SOURCE_UPDATE]: "Update Secret Scanning Data Source", [EventType.SECRET_SCANNING_DATA_SOURCE_DELETE]: "Delete Secret Scanning Data Source", [EventType.SECRET_SCANNING_DATA_SOURCE_GET]: "Get Secret Scanning Data Source", - [EventType.SECRET_SCANNING_DATA_SOURCE_SCAN]: "San Scanning Data Source", + [EventType.SECRET_SCANNING_DATA_SOURCE_TRIGGER_SCAN]: "Trigger Secret Scanning Data Source Scan", + [EventType.SECRET_SCANNING_DATA_SOURCE_SCAN]: "Secret Scanning Data Source Scan", [EventType.SECRET_SCANNING_RESOURCE_LIST]: "List Secret Scanning Resources", [EventType.SECRET_SCANNING_SCAN_LIST]: "List Secret Scanning Scans", [EventType.SECRET_SCANNING_FINDING_LIST]: "List Secret Scanning Findings", diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index ab306e6df..211ae0cb9 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -190,6 +190,7 @@ export enum EventType { SECRET_SCANNING_DATA_SOURCE_UPDATE = "secret-scanning-data-source-update", SECRET_SCANNING_DATA_SOURCE_DELETE = "secret-scanning-data-source-delete", SECRET_SCANNING_DATA_SOURCE_GET = "secret-scanning-data-source-get", + SECRET_SCANNING_DATA_SOURCE_TRIGGER_SCAN = "secret-scanning-data-source-trigger-scan", SECRET_SCANNING_DATA_SOURCE_SCAN = "secret-scanning-data-source-scan", SECRET_SCANNING_RESOURCE_LIST = "secret-scanning-resource-list", SECRET_SCANNING_SCAN_LIST = "secret-scanning-scan-list", diff --git a/frontend/src/hooks/api/secretScanningV2/types/shared/secret-scanning-data-source-base.ts b/frontend/src/hooks/api/secretScanningV2/types/shared/secret-scanning-data-source-base.ts index bdd6b86c3..82dab866a 100644 --- a/frontend/src/hooks/api/secretScanningV2/types/shared/secret-scanning-data-source-base.ts +++ b/frontend/src/hooks/api/secretScanningV2/types/shared/secret-scanning-data-source-base.ts @@ -14,4 +14,5 @@ export type TSecretScanningDataSourceBase = { name: string; }; isAutoScanEnabled: boolean; + isDisconnected: boolean; }; diff --git a/frontend/src/hooks/api/subscriptions/types.ts b/frontend/src/hooks/api/subscriptions/types.ts index ec7b6a2dd..fd73a9595 100644 --- a/frontend/src/hooks/api/subscriptions/types.ts +++ b/frontend/src/hooks/api/subscriptions/types.ts @@ -50,4 +50,5 @@ export type SubscriptionPlan = { enforceMfa: boolean; projectTemplates: boolean; kmip: boolean; + secretScanning: boolean; }; diff --git a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx index 84a530559..9867c417f 100644 --- a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx +++ b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx @@ -77,6 +77,7 @@ export const ProjectLayout = () => { const { data: unresolvedFindings } = useGetSecretScanningUnresolvedFindingCount(workspaceId, { enabled: isSecretScanning && + subscription.secretScanning && permission.can( ProjectPermissionSecretScanningFindingActions.Read, ProjectPermissionSub.SecretScanningFindings @@ -264,7 +265,7 @@ export const ProjectLayout = () => { }} > {({ isActive }) => ( - + Data Sources )} diff --git a/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx b/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx index 7af215116..da4b4e819 100644 --- a/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx +++ b/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx @@ -5,6 +5,7 @@ import { Button } from "@app/components/v2"; import { useProjectPermission, useWorkspace } from "@app/context"; import { useRemoveAssumeProjectPrivilege } from "@app/hooks/api"; import { ActorType } from "@app/hooks/api/auditLogs/enums"; +import { ProjectType } from "@app/hooks/api/workspace/types"; export const AssumePrivilegeModeBanner = () => { const { currentWorkspace } = useWorkspace(); @@ -35,7 +36,20 @@ export const AssumePrivilegeModeBanner = () => { }, { onSuccess: () => { - window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/overview`; + let page: string; + + switch (currentWorkspace.type) { + case ProjectType.SecretScanning: + page = "data-sources"; + break; + case ProjectType.CertificateManager: + page = "subscribers"; + break; + default: + page = "overview"; + } + + window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/${page}`; } } ); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx index c061fd6f2..94666e0f5 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx @@ -28,6 +28,7 @@ import { useGetWorkspaceUserDetails } from "@app/hooks/api"; import { ActorType } from "@app/hooks/api/auditLogs/enums"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { MemberProjectAdditionalPrivilegeSection } from "./components/MemberProjectAdditionalPrivilegeSection"; import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection"; @@ -70,7 +71,21 @@ export const Page = () => { type: "success", text: "User privilege assumption has started" }); - window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/overview`; + + let page: string; + + switch (currentWorkspace.type) { + case ProjectType.SecretScanning: + page = "data-sources"; + break; + case ProjectType.CertificateManager: + page = "subscribers"; + break; + default: + page = "overview"; + } + + window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/${page}`; } } ); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index 0b94edfd1..893222141 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -104,7 +104,7 @@ const SecretScanningDataSourcePolicyActionSchema = z.object({ const SecretScanningFindingPolicyActionSchema = z.object({ [ProjectPermissionSecretScanningFindingActions.Read]: z.boolean().optional(), - [ProjectPermissionSecretScanningFindingActions.Resolve]: z.boolean().optional() + [ProjectPermissionSecretScanningFindingActions.Update]: z.boolean().optional() }); const KmipPolicyActionSchema = z.object({ @@ -731,6 +731,52 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { formVal[subject]![0][ProjectPermissionSecretSyncActions.RemoveSecrets] = true; } + if (subject === ProjectPermissionSub.SecretScanningDataSources) { + const canRead = action.includes(ProjectPermissionSecretScanningDataSourceActions.Read); + const canEdit = action.includes(ProjectPermissionSecretScanningDataSourceActions.Edit); + const canDelete = action.includes(ProjectPermissionSecretScanningDataSourceActions.Delete); + const canCreate = action.includes(ProjectPermissionSecretScanningDataSourceActions.Create); + const canReadScans = action.includes( + ProjectPermissionSecretScanningDataSourceActions.ReadScans + ); + const canReadResources = action.includes( + ProjectPermissionSecretScanningDataSourceActions.ReadResources + ); + const canTriggerScans = action.includes( + ProjectPermissionSecretScanningDataSourceActions.TriggerScans + ); + + if (!formVal[subject]) formVal[subject] = [{}]; + + // from above statement we are sure it won't be undefined + if (canRead) + formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Read] = true; + if (canEdit) + formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Edit] = true; + if (canCreate) + formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Create] = true; + if (canDelete) + formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Delete] = true; + if (canReadScans) + formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.ReadScans] = true; + if (canReadResources) + formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.ReadResources] = true; + if (canTriggerScans) + formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.TriggerScans] = true; + } + + if (subject === ProjectPermissionSub.SecretScanningFindings) { + const canRead = action.includes(ProjectPermissionSecretScanningFindingActions.Read); + const canUpdate = action.includes(ProjectPermissionSecretScanningFindingActions.Update); + + if (!formVal[subject]) formVal[subject] = [{}]; + + // from above statement we are sure it won't be undefined + if (canRead) formVal[subject]![0][ProjectPermissionSecretScanningFindingActions.Read] = true; + if (canUpdate) + formVal[subject]![0][ProjectPermissionSecretScanningFindingActions.Update] = true; + } + if (subject === ProjectPermissionSub.SshHosts) { if (!formVal[subject]) formVal[subject] = []; @@ -1313,8 +1359,8 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { value: ProjectPermissionSecretScanningFindingActions.Read }, { - label: "Resolve Finding Status", - value: ProjectPermissionSecretScanningFindingActions.Resolve + label: "Update Findings", + value: ProjectPermissionSecretScanningFindingActions.Update } ] } diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/SecretScanningDataSourceByIdPage.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/SecretScanningDataSourceByIdPage.tsx index 3c4242041..01e32836c 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/SecretScanningDataSourceByIdPage.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/SecretScanningDataSourceByIdPage.tsx @@ -82,7 +82,6 @@ const PageContent = () => {

{dataSource.name}

{details.name} Data Source

- {/* */}
diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx index 19875eb1f..bed68d164 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx @@ -1,10 +1,10 @@ -import { faBan, faCheck, faEdit } from "@fortawesome/free-solid-svg-icons"; +import { faBan, faCheck, faEdit, faPlugCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ProjectPermissionCan } from "@app/components/permissions"; import { EditSecretScanningDataSourceModal } from "@app/components/secret-scanning"; import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge, IconButton } from "@app/components/v2"; +import { Badge, IconButton, Tooltip } from "@app/components/v2"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; @@ -18,13 +18,31 @@ type Props = { export const SecretScanningDataSourceSection = ({ dataSource }: Props) => { const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["editDataSource"] as const); - const { name, description, connection, isAutoScanEnabled } = dataSource; + const { name, description, connection, isAutoScanEnabled, isDisconnected } = dataSource; return ( <>
-

Details

+
+

Details

+ {isDisconnected && ( + +
+ + + Disconnected + +
+
+ )} +
{
- {type === SecretScanningScanType.FullScan ? "Full scan" : "Diff scan"} + {type === SecretScanningScanType.FullScan ? "Full Scan" : "Diff Scan"} { diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/SecretScanningDataSourcesPage.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/SecretScanningDataSourcesPage.tsx index edf63e811..d681c34af 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/SecretScanningDataSourcesPage.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/SecretScanningDataSourcesPage.tsx @@ -1,7 +1,12 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { PageHeader } from "@app/components/v2"; +import { + ProjectPermissionSecretScanningDataSourceActions, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; import { SecretScanningDataSourcesSection } from "./components"; @@ -12,17 +17,23 @@ export const SecretScanningDataSourcesPage = () => { {t("common.head-title", { title: "Secret Scanning" })} -
-
-
- - + +
+
+
+ + +
-
+ ); }; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx index 45eaf1a03..10c3cc979 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx @@ -7,6 +7,7 @@ import { faEllipsisV, faExpand, faInfoCircle, + faPlugCircleXmark, faSearch, faToggleOff, faToggleOn, @@ -72,7 +73,8 @@ export const SecretScanningDataSourceRow = ({ unresolvedFindings, lastScannedAt, lastScanStatus, - lastScanStatusMessage + lastScanStatusMessage, + isDisconnected } = dataSource; const sourceDetails = SECRET_SCANNING_DATA_SOURCE_MAP[type]; @@ -199,7 +201,7 @@ export const SecretScanningDataSourceRow = ({ ) : ( No scans )} - {!isAutoScanEnabled && ( + {!isAutoScanEnabled && !isDisconnected && ( )} + {isDisconnected && ( + +
+ + + Disconnected + +
+
+ )}
diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx index af0a2899c..032cfcb9d 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx @@ -1,10 +1,11 @@ import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CreateSecretScanningDataSourceModal } from "@app/components/secret-scanning"; import { Button, Spinner } from "@app/components/v2"; -import { ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionSub, useSubscription, useWorkspace } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { useListSecretScanningDataSources } from "@app/hooks/api/secretScanningV2"; @@ -12,7 +13,12 @@ import { useListSecretScanningDataSources } from "@app/hooks/api/secretScanningV import { SecretScanningDataSourcesTable } from "./SecretScanningDataSourcesTable"; export const SecretScanningDataSourcesSection = () => { - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addDataSource"] as const); + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ + "addDataSource", + "upgradePlan" + ] as const); + + const { subscription } = useSubscription(); const { currentWorkspace } = useWorkspace(); @@ -63,7 +69,14 @@ export const SecretScanningDataSourcesSection = () => { colorSchema="secondary" type="submit" leftIcon={} - onClick={() => handlePopUpOpen("addDataSource")} + onClick={() => { + if (!subscription.secretScanning) { + handlePopUpOpen("upgradePlan"); + return; + } + + handlePopUpOpen("addDataSource"); + }} isDisabled={!isAllowed} > Add Data Source @@ -77,6 +90,11 @@ export const SecretScanningDataSourcesSection = () => { isOpen={popUp.addDataSource.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("addDataSource", isOpen)} /> + handlePopUpToggle("upgradePlan", isOpen)} + text="You can create Data Sources by upgrading to Infisical's Enterprise plan." + /> ); }; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx index a81e30e32..170ef227a 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx @@ -3,9 +3,9 @@ import { faArrowDown, faArrowUp, faCheckCircle, + faCubesStacked, faFilter, faMagnifyingGlass, - faPuzzlePiece, faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -368,7 +368,7 @@ export const SecretScanningDataSourcesTable = ({ dataSources }: Props) => { ? "No data sources match search..." : "This project has no data sources configured" } - icon={dataSources.length ? faSearch : faPuzzlePiece} + icon={dataSources.length ? faSearch : faCubesStacked} /> )} diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/SecretScanningFindingsPage.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/SecretScanningFindingsPage.tsx index 696e36843..1eddf5eee 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/SecretScanningFindingsPage.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/SecretScanningFindingsPage.tsx @@ -1,7 +1,10 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { PageHeader } from "@app/components/v2"; +import { ProjectPermissionSub } from "@app/context"; +import { ProjectPermissionSecretScanningFindingActions } from "@app/context/ProjectPermissionContext/types"; import { SecretScanningFindingsSection } from "./components"; @@ -12,14 +15,20 @@ export const SecretScanningFindingsPage = () => { {t("common.head-title", { title: "Secret Scanning" })} -
-
-
- - + +
+
+
+ + +
-
+ ); }; diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx index 514033099..7f47bf6c1 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx @@ -5,6 +5,7 @@ import { format } from "date-fns"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Badge, Button, @@ -18,6 +19,10 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { + ProjectPermissionSecretScanningFindingActions, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; import { SECRET_SCANNING_DATA_SOURCE_MAP, SECRET_SCANNING_FINDING_STATUS_ICON_MAP @@ -191,19 +196,32 @@ export const SecretScanningFindingRow = ({ finding, onUpdate }: Props) => { {details.link} - - + {(isAllowed) => ( + + )} + + {Boolean(remarks) && ( + + {remarks} + + )}
diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx index 6e2aaf0a2..b6438d50b 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx @@ -133,7 +133,7 @@ export const SecretScanningUpdateFindingModal = ({ finding, isOpen, onOpenChange return ( - + onOpenChange(false)} />