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 27916b10d..ba77b5b7a 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 @@ -189,7 +189,7 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) server.route({ method: "GET", - url: "/configs/:projectId", + url: "/configs", config: { rateLimit: readLimit }, @@ -197,7 +197,7 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) hide: false, tags: [ApiDocsTags.SecretScanning], description: "Get the Secret Scanning Config for the specified project.", - params: z.object({ + querystring: z.object({ projectId: z .string() .trim() @@ -213,7 +213,7 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const { - params: { projectId }, + query: { projectId }, permission } = req; @@ -233,7 +233,7 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) server.route({ method: "PATCH", - url: "/configs/:projectId", + url: "/configs", config: { rateLimit: writeLimit }, @@ -241,7 +241,7 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) hide: false, tags: [ApiDocsTags.SecretScanning], description: "Update the specified Secret Scanning Configuration.", - params: z.object({ + querystring: z.object({ projectId: z.string().trim().min(1, "Finding ID required").describe(SecretScanningConfigs.UPDATE.projectId) }), body: z.object({ @@ -254,7 +254,7 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider) onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const { - params: { projectId }, + query: { projectId }, body, permission } = req; 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 00de7cc45..e40d7f622 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 @@ -1,5 +1,6 @@ import { join } from "path"; import { ProbotOctokit } from "probot"; +import RE2 from "re2"; 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"; @@ -28,6 +29,8 @@ import { listGitHubRadarRepositories, TGitHubRadarConnection } from "@app/servic import { TGitHubDataSourceWithConnection, TQueueGitHubResourceDiffScan } from "./github-secret-scanning-types"; +const GitHubRepositoryRegex = new RE2(/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/); + export const GitHubSecretScanningFactory = () => { const initialize: TSecretScanningFactoryInitialize = async ( { connection, secretScanningV2DAL }, @@ -104,6 +107,10 @@ export const GitHubSecretScanningFactory = () => { const repoPath = join(tempFolder, "repo.git"); + if (!GitHubRepositoryRegex.test(resourceName)) { + throw new Error("Invalid GitHub repository name"); + } + await cloneRepository({ cloneUrl: `https://x-access-token:${token}@github.com/${resourceName}.git`, repoPath 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 230dde520..8e38e04c1 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 @@ -13,7 +13,8 @@ export const githubSecretScanningService = ( ) => { const handleInstallationDeletedEvent = async (installationId: number) => { const dataSource = await secretScanningV2DAL.dataSources.findOne({ - externalId: String(installationId) + externalId: String(installationId), + type: SecretScanningDataSource.GitHub }); if (!dataSource) { 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 1128015a4..031d15bb7 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 @@ -1,5 +1,6 @@ import { AxiosError } from "axios"; import { exec } from "child_process"; +import RE2 from "re2"; 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"; @@ -83,6 +84,8 @@ export const replaceNonChangesWithNewlines = (patch: string) => { .join("\n"); }; +const HunkHeaderRegex = new RE2(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/); + export const convertPatchLineToFileLineNumber = (patch: string, patchLineNumber: number) => { const lines = patch.split("\n"); let currentPatchLine = 0; @@ -92,7 +95,7 @@ export const convertPatchLineToFileLineNumber = (patch: string, patchLineNumber: currentPatchLine += 1; // Hunk header: @@ -a,b +c,d @@ - const hunkHeaderMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/); + const hunkHeaderMatch = line.match(HunkHeaderRegex); if (hunkHeaderMatch) { const startLine = parseInt(hunkHeaderMatch[1], 10); currentNewLine = startLine; 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 f02eca27c..3747af81f 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 @@ -28,7 +28,6 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { TSecretScanningV2DALFactory } from "./secret-scanning-v2-dal"; import { SecretScanningDataSource, - SecretScanningFindingStatus, SecretScanningResource, SecretScanningScanStatus, SecretScanningScanType @@ -217,12 +216,11 @@ export const secretScanningV2QueueServiceFactory = async ({ dataSourceType: dataSource.type, resourceName: resource.name, resourceType: resource.type, - scanId, - status: SecretScanningFindingStatus.Unresolved + scanId })), ["projectId", "fingerprint"], tx, - ["resourceName", "dataSourceName", "status"] + ["resourceName", "dataSourceName"] ); } @@ -441,12 +439,11 @@ export const secretScanningV2QueueServiceFactory = async ({ dataSourceType: dataSource.type, resourceName: resource.name, resourceType: resource.type, - scanId, - status: SecretScanningFindingStatus.Unresolved + scanId })), ["projectId", "fingerprint"], tx, - ["resourceName", "dataSourceName", "status"] + ["resourceName", "dataSourceName"] ); } 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 6626adc4b..85d8c4807 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 @@ -1,4 +1,5 @@ import { ForbiddenError } from "@casl/ability"; +import { join } from "path"; import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -9,6 +10,12 @@ import { ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { + createTempFolder, + deleteTempFolder, + scanContentAndGetFindings, + writeTextToFile +} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns"; import { githubSecretScanningService } from "@app/ee/services/secret-scanning-v2/github/github-secret-scanning-service"; import { SecretScanningFindingStatus } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; import { SECRET_SCANNING_FACTORY_MAP } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-factory"; @@ -810,10 +817,26 @@ export const secretScanningV2ServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretScanningConfigActions.Read, + ProjectPermissionSecretScanningConfigActions.Update, ProjectPermissionSub.SecretScanningConfigs ); + if (content) { + const tempFolder = await createTempFolder(); + try { + const configPath = join(tempFolder, "infisical-scan.toml"); + await writeTextToFile(configPath, content); + + await scanContentAndGetFindings("", configPath); + } catch (e) { + throw new BadRequestError({ + message: "Unable to parse configuration: Check syntax and formatting." + }); + } finally { + await deleteTempFolder(tempFolder); + } + } + const [config] = await secretScanningV2DAL.configs.upsert( [ { diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 41a864679..3135dc00c 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -1,5 +1,7 @@ import { z } from "zod"; +import { QueueWorkerProfile } from "@app/lib/types"; + import { removeTrailingSlash } from "../fn"; import { CustomLogger } from "../logger/logger"; import { zpStr } from "../zod"; @@ -57,6 +59,7 @@ const envSchema = z ENCRYPTION_KEY: zpStr(z.string().optional()), ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()), QUEUE_WORKERS_ENABLED: zodStrBool.default("true"), + QUEUE_WORKER_PROFILE: z.nativeEnum(QueueWorkerProfile).default(QueueWorkerProfile.All), HTTPS_ENABLED: zodStrBool, ROTATION_DEVELOPMENT_MODE: zodStrBool.default("false").optional(), // smtp options diff --git a/backend/src/lib/types/index.ts b/backend/src/lib/types/index.ts index 9f063172f..49d8893be 100644 --- a/backend/src/lib/types/index.ts +++ b/backend/src/lib/types/index.ts @@ -78,3 +78,9 @@ export type OrgServiceActor = { authMethod: ActorAuthMethod; orgId: string; }; + +export enum QueueWorkerProfile { + All = "all", + Standard = "standard", + SecretScanning = "secret-scanning" +} diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index 8b0098134..06ff66374 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -19,6 +19,7 @@ import { } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; +import { QueueWorkerProfile } from "@app/lib/types"; import { TFailedIntegrationSyncEmailsPayload, TIntegrationSyncPayload, @@ -269,6 +270,37 @@ export type TQueueJobTypes = { }; }; +const SECRET_SCANNING_JOBS = [ + QueueJobs.SecretScanningV2FullScan, + QueueJobs.SecretScanningV2DiffScan, + QueueJobs.SecretScanningV2SendNotification, + QueueJobs.SecretScan +]; + +const NON_STANDARD_JOBS = [...SECRET_SCANNING_JOBS]; + +const SECRET_SCANNING_QUEUES = [ + QueueName.SecretScanningV2, + QueueName.SecretFullRepoScan, + QueueName.SecretPushEventScan +]; + +const NON_STANDARD_QUEUES = [...SECRET_SCANNING_QUEUES]; + +const isQueueEnabled = (name: QueueName) => { + const appCfg = getConfig(); + switch (appCfg.QUEUE_WORKER_PROFILE) { + case QueueWorkerProfile.Standard: + return !NON_STANDARD_QUEUES.includes(name); + case QueueWorkerProfile.SecretScanning: + return SECRET_SCANNING_QUEUES.includes(name); + case QueueWorkerProfile.All: + default: + // allow all + return true; + } +}; + export type TQueueServiceFactory = ReturnType; export const queueServiceFactory = ( redisUrl: string, @@ -325,7 +357,7 @@ export const queueServiceFactory = ( }); const appCfg = getConfig(); - if (appCfg.QUEUE_WORKERS_ENABLED) { + if (appCfg.QUEUE_WORKERS_ENABLED && isQueueEnabled(name)) { workerContainer[name] = new Worker(name, jobFn, { ...queueSettings, connection @@ -344,6 +376,30 @@ export const queueServiceFactory = ( throw new Error(`${jobName} queue is already initialized`); } + const appCfg = getConfig(); + + if (!appCfg.QUEUE_WORKERS_ENABLED) return; + + switch (appCfg.QUEUE_WORKER_PROFILE) { + case QueueWorkerProfile.Standard: + if (NON_STANDARD_JOBS.includes(jobName)) { + // only process standard jobs + return; + } + + break; + case QueueWorkerProfile.SecretScanning: + if (!SECRET_SCANNING_JOBS.includes(jobName)) { + // only process secret scanning jobs + return; + } + + break; + case QueueWorkerProfile.All: + default: + // allow all + } + await pgBoss.createQueue(jobName); queueContainerPg[jobName] = true; @@ -363,7 +419,7 @@ export const queueServiceFactory = ( listener: WorkerListener[U] ) => { const appCfg = getConfig(); - if (!appCfg.QUEUE_WORKERS_ENABLED) { + if (!appCfg.QUEUE_WORKERS_ENABLED || !isQueueEnabled(name)) { return; } diff --git a/docs/api-reference/endpoints/secret-scanning/config/get-by-project-id.mdx b/docs/api-reference/endpoints/secret-scanning/config/get-by-project-id.mdx index ee0df5661..8204a3098 100644 --- a/docs/api-reference/endpoints/secret-scanning/config/get-by-project-id.mdx +++ b/docs/api-reference/endpoints/secret-scanning/config/get-by-project-id.mdx @@ -1,4 +1,4 @@ --- title: "Get by Project ID" -openapi: "GET /api/v2/secret-scanning/configs/{projectId}" +openapi: "GET /api/v2/secret-scanning/configs" --- diff --git a/docs/api-reference/endpoints/secret-scanning/config/update.mdx b/docs/api-reference/endpoints/secret-scanning/config/update.mdx index d7025430d..bf068a20f 100644 --- a/docs/api-reference/endpoints/secret-scanning/config/update.mdx +++ b/docs/api-reference/endpoints/secret-scanning/config/update.mdx @@ -1,6 +1,6 @@ --- title: "Update" -openapi: "PATCH /api/v2/secret-scanning/configs/{projectId}" +openapi: "PATCH /api/v2/secret-scanning/configs" --- diff --git a/docs/documentation/platform/secret-scanning/overview.mdx b/docs/documentation/platform/secret-scanning/overview.mdx index e5f97da33..69bf5a783 100644 --- a/docs/documentation/platform/secret-scanning/overview.mdx +++ b/docs/documentation/platform/secret-scanning/overview.mdx @@ -10,6 +10,12 @@ Monitor and detect exposed secrets across your data sources, including code repo For additional security, we recommend using our [CLI Secret Scanner](/cli/scanning-overview#automatically-scan-changes-before-you-commit) to check for exposed secrets before pushing your code changes. + + Secret Scanning is a paid feature. + If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, + then you should contact team@infisical.com to purchase an enterprise license to use it. + + ## How Secret Scanning Works Secret Scanning consists of several components that enable you to quickly respond to secret leaks: diff --git a/docs/images/app-connections/github-radar/self-hosted-github-radar-step-4.png b/docs/images/app-connections/github-radar/self-hosted-github-radar-step-4.png index 6d4bdfc3b..52e27cec1 100644 Binary files a/docs/images/app-connections/github-radar/self-hosted-github-radar-step-4.png and b/docs/images/app-connections/github-radar/self-hosted-github-radar-step-4.png differ diff --git a/docs/images/app-connections/github-radar/self-hosted-github-radar-step-5.png b/docs/images/app-connections/github-radar/self-hosted-github-radar-step-5.png index 2e37745c4..aed2940ca 100644 Binary files a/docs/images/app-connections/github-radar/self-hosted-github-radar-step-5.png and b/docs/images/app-connections/github-radar/self-hosted-github-radar-step-5.png differ diff --git a/docs/images/app-connections/github-radar/self-hosted-github-radar-step-6.png b/docs/images/app-connections/github-radar/self-hosted-github-radar-step-6.png index 4e4717cfe..1ed7a8173 100644 Binary files a/docs/images/app-connections/github-radar/self-hosted-github-radar-step-6.png and b/docs/images/app-connections/github-radar/self-hosted-github-radar-step-6.png differ diff --git a/docs/integrations/app-connections/github-radar.mdx b/docs/integrations/app-connections/github-radar.mdx index 04c357499..376973efd 100644 --- a/docs/integrations/app-connections/github-radar.mdx +++ b/docs/integrations/app-connections/github-radar.mdx @@ -5,6 +5,12 @@ description: "Learn how to configure a GitHub Radar Connection for Infisical." Infisical supports GitHub App installation for creating a GitHub Radar Connection. + + GitHub Radar Connections are specifically configured for [Secret Scanning](/documentation/platform/secret-scanning/overview) and require specific permissions and webhook configuration. + + Check out our [GitHub Connection](/integrations/app-connections/github) for secret management features such as [Secret Syncs](/integrations/secret-syncs/overview). + + Using a GitHub Radar Connection with app authentication on a self-hosted instance of Infisical requires configuring an application on GitHub and registering your instance with it. @@ -38,19 +44,14 @@ Infisical supports GitHub App installation for creating a GitHub Radar Connectio ![github radar app webhook](/images/app-connections/github-radar/self-hosted-github-radar-step-3.png) Set the following repository permissions: - 1. **Checks**: `Read and Write` - 2. **Content**: `Read-only` - 3. **Issues**: `Read and Write` - 4. **Metadata**: `Read-only` - 5. **Pull Requests**: `Read and Write` + - **Contents**: `Read-only` + - **Metadata**: `Read-only` ![github radar app permissions 1](/images/app-connections/github-radar/self-hosted-github-radar-step-4.png) ![github radar app permissions 2](/images/app-connections/github-radar/self-hosted-github-radar-step-5.png) Subscribe to the following events: - 1. **Check run** - 2. **Pull request** - 3. **Push** + - **Push** ![github radar app events](/images/app-connections/github-radar/self-hosted-github-radar-step-6.png) @@ -92,7 +93,7 @@ Infisical supports GitHub App installation for creating a GitHub Radar Connectio -## Setup GitHub Connection in Infisical +## Setup GitHub Radar Connection in Infisical @@ -100,7 +101,7 @@ Infisical supports GitHub App installation for creating a GitHub Radar Connectio ![App Connections Tab](/images/app-connections/general/add-connection.png) - Select the **GitHub Connection** option from the connection options modal. + Select the **GitHub Radar Connection** option from the connection options modal. ![Select GitHub Radar Connection](/images/app-connections/github-radar/select-github-radar-connection.png) diff --git a/docs/internals/permissions/project-permissions.mdx b/docs/internals/permissions/project-permissions.mdx index acf95485b..c279e32f0 100644 --- a/docs/internals/permissions/project-permissions.mdx +++ b/docs/internals/permissions/project-permissions.mdx @@ -314,3 +314,32 @@ Supports conditions and permission inversion | `create` | Create new SSH certificate templates | | `edit` | Modify SSH template configurations | | `delete` | Remove SSH certificate templates | + +### Secret Scanning + +#### Subject: `secret-scanning-data-sources` + +| Action | Description | +| -------- | ---------------------------------------------------- | +| `read-data-sources` | View Data Sources | +| `create-data-sources` | Create new Data Sources | +| `edit-data-sources` | Modify Data Sources | +| `delete-data-sources` | Remove Data Sources | +| `read-data-source-resources` | View Data Source Resources | +| `read-data-source-scans` | View Data Source Scans | +| `trigger-data-source-scans` | Trigger Data Source Secret Scans | + +#### Subject: `secret-scanning-findings` + +| Action | Description | +| -------- | --------------------------------- | +| `read-findings` | View Secret Scanning Findings | +| `update-findings` | Update Secret Scanning Findings | + + +#### Subject: `secret-scanning-configs` + +| Action | Description | +| ---------------- | ------------------------------------------------ | +| `read-configs` | View Secret Scanning Project Configuration | +| `update-configs` | Update Secret Scanning Project Configuration | diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/GitHubDataSourceConfigFields.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/GitHubDataSourceConfigFields.tsx index a208738d2..2ccf9f8f8 100644 --- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/GitHubDataSourceConfigFields.tsx +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/GitHubDataSourceConfigFields.tsx @@ -102,7 +102,7 @@ export const GitHubDataSourceConfigFields = () => { isLoading={areRepositoriesLoading && Boolean(connectionId)} isDisabled={!connectionId} isMulti - value={repositories?.find((project) => value.includes(project.name))} + value={repositories?.filter((repository) => value.includes(repository.name))} onChange={(newValue) => { onChange( newValue diff --git a/frontend/src/hooks/api/secretScanningV2/mutations.tsx b/frontend/src/hooks/api/secretScanningV2/mutations.tsx index 36ea07c04..18bbbf236 100644 --- a/frontend/src/hooks/api/secretScanningV2/mutations.tsx +++ b/frontend/src/hooks/api/secretScanningV2/mutations.tsx @@ -145,7 +145,7 @@ export const useUpdateSecretScanningConfig = () => { return useMutation({ mutationFn: async ({ projectId, ...params }: TSecretScanningConfig) => { const { data } = await apiRequest.patch( - `/api/v2/secret-scanning/configs/${projectId}`, + `/api/v2/secret-scanning/configs?projectId=${projectId}`, params ); diff --git a/frontend/src/hooks/api/secretScanningV2/queries.tsx b/frontend/src/hooks/api/secretScanningV2/queries.tsx index 57714b043..d500a5371 100644 --- a/frontend/src/hooks/api/secretScanningV2/queries.tsx +++ b/frontend/src/hooks/api/secretScanningV2/queries.tsx @@ -237,7 +237,8 @@ export const useGetSecretScanningConfig = ( queryKey: secretScanningV2Keys.configByProjectId(projectId), queryFn: async () => { const { data } = await apiRequest.get( - `/api/v2/secret-scanning/configs/${projectId}` + "/api/v2/secret-scanning/configs", + { params: { projectId } } ); return data.config; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/index.ts b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/index.ts index e69de29bb..4e188958f 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/index.ts +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/index.ts @@ -0,0 +1 @@ +export * from "./DataSourceConfigDisplay"; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx index bed68d164..369ae8d34 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx @@ -9,7 +9,8 @@ import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { TSecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; -import { DataSourceConfigDisplay } from "@app/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/DataSourceConfigDisplay"; + +import { DataSourceConfigDisplay } from "./DataSourceConfigDisplay"; type Props = { dataSource: TSecretScanningDataSource; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx index 055d1745d..3109d00bc 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx @@ -1,5 +1,6 @@ import { useCallback } from "react"; import { + faBan, faCheck, faCopy, faEllipsisV, @@ -50,6 +51,13 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => { const { id, name, lastScannedAt, lastScanStatus, unresolvedFindings, lastScanStatusMessage } = resource; + const { + config: { includeRepos } + } = dataSource; + + // scott: will need to be differentiated by type once other data sources are available + const isActive = includeRepos.includes("*") || includeRepos.includes(name); + const triggerDataSourceScan = useTriggerSecretScanningDataSource(); const navigate = useNavigate(); @@ -175,61 +183,76 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => { )} - - - - - - - - - } - onClick={(e) => { - e.stopPropagation(); - handleCopyId(id); - }} - > - Copy Resource ID - - - {(isAllowed) => ( - } - onClick={(e) => { - e.stopPropagation(); - handleTriggerScan(); - }} - > - + {!isActive && ( + +
+ + + Inactive + +
+
+ )} + + + + + + + + + } + onClick={(e) => { + e.stopPropagation(); + handleCopyId(id); + }} + > + Copy Resource ID + + + {(isAllowed) => ( + } + onClick={(e) => { + e.stopPropagation(); + handleTriggerScan(); + }} > -
- Trigger Scan - -
-
-
- )} -
-
-
-
+ +
+ Trigger Scan + +
+
+ + )} + + + + + ); diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceTable.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceTable.tsx index 6fc3eb805..7f6a2485b 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceTable.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceTable.tsx @@ -1,8 +1,12 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { faArrowDown, faArrowUp, faBan, + faBullseye, + faCheckCircle, + faFilter, + faInfoCircle, faMagnifyingGlass, faSearch } from "@fortawesome/free-solid-svg-icons"; @@ -10,6 +14,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, EmptyState, IconButton, Input, @@ -20,6 +29,7 @@ import { TBody, Th, THead, + Tooltip, Tr } from "@app/components/v2"; import { ProjectPermissionSub, useProjectPermission } from "@app/context"; @@ -45,6 +55,15 @@ type Props = { dataSource: TSecretScanningDataSource; }; +enum ResourceStatus { + Active = "active", + Inactive = "inactive" +} + +type ResourceFilters = { + status: ResourceStatus[]; +}; + export const SecretScanningResourcesTable = ({ dataSource }: Props) => { const { permission } = useProjectPermission(); @@ -53,6 +72,10 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => { ProjectPermissionSub.SecretScanningDataSources ); + const [filters, setFilters] = useState({ + status: [ResourceStatus.Active] + }); + const { data: resources = [], isPending: isResourcesPending } = useListSecretScanningResources( { dataSourceId: dataSource.id, type: dataSource.type }, { @@ -81,6 +104,19 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => { resources .filter((resource) => { const { name } = resource; + const { + config: { includeRepos } + } = dataSource; + + // scott: will need to be differentiated by type once other data sources are available + const isActive = includeRepos.includes("*") || includeRepos.includes(name); + + if (filters.status.length === 1) { + if (filters.status.includes(ResourceStatus.Active)) { + return isActive; + } + return !isActive; + } const searchValue = search.trim().toLowerCase(); @@ -114,7 +150,7 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => { return resourceOne.name.toLowerCase().localeCompare(resourceTwo.name.toLowerCase()); } }), - [resources, orderDirection, search, orderBy] + [resources, orderDirection, search, orderBy, filters, dataSource] ); useResetPageHelper({ @@ -141,15 +177,74 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => { const resourceDetails = RESOURCE_DESCRIPTION_HELPER[dataSource.type]; + const isTableFiltered = Boolean(filters.status.length); + return (
- setSearch(e.target.value)} - leftIcon={} - placeholder={`Search ${resourceDetails.pluralNoun}...`} - className="flex-1" - /> +
+ setSearch(e.target.value)} + leftIcon={} + placeholder={`Search ${resourceDetails.pluralNoun}...`} + className="flex-1" + /> + + + + + + + + + Status + + + + + {Object.values(ResourceStatus).map((status) => ( + { + e.preventDefault(); + setFilters((prev) => ({ + ...prev, + status: prev.status.includes(status) + ? prev.status.filter((s) => s !== status) + : [...prev.status, status] + })); + }} + key={status} + icon={ + filters.status.includes(status) && ( + + ) + } + iconPos="right" + > +
+ + {status.replace("-", " ")} +
+
+ ))} +
+
+
diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx index 032cfcb9d..019ca3467 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx @@ -24,10 +24,11 @@ export const SecretScanningDataSourcesSection = () => { const { data: dataSources = [], isPending: isDataSourcesPending } = useListSecretScanningDataSources(currentWorkspace.id, { - refetchInterval: 30000 + refetchInterval: 30000, + enabled: subscription.secretScanning }); - if (isDataSourcesPending) + if (subscription.secretScanning && isDataSourcesPending) return (
@@ -57,7 +58,7 @@ export const SecretScanningDataSourcesSection = () => {

- Use App Connections to scan for secret leaks from third-party services. + Configure Data Sources to scan for secret leaks from third-party services.

{ }); navigate({ - to: `/organization/${ProjectType.SecretManager}/overview` as const + to: `/organization/${ProjectType.SecretScanning}/overview` as const }); handlePopUpClose("deleteWorkspace"); } catch (err) { @@ -123,7 +123,7 @@ export const DeleteProjectSection = () => { }); navigate({ - to: `/organization/${ProjectType.SecretManager}/overview` as const + to: `/organization/${ProjectType.SecretScanning}/overview` as const }); } catch (err) { console.error(err); diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/ProjectSshTab.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/ProjectSshTab.tsx deleted file mode 100644 index 8a79591bc..000000000 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/ProjectSshTab.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ProjectSshConfigCasSection } from "./components"; - -export const ProjectSshTab = () => { - return ( -
- -
- ); -}; diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx deleted file mode 100644 index 503717326..000000000 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; - -import { createNotification } from "@app/components/notifications"; -import { ProjectPermissionCan } from "@app/components/permissions"; -import { Button, FormControl, Select, SelectItem } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { - useGetProjectSshConfig, - useListWorkspaceSshCas, - useUpdateProjectSshConfig -} from "@app/hooks/api"; - -const schema = z - .object({ - defaultUserSshCaId: z.string(), - defaultHostSshCaId: z.string() - }) - .required(); - -export type FormData = z.infer; - -export const ProjectSshConfigCasSection = () => { - const { currentWorkspace } = useWorkspace(); - const { data: sshConfig } = useGetProjectSshConfig(currentWorkspace.id); - const { data: sshCas } = useListWorkspaceSshCas(currentWorkspace.id); - const { mutate: updateProjectSshConfig } = useUpdateProjectSshConfig(); - - const { - control, - handleSubmit, - reset, - formState: { isSubmitting } - } = useForm({ - resolver: zodResolver(schema) - }); - - useEffect(() => { - if (sshConfig) { - reset({ - defaultUserSshCaId: sshConfig.defaultUserSshCaId || "", - defaultHostSshCaId: sshConfig.defaultHostSshCaId || "" - }); - } - }, [sshConfig]); - - const onFormSubmit = async ({ defaultUserSshCaId, defaultHostSshCaId }: FormData) => { - try { - await updateProjectSshConfig({ - projectId: currentWorkspace.id, - defaultUserSshCaId: defaultUserSshCaId || undefined, - defaultHostSshCaId: defaultHostSshCaId || undefined - }); - - createNotification({ - text: "Successfully updated SSH project settings", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update SSH project settings", - type: "error" - }); - } - }; - - return ( -
-

Certificate Authorities

-
- ( - - - - )} - /> - ( - - - - )} - /> - - {(isAllowed) => ( - - )} - - -
- ); -}; diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/components/index.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/components/index.tsx deleted file mode 100644 index 79010edb9..000000000 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/components/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ProjectSshConfigCasSection } from "./ProjectSshConfigCasSection"; diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/index.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/index.tsx deleted file mode 100644 index 2fff359fe..000000000 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectSshTab/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ProjectSshTab } from "./ProjectSshTab";