diff --git a/README.md b/README.md index e9b5d05061..7fb4c59b5a 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,11 @@ Thank you for using ThreatMapper. Please feel welcome to participate in the [Th * [productsecurity at deepfence dot io](SECURITY.md): Found a security issue? Share it in confidence * Find out more at [deepfence.io](https://deepfence.io/) +# Get ThreatStryker for Enterprise + +ThreatStryker is the enterprise version of ThreatMapper, with additional features for enterprise security teams. ThreatStryker is available as a cloud service or for on-premises deployment. + + # Security and Support diff --git a/deepfence_agent/plugins/SecretScanner b/deepfence_agent/plugins/SecretScanner index 97a7eb4ad5..e60ffe8905 160000 --- a/deepfence_agent/plugins/SecretScanner +++ b/deepfence_agent/plugins/SecretScanner @@ -1 +1 @@ -Subproject commit 97a7eb4ad58be365ed6d3d28b406422edb11042a +Subproject commit e60ffe8905d40c56dcebf94e09ea932d44261804 diff --git a/deepfence_agent/plugins/YaraHunter b/deepfence_agent/plugins/YaraHunter index 32fa95a8ca..d414c5e65a 160000 --- a/deepfence_agent/plugins/YaraHunter +++ b/deepfence_agent/plugins/YaraHunter @@ -1 +1 @@ -Subproject commit 32fa95a8ca4c4d872ac981aa0013463e6e704d96 +Subproject commit d414c5e65a8c56f668b035e07e9dd7da2c931159 diff --git a/deepfence_agent/plugins/package-scanner b/deepfence_agent/plugins/package-scanner index cdbde6f230..50726b71f1 160000 --- a/deepfence_agent/plugins/package-scanner +++ b/deepfence_agent/plugins/package-scanner @@ -1 +1 @@ -Subproject commit cdbde6f230da8065166a216bd9ad59d8a846f0ae +Subproject commit 50726b71f14961d4c6593f9ba6131df0e7581604 diff --git a/deepfence_agent/tools/apache/scope/go.sum b/deepfence_agent/tools/apache/scope/go.sum index 62f8d30f9d..049db951b5 100644 --- a/deepfence_agent/tools/apache/scope/go.sum +++ b/deepfence_agent/tools/apache/scope/go.sum @@ -1472,6 +1472,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/deepfence_bootstrapper/assets/config.ini b/deepfence_bootstrapper/assets/config.ini index 6cf2b363b3..5e367756f3 100644 --- a/deepfence_bootstrapper/assets/config.ini +++ b/deepfence_bootstrapper/assets/config.ini @@ -27,7 +27,7 @@ autostart=true autorestart=true [process:malware_scanner] -command=/bin/bash -c "rm -f /tmp/yara-hunter.sock && $DF_INSTALL_DIR/home/deepfence/bin/yara-hunter/YaraHunter --config-path $DF_INSTALL_DIR/home/deepfence/bin/yara-hunter --rules-path $DF_INSTALL_DIR/home/deepfence/bin/yara-hunter/yara-rules --socket-path=/tmp/yara-hunter.sock --http-port=8012 --enable-updater=false" +command=/bin/bash -c "rm -f /tmp/yara-hunter.sock && $DF_INSTALL_DIR/home/deepfence/bin/yara-hunter/YaraHunter --config-path $DF_INSTALL_DIR/home/deepfence/bin/yara-hunter --rules-path $DF_INSTALL_DIR/home/deepfence/bin/yara-hunter/yara-rules --socket-path=/tmp/yara-hunter.sock --enable-updater=false" path=$DF_INSTALL_DIR/home/deepfence/bin/yara-hunter/YaraHunter autostart=true autorestart=true diff --git a/deepfence_bootstrapper/go.sum b/deepfence_bootstrapper/go.sum index 518047f27a..d04d251553 100644 --- a/deepfence_bootstrapper/go.sum +++ b/deepfence_bootstrapper/go.sum @@ -463,8 +463,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.0.0-20181204000039-89a74a8d264d/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= diff --git a/deepfence_frontend/apps/dashboard/api-spec.json b/deepfence_frontend/apps/dashboard/api-spec.json index 5ab058c254..c270007b5c 100644 --- a/deepfence_frontend/apps/dashboard/api-spec.json +++ b/deepfence_frontend/apps/dashboard/api-spec.json @@ -9318,12 +9318,19 @@ "security": [{ "bearer_token": [] }] } }, - "/deepfence/settings/user-activity-log": { - "get": { + "/deepfence/settings/user-audit-log": { + "post": { "tags": ["Settings"], - "summary": "Get activity logs", - "description": "Get activity logs for all users", - "operationId": "getUserActivityLogs", + "summary": "Get user audit logs", + "description": "Get audit logs for all users", + "operationId": "getUserAuditLogs", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ModelGetAuditLogsRequest" } + } + } + }, "responses": { "200": { "description": "OK", @@ -9366,6 +9373,51 @@ "security": [{ "bearer_token": [] }] } }, + "/deepfence/settings/user-audit-log/count": { + "get": { + "tags": ["Settings"], + "summary": "Get user audit logs count", + "description": "Get user audit logs count", + "operationId": "getUserAuditLogsCount", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/SearchSearchCountResp" } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ApiDocsBadRequestResponse" } + } + } + }, + "401": { "description": "Unauthorized" }, + "403": { "description": "Forbidden" }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ApiDocsFailureResponse" } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ApiDocsFailureResponse" } + } + } + } + }, + "security": [{ "bearer_token": [] }] + } + }, "/deepfence/user": { "delete": { "tags": ["User"], @@ -11637,6 +11689,11 @@ "type": "object", "properties": { "report_id": { "type": "string" } } }, + "ModelGetAuditLogsRequest": { + "required": ["window"], + "type": "object", + "properties": { "window": { "$ref": "#/components/schemas/ModelFetchWindow" } } + }, "ModelHost": { "required": [ "node_id", @@ -12666,10 +12723,14 @@ } }, "ModelStopScanRequest": { - "required": ["scan_id", "scan_type"], + "required": ["scan_ids", "scan_type"], "type": "object", "properties": { - "scan_id": { "type": "string" }, + "scan_ids": { + "type": "array", + "items": { "type": "string" }, + "nullable": true + }, "scan_type": { "enum": [ "SecretScan", diff --git a/deepfence_frontend/apps/dashboard/src/api/api.ts b/deepfence_frontend/apps/dashboard/src/api/api.ts index 8df61db920..9cf08f43cd 100644 --- a/deepfence_frontend/apps/dashboard/src/api/api.ts +++ b/deepfence_frontend/apps/dashboard/src/api/api.ts @@ -83,6 +83,7 @@ export function getVulnerabilityApiClient() { return { startVulnerabilityScan: vulnerabilityApi.startVulnerabilityScan.bind(vulnerabilityApi), + stopVulnerabilityScan: vulnerabilityApi.stopVulnerabilityScan.bind(vulnerabilityApi), resultVulnerabilityScan: vulnerabilityApi.resultsVulnerabilityScans.bind(vulnerabilityApi), resultCountVulnerabilityScan: @@ -100,6 +101,7 @@ export function getSecretApiClient() { const secretApi = new SecretScanApi(configuration); return { startSecretScan: secretApi.startSecretScan.bind(secretApi), + stopSecretScan: secretApi.stopSecretScan.bind(secretApi), resultSecretScan: secretApi.resultsSecretScan.bind(secretApi), resultCountSecretScan: secretApi.countResultsSecretScan.bind(secretApi), statusSecretScan: secretApi.statusSecretScan.bind(secretApi), @@ -113,6 +115,7 @@ export function getComplianceApiClient() { const complianceApi = new ComplianceApi(configuration); return { startComplianceScan: complianceApi.startComplianceScan.bind(complianceApi), + stopComplianceScan: complianceApi.stopComplianceScan.bind(complianceApi), statusComplianceScan: complianceApi.statusComplianceScan.bind(complianceApi), resultComplianceScan: complianceApi.resultsComplianceScan.bind(complianceApi), resultCountComplianceScan: @@ -157,6 +160,7 @@ export function getMalwareApiClient() { const malwareApi = new MalwareScanApi(configuration); return { startMalwareScan: malwareApi.startMalwareScan.bind(malwareApi), + stopMalwareScan: malwareApi.stopMalwareScan.bind(malwareApi), resultMalwareScan: malwareApi.resultsMalwareScan.bind(malwareApi), resultCountMalwareScan: malwareApi.countResultsMalwareScan.bind(malwareApi), statusMalwareScan: malwareApi.statusMalwareScan.bind(malwareApi), @@ -217,6 +221,7 @@ export function getScanResultsApiClient() { return { deleteScanResult: scanResultsApi.deleteScanResult.bind(scanResultsApi), downloadScanResultsForScanID: scanResultsApi.downloadScanResults.bind(scanResultsApi), + bulkDeleteScans: scanResultsApi.bulkDeleteScans.bind(scanResultsApi), deleteScanResultsForScanID: scanResultsApi.deleteScanResultsForScanID.bind(scanResultsApi), notifyScanResult: scanResultsApi.notifyScanResult.bind(scanResultsApi), @@ -224,7 +229,6 @@ export function getScanResultsApiClient() { unmaskScanResult: scanResultsApi.unmaskScanResult.bind(scanResultsApi), getAllNodesInScanResults: scanResultsApi.getAllNodesInScanResults.bind(scanResultsApi), - bulkDeleteScansHistory: scanResultsApi.bulkDeleteScans.bind(scanResultsApi), }; } @@ -316,7 +320,8 @@ export function getSettingsApiClient() { return { getSettings: settingsApi.getSettings.bind(settingsApi), updateSettings: settingsApi.updateSetting.bind(settingsApi), - getUserActivityLogs: settingsApi.getUserActivityLogs.bind(settingsApi), + getUserActivityLogs: settingsApi.getUserAuditLogs.bind(settingsApi), + getUserActivityLogCount: settingsApi.getUserAuditLogsCount.bind(settingsApi), getEmailConfiguration: settingsApi.getEmailConfiguration.bind(settingsApi), addEmailConfiguration: settingsApi.addEmailConfiguration.bind(settingsApi), deleteEmailConfiguration: settingsApi.deleteEmailConfiguration.bind(settingsApi), diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/.openapi-generator/FILES b/deepfence_frontend/apps/dashboard/src/api/generated/.openapi-generator/FILES index e6109f8668..c6c63c3707 100644 --- a/deepfence_frontend/apps/dashboard/src/api/generated/.openapi-generator/FILES +++ b/deepfence_frontend/apps/dashboard/src/api/generated/.openapi-generator/FILES @@ -110,6 +110,7 @@ models/ModelFiltersReq.ts models/ModelFiltersResult.ts models/ModelGenerateReportReq.ts models/ModelGenerateReportResp.ts +models/ModelGetAuditLogsRequest.ts models/ModelHost.ts models/ModelImageStub.ts models/ModelInitAgentReq.ts diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/apis/SettingsApi.ts b/deepfence_frontend/apps/dashboard/src/api/generated/apis/SettingsApi.ts index c20f3419ca..233cec44cb 100644 --- a/deepfence_frontend/apps/dashboard/src/api/generated/apis/SettingsApi.ts +++ b/deepfence_frontend/apps/dashboard/src/api/generated/apis/SettingsApi.ts @@ -20,12 +20,14 @@ import type { ModelAddScheduledTaskRequest, ModelEmailConfigurationAdd, ModelEmailConfigurationResp, + ModelGetAuditLogsRequest, ModelMessageResponse, ModelSettingUpdateRequest, ModelSettingsResponse, ModelUpdateScheduledTaskRequest, PostgresqlDbGetAuditLogsRow, PostgresqlDbScheduler, + SearchSearchCountResp, } from '../models'; import { ApiDocsBadRequestResponseFromJSON, @@ -38,6 +40,8 @@ import { ModelEmailConfigurationAddToJSON, ModelEmailConfigurationRespFromJSON, ModelEmailConfigurationRespToJSON, + ModelGetAuditLogsRequestFromJSON, + ModelGetAuditLogsRequestToJSON, ModelMessageResponseFromJSON, ModelMessageResponseToJSON, ModelSettingUpdateRequestFromJSON, @@ -50,6 +54,8 @@ import { PostgresqlDbGetAuditLogsRowToJSON, PostgresqlDbSchedulerFromJSON, PostgresqlDbSchedulerToJSON, + SearchSearchCountRespFromJSON, + SearchSearchCountRespToJSON, } from '../models'; export interface AddEmailConfigurationRequest { @@ -64,6 +70,10 @@ export interface DeleteEmailConfigurationRequest { configId: string; } +export interface GetUserAuditLogsRequest { + modelGetAuditLogsRequest?: ModelGetAuditLogsRequest; +} + export interface UpdateScheduledTaskRequest { id: number; modelUpdateScheduledTaskRequest?: ModelUpdateScheduledTaskRequest; @@ -179,19 +189,35 @@ export interface SettingsApiInterface { getSettings(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * Get activity logs for all users - * @summary Get activity logs + * Get audit logs for all users + * @summary Get user audit logs + * @param {ModelGetAuditLogsRequest} [modelGetAuditLogsRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SettingsApiInterface + */ + getUserAuditLogsRaw(requestParameters: GetUserAuditLogsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>>; + + /** + * Get audit logs for all users + * Get user audit logs + */ + getUserAuditLogs(requestParameters: GetUserAuditLogsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + + /** + * Get user audit logs count + * @summary Get user audit logs count * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SettingsApiInterface */ - getUserActivityLogsRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>>; + getUserAuditLogsCountRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; /** - * Get activity logs for all users - * Get activity logs + * Get user audit logs count + * Get user audit logs count */ - getUserActivityLogs(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; + getUserAuditLogsCount(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; /** * Update scheduled task @@ -475,14 +501,16 @@ export class SettingsApi extends runtime.BaseAPI implements SettingsApiInterface } /** - * Get activity logs for all users - * Get activity logs + * Get audit logs for all users + * Get user audit logs */ - async getUserActivityLogsRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + async getUserAuditLogsRaw(requestParameters: GetUserAuditLogsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; + headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { const token = this.configuration.accessToken; const tokenString = await token("bearer_token", []); @@ -492,21 +520,58 @@ export class SettingsApi extends runtime.BaseAPI implements SettingsApiInterface } } const response = await this.request({ - path: `/deepfence/settings/user-activity-log`, - method: 'GET', + path: `/deepfence/settings/user-audit-log`, + method: 'POST', headers: headerParameters, query: queryParameters, + body: ModelGetAuditLogsRequestToJSON(requestParameters.modelGetAuditLogsRequest), }, initOverrides); return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(PostgresqlDbGetAuditLogsRowFromJSON)); } /** - * Get activity logs for all users - * Get activity logs + * Get audit logs for all users + * Get user audit logs + */ + async getUserAuditLogs(requestParameters: GetUserAuditLogsRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.getUserAuditLogsRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Get user audit logs count + * Get user audit logs count + */ + async getUserAuditLogsCountRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("bearer_token", []); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + const response = await this.request({ + path: `/deepfence/settings/user-audit-log/count`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => SearchSearchCountRespFromJSON(jsonValue)); + } + + /** + * Get user audit logs count + * Get user audit logs count */ - async getUserActivityLogs(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - const response = await this.getUserActivityLogsRaw(initOverrides); + async getUserAuditLogsCount(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.getUserAuditLogsCountRaw(initOverrides); return await response.value(); } diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelGetAuditLogsRequest.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelGetAuditLogsRequest.ts new file mode 100644 index 0000000000..1cf4b64f7f --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelGetAuditLogsRequest.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Deepfence ThreatMapper + * Deepfence Runtime API provides programmatic control over Deepfence microservice securing your container, kubernetes and cloud deployments. The API abstracts away underlying infrastructure details like cloud provider, container distros, container orchestrator and type of deployment. This is one uniform API to manage and control security alerts, policies and response to alerts for microservices running anywhere i.e. managed pure greenfield container deployments or a mix of containers, VMs and serverless paradigms like AWS Fargate. + * + * The version of the OpenAPI document: 2.0.0 + * Contact: community@deepfence.io + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { ModelFetchWindow } from './ModelFetchWindow'; +import { + ModelFetchWindowFromJSON, + ModelFetchWindowFromJSONTyped, + ModelFetchWindowToJSON, +} from './ModelFetchWindow'; + +/** + * + * @export + * @interface ModelGetAuditLogsRequest + */ +export interface ModelGetAuditLogsRequest { + /** + * + * @type {ModelFetchWindow} + * @memberof ModelGetAuditLogsRequest + */ + window: ModelFetchWindow; +} + +/** + * Check if a given object implements the ModelGetAuditLogsRequest interface. + */ +export function instanceOfModelGetAuditLogsRequest(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "window" in value; + + return isInstance; +} + +export function ModelGetAuditLogsRequestFromJSON(json: any): ModelGetAuditLogsRequest { + return ModelGetAuditLogsRequestFromJSONTyped(json, false); +} + +export function ModelGetAuditLogsRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): ModelGetAuditLogsRequest { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'window': ModelFetchWindowFromJSON(json['window']), + }; +} + +export function ModelGetAuditLogsRequestToJSON(value?: ModelGetAuditLogsRequest | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'window': ModelFetchWindowToJSON(value.window), + }; +} + diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelStopScanRequest.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelStopScanRequest.ts index ffe64c3585..029557b8ed 100644 --- a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelStopScanRequest.ts +++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelStopScanRequest.ts @@ -21,10 +21,10 @@ import { exists, mapValues } from '../runtime'; export interface ModelStopScanRequest { /** * - * @type {string} + * @type {Array} * @memberof ModelStopScanRequest */ - scan_id: string; + scan_ids: Array | null; /** * * @type {string} @@ -52,7 +52,7 @@ export type ModelStopScanRequestScanTypeEnum = typeof ModelStopScanRequestScanTy */ export function instanceOfModelStopScanRequest(value: object): boolean { let isInstance = true; - isInstance = isInstance && "scan_id" in value; + isInstance = isInstance && "scan_ids" in value; isInstance = isInstance && "scan_type" in value; return isInstance; @@ -68,7 +68,7 @@ export function ModelStopScanRequestFromJSONTyped(json: any, ignoreDiscriminator } return { - 'scan_id': json['scan_id'], + 'scan_ids': json['scan_ids'], 'scan_type': json['scan_type'], }; } @@ -82,7 +82,7 @@ export function ModelStopScanRequestToJSON(value?: ModelStopScanRequest | null): } return { - 'scan_id': value.scan_id, + 'scan_ids': value.scan_ids, 'scan_type': value.scan_type, }; } diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/index.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/index.ts index 861a470242..febc34c203 100644 --- a/deepfence_frontend/apps/dashboard/src/api/generated/models/index.ts +++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/index.ts @@ -86,6 +86,7 @@ export * from './ModelFiltersReq'; export * from './ModelFiltersResult'; export * from './ModelGenerateReportReq'; export * from './ModelGenerateReportResp'; +export * from './ModelGetAuditLogsRequest'; export * from './ModelHost'; export * from './ModelImageStub'; export * from './ModelInitAgentReq'; diff --git a/deepfence_frontend/apps/dashboard/src/components/ConfigureScanModal.tsx b/deepfence_frontend/apps/dashboard/src/components/ConfigureScanModal.tsx index 64278b80ca..d03d6cf58e 100644 --- a/deepfence_frontend/apps/dashboard/src/components/ConfigureScanModal.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/ConfigureScanModal.tsx @@ -27,6 +27,7 @@ import { ScanTypeEnum } from '@/types/common'; export interface ConfigureScanModalProps { open: boolean; onOpenChange: (open: boolean) => void; + onSuccess?: () => void; scanOptions?: { showAdvancedOptions: boolean } & ( | { scanType: typeof ScanTypeEnum.VulnerabilityScan; @@ -66,6 +67,7 @@ const Header = ({ title }: { title: string }) => { }; export const ConfigureScanModal = ({ open, + onSuccess, onOpenChange, scanOptions, }: ConfigureScanModalProps) => { @@ -95,7 +97,10 @@ export const ConfigureScanModal = ({ onOpenChange(false)} + onSuccess={() => { + onOpenChange(false); + onSuccess?.(); + }} onCancel={() => onOpenChange(false)} /> )} @@ -103,7 +108,10 @@ export const ConfigureScanModal = ({ onOpenChange(false)} + onSuccess={() => { + onOpenChange(false); + onSuccess?.(); + }} onCancel={() => onOpenChange(false)} /> )} @@ -111,7 +119,10 @@ export const ConfigureScanModal = ({ onOpenChange(false)} + onSuccess={() => { + onOpenChange(false); + onSuccess?.(); + }} onCancel={() => onOpenChange(false)} /> )} @@ -120,7 +131,10 @@ export const ConfigureScanModal = ({ onOpenChange(false)} + onSuccess={() => { + onOpenChange(false); + onSuccess?.(); + }} onCancel={() => onOpenChange(false)} /> )} diff --git a/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx b/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx index a8a0d53e40..f0bbb6ab87 100644 --- a/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx @@ -13,6 +13,8 @@ import { isScanComplete, isScanFailed, isScanInProgress, + isScanStopped, + isScanStopping, } from '@/utils/scan'; export const ScanStatusBadge = ({ @@ -46,7 +48,7 @@ export const ScanStatusBadge = ({ } else if (isScanFailed(status)) { return (
- + {!justIcon ? : null} @@ -67,6 +69,26 @@ export const ScanStatusBadge = ({ + {!justIcon ? : null} +
+ ); + } else if (isScanStopping(status)) { + return ( +
+ + + + + {!justIcon ? : null} +
+ ); + } else if (isScanStopped(status)) { + return ( +
+ + + + {!justIcon ? : null}
); diff --git a/deepfence_frontend/apps/dashboard/src/components/ScanStatusMessage.tsx b/deepfence_frontend/apps/dashboard/src/components/ScanStatusMessage.tsx index 328b28bff4..c985ece809 100644 --- a/deepfence_frontend/apps/dashboard/src/components/ScanStatusMessage.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/ScanStatusMessage.tsx @@ -14,6 +14,17 @@ export const ScanStatusInProgress = () => { ); }; +export const ScanStatusStopping = () => { + return ( +
+ + + Scan cancelling + +
+ ); +}; + export const ScanStatusInError = ({ errorMessage }: { errorMessage: string }) => { return (
@@ -36,6 +47,28 @@ export const ScanStatusInError = ({ errorMessage }: { errorMessage: string }) => ); }; +export const ScanStatusStopped = ({ errorMessage }: { errorMessage: string }) => { + return ( +
+ {errorMessage ? ( + +
+ +
+
+ ) : ( +
+ +
+ )} + +
+ Scan cancelled +
+
+ ); +}; + export const ScanStatusNoData = ({ message }: { message?: string }) => { return (
diff --git a/deepfence_frontend/apps/dashboard/src/components/icons/common/ScanStatuses.tsx b/deepfence_frontend/apps/dashboard/src/components/icons/common/ScanStatuses.tsx index e069c3afd3..1da6537a5c 100644 --- a/deepfence_frontend/apps/dashboard/src/components/icons/common/ScanStatuses.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/icons/common/ScanStatuses.tsx @@ -30,7 +30,7 @@ export const ErrorIcon = () => { fillRule="evenodd" clipRule="evenodd" d="M9 3C5.68629 3 3 5.68629 3 9C3 12.3137 5.68629 15 9 15C12.3137 15 15 12.3137 15 9C15 7.4087 14.3679 5.88258 13.2426 4.75736C12.1174 3.63214 10.5913 3 9 3ZM8.255 6C8.255 5.58579 8.59079 5.25 9.005 5.25C9.41921 5.25 9.755 5.58579 9.755 6V9.445C9.755 9.85921 9.41921 10.195 9.005 10.195C8.59079 10.195 8.255 9.85921 8.255 9.445V6ZM8.14 11.89C8.14 12.365 8.52503 12.75 9 12.75C9.47496 12.75 9.86 12.365 9.86 11.89C9.86 11.415 9.47496 11.03 9 11.03C8.52503 11.03 8.14 11.415 8.14 11.89Z" - fill="#E0516D" + fill="currentColor" /> ); diff --git a/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/MalwareScanConfigureForm.tsx b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/MalwareScanConfigureForm.tsx index ce42bbcc20..f105ee621f 100644 --- a/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/MalwareScanConfigureForm.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/MalwareScanConfigureForm.tsx @@ -7,6 +7,7 @@ import { getMalwareApiClient } from '@/api/api'; import { ModelMalwareScanTriggerReq, ModelNodeIdentifierNodeTypeEnum, + ReportersContainsFilter, } from '@/api/generated'; import { invalidateAllQueries } from '@/queries'; import { MalwareScanNodeTypeEnum } from '@/types/common'; @@ -18,19 +19,23 @@ export type MalwareScanConfigureFormProps = { showAdvancedOptions: boolean; data: | { - nodeIds: string[]; - nodeType: - | MalwareScanNodeTypeEnum.host - | MalwareScanNodeTypeEnum.kubernetes_cluster - | MalwareScanNodeTypeEnum.registry - | MalwareScanNodeTypeEnum.container - | MalwareScanNodeTypeEnum.imageTag - | MalwareScanNodeTypeEnum.pod; + nodes: { + nodeId: string; + nodeType: + | MalwareScanNodeTypeEnum.host + | MalwareScanNodeTypeEnum.kubernetes_cluster + | MalwareScanNodeTypeEnum.registry + | MalwareScanNodeTypeEnum.container + | MalwareScanNodeTypeEnum.imageTag + | MalwareScanNodeTypeEnum.pod; + }[]; } | { - nodeIds: string[]; + nodes: { + nodeId: string; + nodeType: MalwareScanNodeTypeEnum.image; + }[]; images: string[]; - nodeType: MalwareScanNodeTypeEnum.image; }; onSuccess: (data?: { nodeType: string; bulkScanId: string }) => void; onCancel?: () => void; @@ -51,35 +56,53 @@ export const scanMalwareApiAction = async ({ const formData = await request.formData(); const nodeIds = formData.get('_nodeIds')?.toString().split(',') ?? []; const _images = formData.get('_images')?.toString().split(',') ?? []; - const nodeType = formData.get('_nodeType')?.toString() ?? ''; + const nodeTypes = formData.get('_nodeTypes')?.toString().split(',') ?? []; const imageTag = formData.get('imageTag')?.toString() ?? ''; - let filter_in = null; - let _nodeType = nodeType; - - if (nodeType === MalwareScanNodeTypeEnum.imageTag || nodeType === 'container_image') { - _nodeType = MalwareScanNodeTypeEnum.image; - } else if (nodeType === MalwareScanNodeTypeEnum.kubernetes_cluster) { - _nodeType = 'cluster'; - } else if (nodeType === MalwareScanNodeTypeEnum.image) { - _nodeType = 'registry'; - if (imageTag !== '') { - filter_in = { - docker_image_name: _images, - docker_image_tag: [imageTag], - }; - } else { - filter_in = { - docker_image_name: _images, - }; + const getNodeType = (nodeType: MalwareScanNodeTypeEnum | 'container_image') => { + let _nodeType = nodeType as ModelNodeIdentifierNodeTypeEnum; + if (nodeType === MalwareScanNodeTypeEnum.imageTag || nodeType === 'container_image') { + _nodeType = MalwareScanNodeTypeEnum.image; + } else if (nodeType === MalwareScanNodeTypeEnum.kubernetes_cluster) { + _nodeType = 'cluster'; + } else if (nodeType === MalwareScanNodeTypeEnum.image) { + _nodeType = 'registry'; } - } else if (nodeType === MalwareScanNodeTypeEnum.registry) { - if (imageTag !== '') { - filter_in = { - docker_image_tag: [imageTag], - }; + return _nodeType; + }; + + const createFilter = ( + nodeType: MalwareScanNodeTypeEnum, + ): ReportersContainsFilter['filter_in'] => { + if (nodeType === MalwareScanNodeTypeEnum.image) { + if (imageTag !== '') { + return { + docker_image_name: _images, + docker_image_tag: [imageTag], + }; + } else { + return { + docker_image_name: _images, + }; + } + } else if (nodeType === MalwareScanNodeTypeEnum.registry) { + if (imageTag !== '') { + return { + docker_image_tag: [imageTag], + }; + } } + return null; + }; + + const nodeType = nodeTypes[0]; + let filter_in = null; + if ( + nodeType === MalwareScanNodeTypeEnum.image || + nodeType === MalwareScanNodeTypeEnum.registry + ) { + filter_in = createFilter(nodeType); } const requestBody: ModelMalwareScanTriggerReq = { @@ -92,15 +115,18 @@ export const scanMalwareApiAction = async ({ filter_in, }, }, - node_ids: nodeIds.map((nodeId) => ({ + node_ids: nodeIds.map((nodeId, index) => ({ node_id: nodeId, - node_type: _nodeType as ModelNodeIdentifierNodeTypeEnum, + node_type: getNodeType( + nodeTypes[index] as MalwareScanNodeTypeEnum, + ) as ModelNodeIdentifierNodeTypeEnum, })), }; const startMalwareScanApi = apiWrapper({ fn: getMalwareApiClient().startMalwareScan, }); + const startMalwareScanResponse = await startMalwareScanApi({ modelMalwareScanTriggerReq: requestBody, }); @@ -129,7 +155,7 @@ export const scanMalwareApiAction = async ({ success: true, data: { bulkScanId: startMalwareScanResponse.value.bulk_scan_id, - nodeType, + nodeType, // for onboard page redirection }, }; }; @@ -155,22 +181,37 @@ export const MalwareScanConfigureForm = ({ } }, [fetcherData, state]); + // in case of registry scan nodeType will be always be of same type + const nodeType = data.nodes[0].nodeType; + return ( - - - {data.nodeType === MalwareScanNodeTypeEnum.image && data.images && ( + node.nodeId).join(',')} + /> + node.nodeType).join(',')} + /> + {'images' in data && ( )}
{wantAdvanceOptions && - isNodeTypeARegistryType(data.nodeType) && - !isNodeTypeARegistryTagType(data.nodeType) && ( // remove isNodeTypeARegistryType when we have other advance options + isNodeTypeARegistryType(nodeType) && + !isNodeTypeARegistryTagType(nodeType) && ( // remove isNodeTypeARegistryType when we have other advance options
Advanced Options
)} {!wantAdvanceOptions && ( @@ -178,12 +219,12 @@ export const MalwareScanConfigureForm = ({ Click on start scan to find malwares

)} - {wantAdvanceOptions && isNodeTypeARegistryTagType(data.nodeType) && ( + {wantAdvanceOptions && isNodeTypeARegistryTagType(nodeType) && (

Click on start scan to find malwares

)} - {wantAdvanceOptions && !isNodeTypeARegistryType(data.nodeType) && ( + {wantAdvanceOptions && !isNodeTypeARegistryType(nodeType) && (

Click on start scan to find malwares

@@ -191,21 +232,20 @@ export const MalwareScanConfigureForm = ({
{wantAdvanceOptions ? (
- {isNodeTypeARegistryType(data.nodeType) && - !isNodeTypeARegistryTagType(data.nodeType) && ( - { - setImageTag(value); - }} - /> - )} + {isNodeTypeARegistryType(nodeType) && !isNodeTypeARegistryTagType(nodeType) && ( + { + setImageTag(value); + }} + /> + )}
) : null} diff --git a/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/SecretScanConfigureForm.tsx b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/SecretScanConfigureForm.tsx index 7e69678d47..c3d0e685d6 100644 --- a/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/SecretScanConfigureForm.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/SecretScanConfigureForm.tsx @@ -7,6 +7,7 @@ import { getSecretApiClient } from '@/api/api'; import { ModelNodeIdentifierNodeTypeEnum, ModelSecretScanTriggerReq, + ReportersContainsFilter, } from '@/api/generated'; import { invalidateAllQueries } from '@/queries'; import { SecretScanNodeTypeEnum } from '@/types/common'; @@ -18,19 +19,23 @@ export type SecretScanConfigureFormProps = { showAdvancedOptions: boolean; data: | { - nodeIds: string[]; - nodeType: - | SecretScanNodeTypeEnum.host - | SecretScanNodeTypeEnum.kubernetes_cluster - | SecretScanNodeTypeEnum.registry - | SecretScanNodeTypeEnum.container - | SecretScanNodeTypeEnum.imageTag - | SecretScanNodeTypeEnum.pod; + nodes: { + nodeId: string; + nodeType: + | SecretScanNodeTypeEnum.host + | SecretScanNodeTypeEnum.kubernetes_cluster + | SecretScanNodeTypeEnum.registry + | SecretScanNodeTypeEnum.container + | SecretScanNodeTypeEnum.imageTag + | SecretScanNodeTypeEnum.pod; + }[]; } | { - nodeIds: string[]; + nodes: { + nodeId: string; + nodeType: SecretScanNodeTypeEnum.image; + }[]; images: string[]; - nodeType: SecretScanNodeTypeEnum.image; }; onSuccess: (data?: { nodeType: string; bulkScanId: string }) => void; onCancel?: () => void; @@ -51,35 +56,53 @@ export const scanSecretApiAction = async ({ const formData = await request.formData(); const nodeIds = formData.get('_nodeIds')?.toString().split(',') ?? []; const _images = formData.get('_images')?.toString().split(',') ?? []; - const nodeType = formData.get('_nodeType')?.toString() ?? ''; + const nodeTypes = formData.get('_nodeTypes')?.toString().split(',') ?? []; const imageTag = formData.get('imageTag')?.toString() ?? ''; - let filter_in = null; - let _nodeType = nodeType; - - if (nodeType === SecretScanNodeTypeEnum.imageTag || nodeType === 'container_image') { - _nodeType = SecretScanNodeTypeEnum.image; - } else if (nodeType === SecretScanNodeTypeEnum.kubernetes_cluster) { - _nodeType = 'cluster'; - } else if (nodeType === SecretScanNodeTypeEnum.image) { - _nodeType = 'registry'; - if (imageTag !== '') { - filter_in = { - docker_image_name: _images, - docker_image_tag: [imageTag], - }; - } else { - filter_in = { - docker_image_name: _images, - }; + const getNodeType = (nodeType: SecretScanNodeTypeEnum | 'container_image') => { + let _nodeType = nodeType as ModelNodeIdentifierNodeTypeEnum; + if (nodeType === SecretScanNodeTypeEnum.imageTag || nodeType === 'container_image') { + _nodeType = SecretScanNodeTypeEnum.image; + } else if (nodeType === SecretScanNodeTypeEnum.kubernetes_cluster) { + _nodeType = 'cluster'; + } else if (nodeType === SecretScanNodeTypeEnum.image) { + _nodeType = 'registry'; } - } else if (nodeType === SecretScanNodeTypeEnum.registry) { - if (imageTag !== '') { - filter_in = { - docker_image_tag: [imageTag], - }; + return _nodeType; + }; + + const createFilter = ( + nodeType: SecretScanNodeTypeEnum, + ): ReportersContainsFilter['filter_in'] => { + if (nodeType === SecretScanNodeTypeEnum.image) { + if (imageTag !== '') { + return { + docker_image_name: _images, + docker_image_tag: [imageTag], + }; + } else { + return { + docker_image_name: _images, + }; + } + } else if (nodeType === SecretScanNodeTypeEnum.registry) { + if (imageTag !== '') { + return { + docker_image_tag: [imageTag], + }; + } } + return null; + }; + + const nodeType = nodeTypes[0]; + let filter_in = null; + if ( + nodeType === SecretScanNodeTypeEnum.image || + nodeType === SecretScanNodeTypeEnum.registry + ) { + filter_in = createFilter(nodeType); } const requestBody: ModelSecretScanTriggerReq = { @@ -92,14 +115,17 @@ export const scanSecretApiAction = async ({ filter_in, }, }, - node_ids: nodeIds.map((nodeId) => ({ + node_ids: nodeIds.map((nodeId, index) => ({ node_id: nodeId, - node_type: _nodeType as ModelNodeIdentifierNodeTypeEnum, + node_type: getNodeType( + nodeTypes[index] as SecretScanNodeTypeEnum, + ) as ModelNodeIdentifierNodeTypeEnum, })), }; const startSecretScanApi = apiWrapper({ fn: getSecretApiClient().startSecretScan, }); + const startSecretScanResponse = await startSecretScanApi({ modelSecretScanTriggerReq: requestBody, }); @@ -128,7 +154,7 @@ export const scanSecretApiAction = async ({ success: true, data: { bulkScanId: startSecretScanResponse.value.bulk_scan_id, - nodeType, + nodeType, // for onboard page redirection }, }; }; @@ -154,22 +180,37 @@ export const SecretScanConfigureForm = ({ } }, [fetcherData, state]); + // in case of registry scan nodeType will be always be of same type + const nodeType = data.nodes[0].nodeType; + return ( - - - {data.nodeType === SecretScanNodeTypeEnum.image && data.images && ( + node.nodeId).join(',')} + /> + node.nodeType).join(',')} + /> + {'images' in data && ( )}
{wantAdvanceOptions && - isNodeTypeARegistryType(data.nodeType) && - !isNodeTypeARegistryTagType(data.nodeType) && ( // remove isNodeTypeARegistryType when we have other advance options + isNodeTypeARegistryType(nodeType) && + !isNodeTypeARegistryTagType(nodeType) && ( // remove isNodeTypeARegistryType when we have other advance options
Advanced Options
)} {!wantAdvanceOptions && ( @@ -177,12 +218,12 @@ export const SecretScanConfigureForm = ({ Click on start scan to find secrets

)} - {wantAdvanceOptions && isNodeTypeARegistryTagType(data.nodeType) && ( + {wantAdvanceOptions && isNodeTypeARegistryTagType(nodeType) && (

Click on start scan to find secrets

)} - {wantAdvanceOptions && !isNodeTypeARegistryType(data.nodeType) && ( + {wantAdvanceOptions && !isNodeTypeARegistryType(nodeType) && (

Click on start scan to find secrets

@@ -190,21 +231,20 @@ export const SecretScanConfigureForm = ({
{wantAdvanceOptions ? (
- {isNodeTypeARegistryType(data.nodeType) && - !isNodeTypeARegistryTagType(data.nodeType) && ( - { - setImageTag(value); - }} - /> - )} + {isNodeTypeARegistryType(nodeType) && !isNodeTypeARegistryTagType(nodeType) && ( + { + setImageTag(value); + }} + /> + )}
) : null} diff --git a/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/StopScanForm.tsx b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/StopScanForm.tsx new file mode 100644 index 0000000000..4eeb2d6a48 --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/StopScanForm.tsx @@ -0,0 +1,165 @@ +import { useEffect } from 'react'; +import { ActionFunctionArgs, generatePath, useFetcher } from 'react-router-dom'; +import { Button, Modal } from 'ui-components'; + +import { + getComplianceApiClient, + getMalwareApiClient, + getSecretApiClient, + getVulnerabilityApiClient, +} from '@/api/api'; +import { ModelNodeIdentifierNodeTypeEnum } from '@/api/generated'; +import { ErrorStandardLineIcon } from '@/components/icons/common/ErrorStandardLine'; +import { SuccessModalContent } from '@/features/settings/components/SuccessModalContent'; +import { invalidateAllQueries } from '@/queries'; +import { ScanTypeEnum, VulnerabilityScanNodeTypeEnum } from '@/types/common'; +import { get403Message } from '@/utils/403'; +import { apiWrapper } from '@/utils/api'; + +export enum ActionEnumType { + STOP_SCAN = 'stop_scan', +} +interface IActionData { + action: ActionEnumType; + success: boolean; + message?: string; +} + +export const stopScanApiFunctionMap = { + [ScanTypeEnum.VulnerabilityScan]: getVulnerabilityApiClient().stopVulnerabilityScan, + [ScanTypeEnum.SecretScan]: getSecretApiClient().stopSecretScan, + [ScanTypeEnum.MalwareScan]: getMalwareApiClient().stopMalwareScan, + [ScanTypeEnum.ComplianceScan]: getComplianceApiClient().stopComplianceScan, + [ScanTypeEnum.CloudComplianceScan]: getComplianceApiClient().stopComplianceScan, +}; + +export const actionStopScan = async ({ + params, + request, +}: ActionFunctionArgs): Promise<{ success?: boolean; message?: string }> => { + const scanType = params?.scanType?.toString() as ScanTypeEnum; + const formData = await request.formData(); + const scanIds = formData.getAll('scanIds[]') as string[]; + + if (!scanType || scanIds.length === 0) { + console.error('Scan id and Scan Type are required for stoping scan'); + throw new Error('Scan id and Scan Type are required for stoping scan'); + } + + const stopScanApi = apiWrapper({ + fn: stopScanApiFunctionMap[scanType], + }); + const result = await stopScanApi({ + modelStopScanRequest: { + scan_ids: scanIds, + scan_type: scanType, + }, + }); + if (!result.ok) { + if (result.error.response.status === 400 || result.error.response.status === 409) { + return { + success: false, + message: result.error.message ?? '', + }; + } else if (result.error.response.status === 403) { + const message = await get403Message(result.error); + return { + success: false, + message, + }; + } + throw result.error; + } + + invalidateAllQueries(); + return { + success: true, + }; +}; + +export const StopScanForm = ({ + open, + scanIds, + closeModal, + scanType, + onCancelScanSuccess, +}: { + open: boolean; + scanIds: string[]; + scanType: ScanTypeEnum; + closeModal: React.Dispatch>; + onCancelScanSuccess?: () => void; +}) => { + const fetcher = useFetcher(); + + useEffect(() => { + if (fetcher.state === 'idle' && fetcher.data?.success) { + onCancelScanSuccess?.(); + } + }, [fetcher]); + + return ( + closeModal(false)} + title={ + !fetcher.data?.success ? ( +
+ + + + Cancel {scanIds.length > 1 ? 'scans' : 'scan'} +
+ ) : undefined + } + footer={ + !fetcher.data?.success ? ( +
+ + +
+ ) : undefined + } + > + {!fetcher.data?.success ? ( +
+ The selected scan will be cancelled. +
+ Are you sure you want to cancel? + {fetcher.data?.message && ( +

{fetcher.data?.message}

+ )} +
+ ) : ( + + )} +
+ ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/VulnerabilityScanConfigureForm.tsx b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/VulnerabilityScanConfigureForm.tsx index 7093b7d42d..ab3d2a6fcf 100644 --- a/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/VulnerabilityScanConfigureForm.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/scan-configure-forms/VulnerabilityScanConfigureForm.tsx @@ -8,6 +8,7 @@ import { ModelNodeIdentifierNodeTypeEnum, ModelVulnerabilityScanConfigLanguageLanguageEnum, ModelVulnerabilityScanTriggerReq, + ReportersContainsFilter, } from '@/api/generated'; import { invalidateAllQueries } from '@/queries'; import { VulnerabilityScanNodeTypeEnum } from '@/types/common'; @@ -82,20 +83,25 @@ export type VulnerabilityScanConfigureFormProps = { showAdvancedOptions: boolean; data: | { - nodeIds: string[]; - nodeType: - | VulnerabilityScanNodeTypeEnum.host - | VulnerabilityScanNodeTypeEnum.kubernetes_cluster - | VulnerabilityScanNodeTypeEnum.registry - | VulnerabilityScanNodeTypeEnum.container - | VulnerabilityScanNodeTypeEnum.imageTag - | VulnerabilityScanNodeTypeEnum.pod; + nodes: { + nodeId: string; + nodeType: + | VulnerabilityScanNodeTypeEnum.host + | VulnerabilityScanNodeTypeEnum.kubernetes_cluster + | VulnerabilityScanNodeTypeEnum.registry + | VulnerabilityScanNodeTypeEnum.container + | VulnerabilityScanNodeTypeEnum.imageTag + | VulnerabilityScanNodeTypeEnum.pod; + }[]; } | { - nodeIds: string[]; + nodes: { + nodeId: string; + nodeType: VulnerabilityScanNodeTypeEnum.image; + }[]; images: string[]; - nodeType: VulnerabilityScanNodeTypeEnum.image; }; + onSuccess: (data?: { nodeType: string; bulkScanId: string }) => void; onCancel?: () => void; }; @@ -115,41 +121,50 @@ export const scanVulnerabilityApiAction = async ({ const formData = await request.formData(); const nodeIds = formData.get('_nodeIds')?.toString().split(',') ?? []; const _images = formData.get('_images')?.toString().split(',') ?? []; - const nodeType = formData.get('_nodeType')?.toString() ?? ''; + const nodeTypes = formData.get('_nodeTypes')?.toString().split(',') ?? []; const imageTag = formData.get('imageTag')?.toString() ?? ''; const packages = formData.getAll('packages'); const binaries = formData.getAll('binaries'); - let filter_in = null; - let _nodeType = nodeType; - - if ( - nodeType === VulnerabilityScanNodeTypeEnum.imageTag || - nodeType === 'container_image' - ) { - _nodeType = VulnerabilityScanNodeTypeEnum.image; - } else if (nodeType === VulnerabilityScanNodeTypeEnum.kubernetes_cluster) { - _nodeType = 'cluster'; - } else if (nodeType === VulnerabilityScanNodeTypeEnum.image) { - _nodeType = 'registry'; - if (imageTag !== '') { - filter_in = { - docker_image_name: _images, - docker_image_tag: [imageTag], - }; - } else { - filter_in = { - docker_image_name: _images, - }; + const getNodeType = (nodeType: VulnerabilityScanNodeTypeEnum | 'container_image') => { + let _nodeType = nodeType as ModelNodeIdentifierNodeTypeEnum; + if ( + nodeType === VulnerabilityScanNodeTypeEnum.imageTag || + nodeType === 'container_image' + ) { + _nodeType = VulnerabilityScanNodeTypeEnum.image; + } else if (nodeType === VulnerabilityScanNodeTypeEnum.kubernetes_cluster) { + _nodeType = 'cluster'; + } else if (nodeType === VulnerabilityScanNodeTypeEnum.image) { + _nodeType = 'registry'; } - } else if (nodeType === VulnerabilityScanNodeTypeEnum.registry) { - if (imageTag !== '') { - filter_in = { - docker_image_tag: [imageTag], - }; + return _nodeType; + }; + + const createFilter = ( + nodeType: VulnerabilityScanNodeTypeEnum, + ): ReportersContainsFilter['filter_in'] => { + if (nodeType === VulnerabilityScanNodeTypeEnum.image) { + if (imageTag !== '') { + return { + docker_image_name: _images, + docker_image_tag: [imageTag], + }; + } else { + return { + docker_image_name: _images, + }; + } + } else if (nodeType === VulnerabilityScanNodeTypeEnum.registry) { + if (imageTag !== '') { + return { + docker_image_tag: [imageTag], + }; + } } - } + return null; + }; const packagesSelected = packages.map((pkg) => ({ language: pkg as unknown as ModelVulnerabilityScanConfigLanguageLanguageEnum, @@ -161,6 +176,15 @@ export const scanVulnerabilityApiAction = async ({ }); }); } + const nodeType = nodeTypes[0]; + let filter_in = null; + if ( + nodeType === VulnerabilityScanNodeTypeEnum.image || + nodeType === VulnerabilityScanNodeTypeEnum.registry + ) { + filter_in = createFilter(nodeType); + } + const requestBody: ModelVulnerabilityScanTriggerReq = { filters: { cloud_account_scan_filter: { filter_in: null }, @@ -171,9 +195,11 @@ export const scanVulnerabilityApiAction = async ({ filter_in, }, }, - node_ids: nodeIds.map((nodeId) => ({ + node_ids: nodeIds.map((nodeId, index) => ({ node_id: nodeId, - node_type: _nodeType as ModelNodeIdentifierNodeTypeEnum, + node_type: getNodeType( + nodeTypes[index] as VulnerabilityScanNodeTypeEnum, + ) as ModelNodeIdentifierNodeTypeEnum, })), scan_config: packagesSelected, }; @@ -211,7 +237,7 @@ export const scanVulnerabilityApiAction = async ({ success: true, data: { bulkScanId: startVulnerabilityScanResponse.value.bulk_scan_id, - nodeType, + nodeType, // for onboard page redirection }, }; }; @@ -302,17 +328,32 @@ export const VulnerabilityScanConfigureForm = ({ } }; + // in case of registry scan nodeType will be always be of same type + const nodeType = data.nodes[0].nodeType; const isNodeTypeRegistryType = - data.nodeType === 'registry' || isNodeTypeARegistryType(data.nodeType); + nodeType === 'registry' || isNodeTypeARegistryType(nodeType); + return ( - - - {data.nodeType === VulnerabilityScanNodeTypeEnum.image && data.images && ( + node.nodeId).join(',')} + /> + node.nodeType).join(',')} + /> + {'images' in data && ( )}
Select packages
@@ -375,9 +416,7 @@ export const VulnerabilityScanConfigureForm = ({ ) : null} - {wantAdvanceOptions && ( - - )} + {wantAdvanceOptions && } {fetcherData?.message && (

{fetcherData.message}

diff --git a/deepfence_frontend/apps/dashboard/src/features/integrations/components/useIntegrationTableColumn.tsx b/deepfence_frontend/apps/dashboard/src/features/integrations/components/useIntegrationTableColumn.tsx index f3ab29429e..da5133a6eb 100644 --- a/deepfence_frontend/apps/dashboard/src/features/integrations/components/useIntegrationTableColumn.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/integrations/components/useIntegrationTableColumn.tsx @@ -482,7 +482,7 @@ export const useIntegrationTableColumn = ( {isError ? (
- + Error diff --git a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareDetailModal.tsx b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareDetailModal.tsx index d91112d62a..ffd4779562 100644 --- a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareDetailModal.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareDetailModal.tsx @@ -18,6 +18,7 @@ import { CopyLineIcon } from '@/components/icons/common/CopyLine'; import { SeverityBadge } from '@/components/SeverityBadge'; import { MalwareIcon } from '@/components/sideNavigation/icons/Malware'; import { queries } from '@/queries'; +import { formatMilliseconds } from '@/utils/date'; import { replacebyUppercaseCharacters } from '@/utils/label'; import { usePageNavigation } from '@/utils/usePageNavigation'; @@ -29,6 +30,10 @@ function useGetMalwareDetails() { }), }); } +const timeFormatKey = { + updated_at: 'updated_at', +}; + const Header = () => { const { data: { data: malwares }, @@ -173,7 +178,7 @@ const DetailsComponent = () => {
- {valueAsStr} + {key in timeFormatKey ? formatMilliseconds(+valueAsStr) : valueAsStr}
); diff --git a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx index f8cf5e0b7e..958ae8c478 100644 --- a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx @@ -37,6 +37,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { ModelMalware, + ModelScanInfo, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, } from '@/api/generated'; @@ -55,12 +56,15 @@ import { EyeSolidIcon } from '@/components/icons/common/EyeSolid'; import { FilterIcon } from '@/components/icons/common/Filter'; import { TimesIcon } from '@/components/icons/common/Times'; import { TrashLineIcon } from '@/components/icons/common/TrashLine'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanHistoryDropdown } from '@/components/scan-history/HistoryList'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { ScanStatusInError, ScanStatusInProgress, ScanStatusNoData, + ScanStatusStopped, + ScanStatusStopping, } from '@/components/ScanStatusMessage'; import { SeverityBadge } from '@/components/SeverityBadge'; import { MalwareIcon } from '@/components/sideNavigation/icons/Malware'; @@ -76,7 +80,13 @@ import { get403Message } from '@/utils/403'; import { apiWrapper } from '@/utils/api'; import { formatMilliseconds } from '@/utils/date'; import { abbreviateNumber } from '@/utils/number'; -import { isScanComplete, isScanFailed, isScanInProgress } from '@/utils/scan'; +import { + isScanComplete, + isScanFailed, + isScanInProgress, + isScanStopped, + isScanStopping, +} from '@/utils/scan'; import { getOrderFromSearchParams, getPageFromSearchParams, @@ -574,6 +584,7 @@ const HistoryControls = () => { const { scan_id, node_id, node_type, updated_at, status } = scanStatusResult ?? {}; const { navigate, goBack } = usePageNavigation(); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); const [showScanCompareModal, setShowScanCompareModal] = useState(false); const [scanIdToDelete, setScanIdToDelete] = useState(null); @@ -621,7 +632,15 @@ const HistoryControls = () => { }; return ( - <> +
+ {openStopScanModal && ( + + )} {compareInput.showScanTimeModal && ( { )}
- {!isScanInProgress(status ?? '') && ( + {!isScanInProgress(status ?? '') ? ( <>
@@ -763,9 +782,22 @@ const HistoryControls = () => {
+ ) : ( + )}
- +
); }; @@ -1087,6 +1119,20 @@ const TablePlaceholder = ({ ); } + if (isScanStopped(scanStatus)) { + return ( +
+ +
+ ); + } + if (isScanStopping(scanStatus)) { + return ( +
+ +
+ ); + } if (isScanInProgress(scanStatus)) { return (
@@ -1434,6 +1480,57 @@ const ScanResults = () => { ); }; +const ScanStatusWrapper = ({ + children, + scanStatusResult, + displayNoData, +}: { + children: React.ReactNode; + scanStatusResult: ModelScanInfo | undefined; + displayNoData?: boolean; +}) => { + if (isScanFailed(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanStopped(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanStopping(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanInProgress(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + if (displayNoData) { + return ( +
+ +
+ ); + } + + return <>{children}; +}; + const SeverityCounts = ({ severityCounts, }: { @@ -1472,38 +1569,25 @@ const SeverityCountWidget = () => { [k: string]: number; } = data?.severityCounts ?? {}; - if (isScanFailed(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - if (isScanInProgress(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - return ( -
-
- -
-
- {keys(severityCounts).length === 0 ? ( -
- -
- ) : ( -
- -
- )} + +
+
+ +
+
+ {keys(severityCounts).length === 0 ? ( +
+ +
+ ) : ( +
+ +
+ )} +
-
+ ); }; const Top5Widget = () => { @@ -1513,62 +1597,43 @@ const Top5Widget = () => { const { data: scanResult } = useScanResults(); const { scanStatusResult } = scanResult; - if (isScanFailed(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - if (isScanInProgress(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - if (!data.data || data.data?.length === 0) { - return ( -
- -
- ); - } - return ( - - - {data.data.map((malware) => { - return ( - - + + ); + })} + +
- -
- -
-
- + + + + {data.data?.map?.((malware) => { + return ( + + + - - - ); - })} - -
+ +
+ +
+
+ +
+
+
+
+
- -
-
- -
-
+
+ ); }; @@ -1576,23 +1641,11 @@ const TopAttackPath = () => { const { data: scanResult } = useScanResults(); const { scanStatusResult } = scanResult; - if (isScanFailed(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - if (isScanInProgress(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - return ; + return ( + + ; + + ); }; const Widgets = () => { diff --git a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScans.tsx b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScans.tsx index 0e6676a35a..0b000e295b 100644 --- a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScans.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScans.tsx @@ -19,7 +19,9 @@ import { createColumnHelper, Dropdown, DropdownItem, + getRowSelectionColumn, Modal, + RowSelectionState, SortingState, Table, TableSkeleton, @@ -27,6 +29,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { + ModelBulkDeleteScansRequestScanTypeEnum, ModelScanInfo, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, @@ -45,6 +48,8 @@ import { EllipsisIcon } from '@/components/icons/common/Ellipsis'; import { ErrorStandardLineIcon } from '@/components/icons/common/ErrorStandardLine'; import { FilterIcon } from '@/components/icons/common/Filter'; import { TimesIcon } from '@/components/icons/common/Times'; +import { TrashLineIcon } from '@/components/icons/common/TrashLine'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { MalwareIcon } from '@/components/sideNavigation/icons/Malware'; import { TruncatedText } from '@/components/TruncatedText'; @@ -61,6 +66,7 @@ import { isNeverScanned, isScanComplete, isScanInProgress, + isScanStopping, MalwareScanGroupedStatus, SCAN_STATUS_GROUPS, } from '@/utils/scan'; @@ -96,19 +102,30 @@ const action = async ({ > => { const formData = await request.formData(); const actionType = formData.get('actionType'); - const scanId = formData.get('scanId'); - const nodeId = formData.get('nodeId'); - if (!actionType || !scanId || !nodeId) { + const scanIds = formData.getAll('scanId'); + + if (!actionType || scanIds.length === 0) { throw new Error('Invalid action'); } if (actionType === ActionEnumType.DELETE) { const resultApi = apiWrapper({ - fn: getScanResultsApiClient().deleteScanResultsForScanID, + fn: getScanResultsApiClient().bulkDeleteScans, }); const result = await resultApi({ - scanId: scanId.toString(), - scanType: ScanTypeEnum.MalwareScan, + modelBulkDeleteScansRequest: { + filters: { + compare_filter: null, + contains_filter: { + filter_in: { + node_id: scanIds, + }, + }, + order_filter: { order_fields: [] }, + match_filter: { filter_in: {} }, + }, + scan_type: ModelBulkDeleteScansRequestScanTypeEnum.Malware, + }, }); if (!result.ok) { if (result.error.response.status === 400 || result.error.response.status === 409) { @@ -134,14 +151,14 @@ const action = async ({ const DeleteConfirmationModal = ({ showDialog, - scanId, - nodeId, + scanIds, setShowDialog, + onDeleteSuccess, }: { showDialog: boolean; - scanId: string; - nodeId: string; + scanIds: string[]; setShowDialog: React.Dispatch>; + onDeleteSuccess: () => void; }) => { const fetcher = useFetcher(); @@ -149,15 +166,24 @@ const DeleteConfirmationModal = ({ (actionType: string) => { const formData = new FormData(); formData.append('actionType', actionType); - formData.append('scanId', scanId); - formData.append('nodeId', nodeId); + scanIds.forEach((scanId) => formData.append('scanId', scanId)); fetcher.submit(formData, { method: 'post', }); }, - [scanId, nodeId, fetcher], + [scanIds, fetcher], ); + useEffect(() => { + if ( + fetcher.state === 'idle' && + fetcher.data?.success && + fetcher.data.action === ActionEnumType.DELETE + ) { + onDeleteSuccess(); + } + }, [fetcher]); + return ( >; setScanIdToDelete: React.Dispatch>; - setNodeIdToDelete: React.Dispatch>; setStartScanInfo: React.Dispatch< React.SetStateAction<{ start: boolean; @@ -246,6 +270,7 @@ const ActionDropdown = ({ const fetcher = useFetcher(); const [open, setOpen] = useState(false); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); const onDownloadAction = useCallback(() => { downloadScan({ @@ -261,6 +286,15 @@ const ActionDropdown = ({ return ( <> + {openStopScanModal && ( + + )} + Start scan + {isScanInProgress(scanStatus) && ( + { + e.preventDefault(); + setOpenStopScanModal(true); + }} + disabled={!isScanInProgress(scanStatus)} + > + Cancel scan + + )} { setScanIdToDelete(scanId); - setNodeIdToDelete(nodeId); setShowDeleteDialog(true); }} > @@ -412,7 +456,7 @@ const Filters = () => { { setSearchParams((prev) => { @@ -433,7 +477,7 @@ const Filters = () => { }} /> { setSearchParams((prev) => { @@ -454,7 +498,7 @@ const Filters = () => { }} /> { setSearchParams((prev) => { @@ -543,7 +587,13 @@ const Filters = () => { ); }; -const ScansTable = () => { +const ScansTable = ({ + rowSelectionState, + setRowSelectionState, +}: { + rowSelectionState: RowSelectionState; + setRowSelectionState: React.Dispatch>; +}) => { const [searchParams, setSearchParams] = useSearchParams(); const { data } = useSuspenseQuery({ ...queries.malware.scanList({ @@ -571,12 +621,16 @@ const ScansTable = () => { nodeId: '', nodeType: '', }); - const [nodeIdToDelete, setNodeIdToDelete] = useState(''); const columnHelper = createColumnHelper(); const columns = useMemo(() => { const columns = [ + getRowSelectionColumn(columnHelper, { + size: 45, + minSize: 45, + maxSize: 45, + }), columnHelper.display({ id: 'actions', enableSorting: false, @@ -587,7 +641,6 @@ const ScansTable = () => { nodeType={cell.row.original.node_type} scanStatus={cell.row.original.status} setScanIdToDelete={setScanIdToDelete} - setNodeIdToDelete={setNodeIdToDelete} setShowDeleteDialog={setShowDeleteDialog} setStartScanInfo={setStartScanInfo} trigger={ @@ -825,9 +878,11 @@ const ScansTable = () => { {showDeleteDialog && ( { + // + }} /> )} {startScanInfo.start && ( @@ -845,8 +900,12 @@ const ScansTable = () => { showAdvancedOptions: true, scanType: ScanTypeEnum.MalwareScan, data: { - nodeIds: [startScanInfo.nodeId], - nodeType: startScanInfo.nodeType, + nodes: [ + { + nodeId: startScanInfo.nodeId, + nodeType: startScanInfo.nodeType, + }, + ], }, } as ConfigureScanModalProps['scanOptions'] } @@ -855,6 +914,17 @@ const ScansTable = () => { { + return JSON.stringify({ + scanId: row.scan_id, + nodeId: row.node_id, + nodeType: row.node_type, + updatedAt: row.updated_at, + }); + }} enablePagination manualPagination enableColumnResizing @@ -912,6 +982,89 @@ const ScansTable = () => { ); }; +const BulkActions = ({ + selectedRows, + setIdsToDelete, + setShowDeleteDialog, + setShowCancelScanDialog, + setRowSelectionState, +}: { + selectedRows: { + scanId: string; + nodeId: string; + nodeType: string; + }[]; + setIdsToDelete: React.Dispatch>; + setShowDeleteDialog: React.Dispatch>; + setShowCancelScanDialog: React.Dispatch>; + setRowSelectionState: React.Dispatch>; +}) => { + const [openStartScan, setOpenStartScan] = useState(false); + return ( + <> + {openStartScan && ( + setOpenStartScan(false)} + onSuccess={() => setRowSelectionState({})} + scanOptions={ + { + showAdvancedOptions: true, + scanType: ScanTypeEnum.MalwareScan, + data: { + nodes: selectedRows.map((row) => { + return { + nodeId: row.nodeId, + nodeType: row.nodeType, + }; + }), + }, + } as ConfigureScanModalProps['scanOptions'] + } + /> + )} + + + + + + ); +}; + const MalwareScans = () => { const [searchParams] = useSearchParams(); const isFetching = useIsFetching({ @@ -919,6 +1072,23 @@ const MalwareScans = () => { }); const [filtersExpanded, setFiltersExpanded] = useState(false); + const [rowSelectionState, setRowSelectionState] = useState({}); + const [idsToDelete, setIdsToDelete] = useState([]); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [showCancelScanDialog, setShowCancelScanDialog] = useState(false); + + const selectedRows = useMemo< + { + scanId: string; + nodeId: string; + nodeType: string; + }[] + >(() => { + return Object.keys(rowSelectionState).map((item) => { + return JSON.parse(item); + }); + }, [rowSelectionState]); + return (
@@ -940,9 +1110,16 @@ const MalwareScans = () => {
+
{filtersExpanded ? : null} }> - + + {showDeleteDialog && ( + { + setRowSelectionState({}); + }} + /> + )} + {showCancelScanDialog ? ( + row.scanId)} + scanType={ScanTypeEnum.MalwareScan} + onCancelScanSuccess={() => { + setRowSelectionState({}); + }} + /> + ) : null}
); diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ConfigureScanForm.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ConfigureScanForm.tsx index 9fee8b943d..fa1c1d98f5 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ConfigureScanForm.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ConfigureScanForm.tsx @@ -3,9 +3,18 @@ import { generatePath, Navigate, useLocation, useParams } from 'react-router-dom import { Button, Card, Tooltip } from 'ui-components'; import { ComplianceScanConfigureForm } from '@/components/scan-configure-forms/ComplianceScanConfigureForm'; -import { MalwareScanConfigureForm } from '@/components/scan-configure-forms/MalwareScanConfigureForm'; -import { SecretScanConfigureForm } from '@/components/scan-configure-forms/SecretScanConfigureForm'; -import { VulnerabilityScanConfigureForm } from '@/components/scan-configure-forms/VulnerabilityScanConfigureForm'; +import { + MalwareScanConfigureForm, + MalwareScanConfigureFormProps, +} from '@/components/scan-configure-forms/MalwareScanConfigureForm'; +import { + SecretScanConfigureForm, + SecretScanConfigureFormProps, +} from '@/components/scan-configure-forms/SecretScanConfigureForm'; +import { + VulnerabilityScanConfigureForm, + VulnerabilityScanConfigureFormProps, +} from '@/components/scan-configure-forms/VulnerabilityScanConfigureForm'; import { ConnectorHeader } from '@/features/onboard/components/ConnectorHeader'; import { OnboardConnectionNode } from '@/features/onboard/pages/connectors/MyConnectors'; import { @@ -96,11 +105,17 @@ const ScanConfigureForm = () => { {scanType === ScanTypeEnum.VulnerabilityScan && ( node.urlId), - nodeType: state[0].urlType as VulnerabilityScanNodeTypeEnum, - images: [], - }} + data={ + { + nodes: state.map((node) => { + return { + nodeId: node.urlId, + nodeType: state[0].urlType as VulnerabilityScanNodeTypeEnum, + }; + }), + images: [], + } as VulnerabilityScanConfigureFormProps['data'] + } onSuccess={(data) => { if (data) { const { nodeType, bulkScanId } = data; @@ -121,11 +136,17 @@ const ScanConfigureForm = () => { {scanType === ScanTypeEnum.SecretScan && ( node.urlId), - nodeType: state[0].urlType as SecretScanNodeTypeEnum, - images: [], - }} + data={ + { + nodes: state.map((node) => { + return { + nodeId: node.urlId, + nodeType: state[0].urlType as SecretScanNodeTypeEnum, + }; + }), + images: [], + } as SecretScanConfigureFormProps['data'] + } onSuccess={(data) => { if (data) { const { nodeType, bulkScanId } = data; @@ -146,11 +167,17 @@ const ScanConfigureForm = () => { {scanType === ScanTypeEnum.MalwareScan && ( node.urlId), - nodeType: state[0].urlType as MalwareScanNodeTypeEnum, - images: [], - }} + data={ + { + nodes: state.map((node) => { + return { + nodeId: node.urlId, + nodeType: state[0].urlType as MalwareScanNodeTypeEnum, + }; + }), + images: [], + } as MalwareScanConfigureFormProps['data'] + } onSuccess={(data) => { if (data) { const { nodeType, bulkScanId } = data; diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ScanInProgress.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ScanInProgress.tsx index 6edc8a2418..e96ac0b447 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ScanInProgress.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ScanInProgress.tsx @@ -120,7 +120,7 @@ export const ScanInProgressError = () => { description={'An error has occurred, please retry.'} />
- +

@@ -231,7 +231,7 @@ const ScanStatus = () => { ) : ( <> {allScanFailed ? ( - + ) : ( diff --git a/deepfence_frontend/apps/dashboard/src/features/postures/pages/Accounts.tsx b/deepfence_frontend/apps/dashboard/src/features/postures/pages/Accounts.tsx index 1559fffea6..1d81bfb691 100644 --- a/deepfence_frontend/apps/dashboard/src/features/postures/pages/Accounts.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/postures/pages/Accounts.tsx @@ -10,6 +10,7 @@ import { useSearchParams, } from 'react-router-dom'; import { toast } from 'sonner'; +import { cn } from 'tailwind-preset'; import { Badge, Breadcrumb, @@ -34,6 +35,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { + ModelBulkDeleteScansRequestScanTypeEnum, ModelCloudNodeAccountInfo, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, @@ -50,7 +52,9 @@ import { ErrorStandardLineIcon } from '@/components/icons/common/ErrorStandardLi import { FilterIcon } from '@/components/icons/common/Filter'; import { PlusIcon } from '@/components/icons/common/Plus'; import { TimesIcon } from '@/components/icons/common/Times'; +import { TrashLineIcon } from '@/components/icons/common/TrashLine'; import { CLOUDS } from '@/components/scan-configure-forms/ComplianceScanConfigureForm'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { PostureIcon } from '@/components/sideNavigation/icons/Posture'; import { TruncatedText } from '@/components/TruncatedText'; @@ -75,6 +79,8 @@ import { COMPLIANCE_SCAN_STATUS_GROUPS, ComplianceScanGroupedStatus, isScanComplete, + isScanInProgress, + isScanStopping, SCAN_STATUS_GROUPS, } from '@/utils/scan'; import { @@ -128,22 +134,33 @@ const action = async ({ }: ActionFunctionArgs): Promise<{ success?: boolean; message?: string } | null> => { const formData = await request.formData(); const actionType = formData.get('actionType'); - const scanId = formData.get('scanId'); - const scanType = formData.get('scanType'); + const scanIds = formData.getAll('scanId'); + const scanType = formData.get('scanType') as ModelBulkDeleteScansRequestScanTypeEnum; if (!actionType) { throw new Error('Invalid action'); } if (actionType === ActionEnumType.DELETE) { - if (!scanId) { - throw new Error('Invalid action'); + if (scanIds.length === 0) { + throw new Error('Scan ids are required for deletion'); } const deleteScanResultsForScanIDApi = apiWrapper({ - fn: getScanResultsApiClient().deleteScanResultsForScanID, + fn: getScanResultsApiClient().bulkDeleteScans, }); const result = await deleteScanResultsForScanIDApi({ - scanId: scanId.toString(), - scanType: scanType as ScanTypeEnum, + modelBulkDeleteScansRequest: { + filters: { + compare_filter: null, + contains_filter: { + filter_in: { + node_id: scanIds, + }, + }, + order_filter: { order_fields: [] }, + match_filter: { filter_in: {} }, + }, + scan_type: scanType, + }, }); if (!result.ok) { if (result.error.response.status === 400 || result.error.response.status === 409) { @@ -357,14 +374,16 @@ const Filters = () => { }; const DeleteConfirmationModal = ({ showDialog, - scanId, + scanIds, scanType, setShowDialog, + onSuccess, }: { showDialog: boolean; - scanId: string; + scanIds: string[]; setShowDialog: React.Dispatch>; - scanType?: ScanTypeEnum; + scanType?: ModelBulkDeleteScansRequestScanTypeEnum; + onSuccess: () => void; }) => { const fetcher = useFetcher(); @@ -372,15 +391,25 @@ const DeleteConfirmationModal = ({ (actionType: string) => { const formData = new FormData(); formData.append('actionType', actionType); - formData.append('scanId', scanId); + scanIds.forEach((scanId) => formData.append('scanId', scanId)); formData.append('scanType', scanType ?? ''); fetcher.submit(formData, { method: 'post', }); }, - [scanId, scanType, fetcher], + [scanIds, scanType, fetcher], ); + useEffect(() => { + if ( + fetcher.state === 'idle' && + fetcher.data?.success && + fetcher.data.action === ActionEnumType.DELETE + ) { + onSuccess(); + } + }, [fetcher]); + return ( >; onTableAction: (ids: string[], actionType: ActionEnumType) => void; - setScanIdToDelete: React.Dispatch>; }) => { const fetcher = useFetcher(); const [open, setOpen] = useState(false); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); + + const [showDeleteDialog, setShowDeleteDialog] = useState(false); const onDownloadAction = useCallback(() => { - if (!scanId || !nodeType) return; downloadScan({ scanId, nodeType: nodeType as UtilsReportFiltersNodeTypeEnum, @@ -480,59 +507,107 @@ const ActionDropdown = ({ if (fetcher.state === 'idle') setOpen(false); }, [fetcher]); + if (!nodeType || !nodeId) { + throw new Error('Node type and Node id are required'); + } + return ( - - { - if (!nodeId) { - throw new Error('Node id is required to start scan'); - } - onTableAction([nodeId], ActionEnumType.START_SCAN); - }} - > - Start scan - - { - if (!isScanComplete(scanStatus)) return; - e.preventDefault(); - onDownloadAction(); - }} - > - Download latest report - - { - if (!scanId || !nodeType) return; - setScanIdToDelete(scanId); - setShowDeleteDialog(true); - }} - > - - Delete latest scan - - - - } - > - {trigger} - + <> + {openStopScanModal && ( + + )} + {showDeleteDialog && ( + { + // + }} + /> + )} + + { + if (!nodeId) { + throw new Error('Node id is required to start scan'); + } + onTableAction([nodeId], ActionEnumType.START_SCAN); + }} + > + Start scan + + {isScanInProgress(scanStatus) && ( + { + e.preventDefault(); + setOpenStopScanModal(true); + }} + disabled={!isScanInProgress(scanStatus)} + > + Cancel scan + + )} + { + if (!isScanComplete(scanStatus)) return; + e.preventDefault(); + onDownloadAction(); + }} + > + Download latest report + + { + if (!scanId || !nodeType) return; + setShowDeleteDialog(true); + }} + > + + Delete latest scan + + + + } + > + {trigger} + + ); }; const BulkActions = ({ onClick, + onDelete, + onCancelScan, disabled, }: { onClick?: React.MouseEventHandler | undefined; + onCancelScan?: React.MouseEventHandler | undefined; + onDelete?: React.MouseEventHandler | undefined; disabled: boolean; }) => { const { navigate } = usePageNavigation(); @@ -568,6 +643,25 @@ const BulkActions = ({ > Start scan + + ); }; @@ -577,8 +671,6 @@ const AccountTable = ({ rowSelectionState, onTableAction, scanType, - setShowDeleteDialog, - setScanIdToDelete, nodeType, }: { nodeType?: ComplianceScanNodeTypeEnum; @@ -586,8 +678,6 @@ const AccountTable = ({ setRowSelectionState: React.Dispatch>; rowSelectionState: RowSelectionState; onTableAction: (ids: string[], actionType: ActionEnumType) => void; - setShowDeleteDialog: React.Dispatch>; - setScanIdToDelete: React.Dispatch>; }) => { const [searchParams, setSearchParams] = useSearchParams(); const { data } = usePostureAccounts(); @@ -597,7 +687,6 @@ const AccountTable = ({ const columnHelper = createColumnHelper(); const accounts = data?.accounts ?? []; - const columnWidth = nodeType?.endsWith('_org') ? { node_name: { @@ -671,10 +760,8 @@ const AccountTable = ({ nodeId={cell.row.original.node_id} nodeType={nodeType} scanType={scanType} - setScanIdToDelete={setScanIdToDelete} scanStatus={cell.row.original.last_scan_status || ''} onTableAction={onTableAction} - setShowDeleteDialog={setShowDeleteDialog} trigger={

@@ -1055,34 +1175,36 @@ const AccountWithTab = () => { const { navigate } = usePageNavigation(); return ( -
+ <>
- { - if (currentTab === value) return; - let _nodeType = nodeType; - if (value === 'org-accounts') { - _nodeType = _nodeType + '_org'; - } else { - _nodeType = _nodeType.split('_')[0]; - } - setTab(value); - navigate( - generatePath('/posture/accounts/:nodeType', { - nodeType: _nodeType, - }), - ); - }} - size="md" - > -
- -
-
-
+
+ { + if (currentTab === value) return; + let _nodeType = nodeType; + if (value === 'org-accounts') { + _nodeType = _nodeType + '_org'; + } else { + _nodeType = _nodeType.split('_')[0]; + } + setTab(value); + navigate( + generatePath('/posture/accounts/:nodeType', { + nodeType: _nodeType, + }), + ); + }} + size="md" + > +
+ +
+
+
+ ); }; diff --git a/deepfence_frontend/apps/dashboard/src/features/postures/pages/Posture.tsx b/deepfence_frontend/apps/dashboard/src/features/postures/pages/Posture.tsx index 00f14195a3..8f58413530 100644 --- a/deepfence_frontend/apps/dashboard/src/features/postures/pages/Posture.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/postures/pages/Posture.tsx @@ -30,10 +30,10 @@ const isKubernetesProvider = (provider: string) => provider === 'kubernetes'; const HeaderSkeleton = () => { return (
-
-
+
+
-
+
@@ -70,10 +70,10 @@ const CountSkeleton = () => {
); }; -const CardSkeleton = () => { +const CardSkeleton = ({ count }: { count: number }) => { return ( <> - {Array.from(Array(5).keys()).map((k) => ( + {Array.from(Array(count).keys()).map((k) => ( {
- }> + +
+ +
+ +
+ +
+
+ } + >
diff --git a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudDetailModal.tsx b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudDetailModal.tsx index 291520b190..5b3dc2372a 100644 --- a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudDetailModal.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudDetailModal.tsx @@ -19,6 +19,7 @@ import { PostureStatusBadge } from '@/components/SeverityBadge'; import { PostureIcon } from '@/components/sideNavigation/icons/Posture'; import { queries } from '@/queries'; import { PostureSeverityType } from '@/types/common'; +import { formatMilliseconds } from '@/utils/date'; import { replacebyUppercaseCharacters } from '@/utils/label'; import { usePageNavigation } from '@/utils/usePageNavigation'; @@ -30,6 +31,10 @@ function useGetComplianceDetails() { }), }); } +const timeFormatKey = { + updated_at: 'updated_at', +}; + const Header = () => { const { data: { data: cloudPostures }, @@ -169,7 +174,7 @@ const DetailsComponent = () => {
- {valueAsStr} + {key in timeFormatKey ? formatMilliseconds(+valueAsStr) : valueAsStr}
); diff --git a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudScanResults.tsx b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudScanResults.tsx index d4441513bc..15762749e0 100644 --- a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudScanResults.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureCloudScanResults.tsx @@ -36,6 +36,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { ModelCloudCompliance, + ModelScanInfo, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, } from '@/api/generated'; @@ -56,12 +57,15 @@ import { TaskIcon } from '@/components/icons/common/Task'; import { TimesIcon } from '@/components/icons/common/Times'; import { TrashLineIcon } from '@/components/icons/common/TrashLine'; import { complianceType } from '@/components/scan-configure-forms/ComplianceScanConfigureForm'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanHistoryDropdown } from '@/components/scan-history/HistoryList'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { ScanStatusInError, ScanStatusInProgress, ScanStatusNoData, + ScanStatusStopped, + ScanStatusStopping, } from '@/components/ScanStatusMessage'; import { PostureStatusBadge } from '@/components/SeverityBadge'; import { PostureIcon } from '@/components/sideNavigation/icons/Posture'; @@ -83,7 +87,13 @@ import { get403Message } from '@/utils/403'; import { apiWrapper } from '@/utils/api'; import { formatMilliseconds } from '@/utils/date'; import { abbreviateNumber } from '@/utils/number'; -import { isScanComplete, isScanFailed, isScanInProgress } from '@/utils/scan'; +import { + isScanComplete, + isScanFailed, + isScanInProgress, + isScanStopped, + isScanStopping, +} from '@/utils/scan'; import { getOrderFromSearchParams, getPageFromSearchParams, @@ -562,10 +572,12 @@ const HistoryControls = () => { const { data, fetchStatus } = useScanResults(); const { nodeType = '' } = useParams(); const { scanStatusResult } = data; - const { scan_id, node_id, node_type, updated_at, status } = scanStatusResult ?? {}; const { navigate, goBack } = usePageNavigation(); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); + const { scan_id, node_id, node_type, updated_at, status } = scanStatusResult ?? {}; + const [showScanCompareModal, setShowScanCompareModal] = useState(false); const [scanIdToDelete, setScanIdToDelete] = useState(null); @@ -597,8 +609,8 @@ const HistoryControls = () => { refetch(); }, [scan_id]); - if (!node_id || !node_type) { - throw new Error('Node Type and Node Id are required'); + if (!node_id || !node_type || !scan_id) { + throw new Error('Scan id, Node type and Node id are required'); } const onCompareScanClick = (baseScanTime: number) => { @@ -610,7 +622,15 @@ const HistoryControls = () => { }; return ( - <> +
+ {openStopScanModal && ( + + )} {compareInput.showScanTimeModal && ( { )}
- {!isScanInProgress(status ?? '') && ( + {!isScanInProgress(status ?? '') ? ( <>
@@ -753,9 +773,22 @@ const HistoryControls = () => {
+ ) : ( + )}
- + ); }; @@ -1172,7 +1205,7 @@ const CloudPostureResults = () => { return (
-
+
); } - + if (isScanStopped(scanStatus)) { + return ( +
+ +
+ ); + } + if (isScanStopping(scanStatus)) { + return ( +
+ +
+ ); + } if (isScanInProgress(scanStatus)) { return (
@@ -1540,6 +1586,59 @@ const StatusesCount = ({ ); }; +const ScanStatusWrapper = ({ + children, + scanStatusResult, + displayNoData, + className, +}: { + children: React.ReactNode; + className: string; + scanStatusResult: ModelScanInfo | undefined; + displayNoData?: boolean; +}) => { + if (isScanFailed(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanStopped(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanStopping(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanInProgress(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + if (displayNoData) { + return ( +
+ +
+ ); + } + + return <>{children}; +}; + const SeverityCountWidget = () => { const { data: { data, scanStatusResult }, @@ -1556,23 +1655,14 @@ const SeverityCountWidget = () => { return (
- {isScanFailed(scanStatusResult?.status ?? '') ? ( -
- + +
+
- ) : ( - <> - {isScanInProgress(scanStatusResult?.status ?? '') ? ( -
- -
- ) : ( -
- -
- )} - - )} +
{isScanComplete(scanStatusResult?.status ?? '') ? (
Total compliances @@ -1593,27 +1683,18 @@ const SeverityCountWidget = () => {
- {isScanComplete(scanStatusResult?.status ?? '') ? ( - <> - {keys(statusCounts).length === 0 ? ( -
- -
- ) : ( - - )} - - ) : ( -
- {isScanInProgress(scanStatusResult?.status ?? '') ? ( - - ) : ( - isScanFailed(scanStatusResult?.status ?? '') && ( - - ) - )} -
- )} + + {keys(statusCounts).length === 0 ? ( +
+ +
+ ) : ( + + )} +
); }; diff --git a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureDetailModal.tsx b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureDetailModal.tsx index dd1e2b7d2d..ba7ab362a6 100644 --- a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureDetailModal.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureDetailModal.tsx @@ -19,6 +19,7 @@ import { PostureStatusBadge } from '@/components/SeverityBadge'; import { PostureIcon } from '@/components/sideNavigation/icons/Posture'; import { queries } from '@/queries'; import { PostureSeverityType } from '@/types/common'; +import { formatMilliseconds } from '@/utils/date'; import { replacebyUppercaseCharacters } from '@/utils/label'; import { usePageNavigation } from '@/utils/usePageNavigation'; @@ -30,7 +31,9 @@ function useGetComplianceDetails() { }), }); } - +const timeFormatKey = { + updated_at: 'updated_at', +}; const Header = () => { const { data: { data: postures }, @@ -163,7 +166,9 @@ const DetailsComponent = () => {
-
{valueAsStr}
+
+ {key in timeFormatKey ? formatMilliseconds(+valueAsStr) : valueAsStr} +
); })} diff --git a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureScanResults.tsx b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureScanResults.tsx index c0d97d9661..e4cb4d2338 100644 --- a/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureScanResults.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/postures/pages/PostureScanResults.tsx @@ -36,6 +36,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { ModelCompliance, + ModelScanInfo, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, } from '@/api/generated'; @@ -56,12 +57,15 @@ import { TaskIcon } from '@/components/icons/common/Task'; import { TimesIcon } from '@/components/icons/common/Times'; import { TrashLineIcon } from '@/components/icons/common/TrashLine'; import { complianceType } from '@/components/scan-configure-forms/ComplianceScanConfigureForm'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanHistoryDropdown } from '@/components/scan-history/HistoryList'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { ScanStatusInError, ScanStatusInProgress, ScanStatusNoData, + ScanStatusStopped, + ScanStatusStopping, } from '@/components/ScanStatusMessage'; import { PostureStatusBadge } from '@/components/SeverityBadge'; import { PostureIcon } from '@/components/sideNavigation/icons/Posture'; @@ -82,7 +86,13 @@ import { get403Message } from '@/utils/403'; import { apiWrapper } from '@/utils/api'; import { formatMilliseconds } from '@/utils/date'; import { abbreviateNumber } from '@/utils/number'; -import { isScanComplete, isScanFailed, isScanInProgress } from '@/utils/scan'; +import { + isScanComplete, + isScanFailed, + isScanInProgress, + isScanStopped, + isScanStopping, +} from '@/utils/scan'; import { getOrderFromSearchParams, getPageFromSearchParams, @@ -563,6 +573,8 @@ const HistoryControls = () => { const { navigate, goBack } = usePageNavigation(); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); + const [showScanCompareModal, setShowScanCompareModal] = useState(false); const [scanIdToDelete, setScanIdToDelete] = useState(null); @@ -593,8 +605,8 @@ const HistoryControls = () => { refetch(); }, [scan_id]); - if (!node_id || !node_type) { - throw new Error('Node Type and Node Id are required'); + if (!node_id || !node_type || !scan_id) { + throw new Error('Scan id, Node type and Node id are required'); } const onCompareScanClick = (baseScanTime: number) => { @@ -606,7 +618,15 @@ const HistoryControls = () => { }; return ( - <> +
+ {openStopScanModal && ( + + )} {compareInput.showScanTimeModal && ( { )}
- +
); }; @@ -1139,7 +1159,7 @@ const PostureResults = () => { return (
-
+
); } - + if (isScanStopped(scanStatus)) { + return ( +
+ +
+ ); + } + if (isScanStopping(scanStatus)) { + return ( +
+ +
+ ); + } if (isScanInProgress(scanStatus)) { return (
@@ -1506,6 +1539,59 @@ const StatusesCount = ({ ); }; +const ScanStatusWrapper = ({ + children, + scanStatusResult, + displayNoData, + className, +}: { + children: React.ReactNode; + className: string; + scanStatusResult: ModelScanInfo | undefined; + displayNoData?: boolean; +}) => { + if (isScanFailed(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanStopped(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanStopping(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + + if (isScanInProgress(scanStatusResult?.status ?? '')) { + return ( +
+ +
+ ); + } + if (displayNoData) { + return ( +
+ +
+ ); + } + + return <>{children}; +}; + const SeverityCountWidget = () => { const { data: { data, scanStatusResult }, @@ -1522,23 +1608,15 @@ const SeverityCountWidget = () => { return (
- {isScanFailed(scanStatusResult?.status ?? '') ? ( -
- + +
+
- ) : ( - <> - {isScanInProgress(scanStatusResult?.status ?? '') ? ( -
- -
- ) : ( -
- -
- )} - - )} +
+ {isScanComplete(scanStatusResult?.status ?? '') ? (
Total compliances @@ -1558,27 +1636,18 @@ const SeverityCountWidget = () => { ) : null}
- {isScanComplete(scanStatusResult?.status ?? '') ? ( - <> - {keys(statusCounts).length === 0 ? ( -
- -
- ) : ( - - )} - - ) : ( -
- {isScanInProgress(scanStatusResult?.status ?? '') ? ( - - ) : ( - isScanFailed(scanStatusResult?.status ?? '') && ( - - ) - )} -
- )} + + {keys(statusCounts).length === 0 ? ( +
+ +
+ ) : ( + + )} +
); }; diff --git a/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryAccounts.tsx b/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryAccounts.tsx index e0b2a6a910..6522293247 100644 --- a/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryAccounts.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryAccounts.tsx @@ -72,8 +72,10 @@ function getScanOptions( showAdvancedOptions: true, scanType, data: { - nodeIds: ids, - nodeType: VulnerabilityScanNodeTypeEnum.registry, + nodes: ids.map((id) => ({ + nodeId: id, + nodeType: VulnerabilityScanNodeTypeEnum.registry, + })), }, }; } @@ -83,8 +85,10 @@ function getScanOptions( showAdvancedOptions: true, scanType, data: { - nodeIds: ids, - nodeType: SecretScanNodeTypeEnum.registry, + nodes: ids.map((id) => ({ + nodeId: id, + nodeType: SecretScanNodeTypeEnum.registry, + })), }, }; } @@ -94,8 +98,10 @@ function getScanOptions( showAdvancedOptions: true, scanType, data: { - nodeIds: ids, - nodeType: MalwareScanNodeTypeEnum.registry, + nodes: ids.map((id) => ({ + nodeId: id, + nodeType: MalwareScanNodeTypeEnum.registry, + })), }, }; } diff --git a/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImageTags.tsx b/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImageTags.tsx index 1fb7682057..d8513c9289 100644 --- a/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImageTags.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImageTags.tsx @@ -63,8 +63,12 @@ function getScanOptions( showAdvancedOptions: nodeIds.length === 1, scanType, data: { - nodeIds, - nodeType: VulnerabilityScanNodeTypeEnum.imageTag, + nodes: nodeIds.map((nodeId) => { + return { + nodeId, + nodeType: VulnerabilityScanNodeTypeEnum.imageTag, + }; + }), }, }; } @@ -74,8 +78,12 @@ function getScanOptions( showAdvancedOptions: nodeIds.length === 1, scanType, data: { - nodeIds, - nodeType: SecretScanNodeTypeEnum.imageTag, + nodes: nodeIds.map((nodeId) => { + return { + nodeId, + nodeType: SecretScanNodeTypeEnum.imageTag, + }; + }), }, }; } @@ -85,8 +93,12 @@ function getScanOptions( showAdvancedOptions: nodeIds.length === 1, scanType, data: { - nodeIds, - nodeType: MalwareScanNodeTypeEnum.imageTag, + nodes: nodeIds.map((nodeId) => { + return { + nodeId, + nodeType: MalwareScanNodeTypeEnum.imageTag, + }; + }), }, }; } diff --git a/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImages.tsx b/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImages.tsx index b92cf341df..79256fb7fe 100644 --- a/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImages.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/registries/pages/RegistryImages.tsx @@ -91,8 +91,12 @@ function getScanOptions( showAdvancedOptions: true, scanType, data: { - nodeIds, - nodeType: VulnerabilityScanNodeTypeEnum.image, + nodes: nodeIds.map((nodeId) => { + return { + nodeId: nodeId, + nodeType: VulnerabilityScanNodeTypeEnum.image, + }; + }), images, }, }; @@ -103,8 +107,12 @@ function getScanOptions( showAdvancedOptions: true, scanType, data: { - nodeIds, - nodeType: SecretScanNodeTypeEnum.image, + nodes: nodeIds.map((nodeId) => { + return { + nodeId: nodeId, + nodeType: SecretScanNodeTypeEnum.image, + }; + }), images, }, }; @@ -115,8 +123,12 @@ function getScanOptions( showAdvancedOptions: true, scanType, data: { - nodeIds, - nodeType: MalwareScanNodeTypeEnum.image, + nodes: nodeIds.map((nodeId) => { + return { + nodeId: nodeId, + nodeType: MalwareScanNodeTypeEnum.image, + }; + }), images, }, }; diff --git a/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretDetailModal.tsx b/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretDetailModal.tsx index becb0b0ffa..b56ebca484 100644 --- a/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretDetailModal.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretDetailModal.tsx @@ -18,6 +18,7 @@ import { CopyLineIcon } from '@/components/icons/common/CopyLine'; import { SeverityBadge } from '@/components/SeverityBadge'; import { SecretsIcon } from '@/components/sideNavigation/icons/Secrets'; import { queries } from '@/queries'; +import { formatMilliseconds } from '@/utils/date'; import { replacebyUppercaseCharacters } from '@/utils/label'; import { usePageNavigation } from '@/utils/usePageNavigation'; @@ -30,6 +31,10 @@ function useGetSecretDetails() { }); } +const timeFormatKey = { + updated_at: 'updated_at', +}; + const Header = () => { const { data: { data: secrets }, @@ -161,7 +166,7 @@ const DetailsComponent = () => {
- {valueAsStr} + {key in timeFormatKey ? formatMilliseconds(+valueAsStr) : valueAsStr}
); diff --git a/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScanResults.tsx b/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScanResults.tsx index 519665197b..210a18f87c 100644 --- a/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScanResults.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScanResults.tsx @@ -36,6 +36,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { + ModelScanInfo, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, } from '@/api/generated'; @@ -55,12 +56,15 @@ import { EyeSolidIcon } from '@/components/icons/common/EyeSolid'; import { FilterIcon } from '@/components/icons/common/Filter'; import { TimesIcon } from '@/components/icons/common/Times'; import { TrashLineIcon } from '@/components/icons/common/TrashLine'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanHistoryDropdown } from '@/components/scan-history/HistoryList'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { ScanStatusInError, ScanStatusInProgress, ScanStatusNoData, + ScanStatusStopped, + ScanStatusStopping, } from '@/components/ScanStatusMessage'; import { SeverityBadge } from '@/components/SeverityBadge'; import { SecretsIcon } from '@/components/sideNavigation/icons/Secrets'; @@ -76,7 +80,13 @@ import { get403Message } from '@/utils/403'; import { apiWrapper } from '@/utils/api'; import { formatMilliseconds } from '@/utils/date'; import { abbreviateNumber } from '@/utils/number'; -import { isScanComplete, isScanFailed, isScanInProgress } from '@/utils/scan'; +import { + isScanComplete, + isScanFailed, + isScanInProgress, + isScanStopped, + isScanStopping, +} from '@/utils/scan'; import { getOrderFromSearchParams, getPageFromSearchParams, @@ -574,6 +584,7 @@ const HistoryControls = () => { const { scan_id, node_id, node_type, updated_at, status } = scanStatusResult ?? {}; const { navigate, goBack } = usePageNavigation(); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); const [showScanCompareModal, setShowScanCompareModal] = useState(false); @@ -621,7 +632,15 @@ const HistoryControls = () => { }; return ( - <> +
+ {openStopScanModal && ( + + )} {compareInput.showScanTimeModal && ( { )}
- {!isScanInProgress(status ?? '') && ( + {!isScanInProgress(status ?? '') ? ( <>
@@ -763,9 +782,22 @@ const HistoryControls = () => {
+ ) : ( + )}
- +
); }; @@ -1087,6 +1119,20 @@ const TablePlaceholder = ({
); } + if (isScanStopped(scanStatus)) { + return ( +
+ +
+ ); + } + if (isScanStopping(scanStatus)) { + return ( +
+ +
+ ); + } if (isScanInProgress(scanStatus)) { return (
@@ -1466,15 +1512,15 @@ const SeverityCounts = ({ ); }; -const SeverityCountWidget = () => { - const { - data: { data, scanStatusResult }, - } = useScanResults(); - - const severityCounts: { - [k: string]: number; - } = data?.severityCounts ?? {}; - +const ScanStatusWrapper = ({ + children, + scanStatusResult, + displayNoData, +}: { + children: React.ReactNode; + scanStatusResult: ModelScanInfo | undefined; + displayNoData?: boolean; +}) => { if (isScanFailed(scanStatusResult?.status ?? '')) { return (
@@ -1483,47 +1529,18 @@ const SeverityCountWidget = () => { ); } - if (isScanInProgress(scanStatusResult?.status ?? '')) { + if (isScanStopped(scanStatusResult?.status ?? '')) { return (
- +
); } - return ( -
-
- -
-
-
- {keys(severityCounts).length === 0 ? ( -
- -
- ) : ( -
- -
- )} -
-
-
- ); -}; - -const Top5Widget = () => { - const { data } = useTop5Secrets(); - const [searchParams] = useSearchParams(); - - const { data: scanResult } = useScanResults(); - const { scanStatusResult } = scanResult; - - if (isScanFailed(scanStatusResult?.status ?? '')) { + if (isScanStopping(scanStatusResult?.status ?? '')) { return (
- +
); } @@ -1535,8 +1552,7 @@ const Top5Widget = () => {
); } - - if (!data.data || data.data?.length === 0) { + if (displayNoData) { return (
@@ -1544,61 +1560,97 @@ const Top5Widget = () => { ); } + return <>{children}; +}; + +const SeverityCountWidget = () => { + const { + data: { data, scanStatusResult }, + } = useScanResults(); + + const severityCounts: { + [k: string]: number; + } = data?.severityCounts ?? {}; + return ( -
- - {data.data.map((secret) => { - return ( - - + + ); + })} + +
- -
- -
-
- + +
+
+ +
+
+
+ {keys(severityCounts).length === 0 ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+
+
+ ); +}; + +const Top5Widget = () => { + const { data } = useTop5Secrets(); + const [searchParams] = useSearchParams(); + + const { data: scanResult } = useScanResults(); + const { scanStatusResult } = scanResult; + + return ( + + + + {data.data?.map?.((secret) => { + return ( + + + - - - ); - })} - -
+ +
+ +
+
+ +
+
+
+
+
- -
-
- -
-
+
+ ); }; const TopAttackPath = () => { const { data: scanResult } = useScanResults(); const { scanStatusResult } = scanResult; - if (isScanFailed(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - if (isScanInProgress(scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - return ; + return ( + + + + ); }; const Widgets = () => { return ( diff --git a/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScans.tsx b/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScans.tsx index fc83f05e6f..cd1ecf792a 100644 --- a/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScans.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/secrets/pages/SecretScans.tsx @@ -19,7 +19,9 @@ import { createColumnHelper, Dropdown, DropdownItem, + getRowSelectionColumn, Modal, + RowSelectionState, SortingState, Table, TableSkeleton, @@ -27,6 +29,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { + ModelBulkDeleteScansRequestScanTypeEnum, ModelScanInfo, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, @@ -45,6 +48,8 @@ import { EllipsisIcon } from '@/components/icons/common/Ellipsis'; import { ErrorStandardLineIcon } from '@/components/icons/common/ErrorStandardLine'; import { FilterIcon } from '@/components/icons/common/Filter'; import { TimesIcon } from '@/components/icons/common/Times'; +import { TrashLineIcon } from '@/components/icons/common/TrashLine'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { SecretsIcon } from '@/components/sideNavigation/icons/Secrets'; import { TruncatedText } from '@/components/TruncatedText'; @@ -61,6 +66,7 @@ import { isNeverScanned, isScanComplete, isScanInProgress, + isScanStopping, SCAN_STATUS_GROUPS, SecretScanGroupedStatus, } from '@/utils/scan'; @@ -96,19 +102,30 @@ const action = async ({ > => { const formData = await request.formData(); const actionType = formData.get('actionType'); - const scanId = formData.get('scanId'); - const nodeId = formData.get('nodeId'); - if (!actionType || !scanId || !nodeId) { + const scanIds = formData.getAll('scanId'); + + if (!actionType || scanIds.length === 0) { throw new Error('Invalid action'); } if (actionType === ActionEnumType.DELETE) { const resultApi = apiWrapper({ - fn: getScanResultsApiClient().deleteScanResultsForScanID, + fn: getScanResultsApiClient().bulkDeleteScans, }); const result = await resultApi({ - scanId: scanId.toString(), - scanType: ScanTypeEnum.SecretScan, + modelBulkDeleteScansRequest: { + filters: { + compare_filter: null, + contains_filter: { + filter_in: { + node_id: scanIds, + }, + }, + order_filter: { order_fields: [] }, + match_filter: { filter_in: {} }, + }, + scan_type: ModelBulkDeleteScansRequestScanTypeEnum.Secret, + }, }); if (!result.ok) { if (result.error.response.status === 400 || result.error.response.status === 409) { @@ -134,14 +151,14 @@ const action = async ({ const DeleteConfirmationModal = ({ showDialog, - scanId, - nodeId, + scanIds, setShowDialog, + onDeleteSuccess, }: { showDialog: boolean; - scanId: string; - nodeId: string; + scanIds: string[]; setShowDialog: React.Dispatch>; + onDeleteSuccess: () => void; }) => { const fetcher = useFetcher(); @@ -149,15 +166,24 @@ const DeleteConfirmationModal = ({ (actionType: string) => { const formData = new FormData(); formData.append('actionType', actionType); - formData.append('scanId', scanId); - formData.append('nodeId', nodeId); + scanIds.forEach((scanId) => formData.append('scanId', scanId)); fetcher.submit(formData, { method: 'post', }); }, - [scanId, nodeId, fetcher], + [scanIds, fetcher], ); + useEffect(() => { + if ( + fetcher.state === 'idle' && + fetcher.data?.success && + fetcher.data.action === ActionEnumType.DELETE + ) { + onDeleteSuccess(); + } + }, [fetcher]); + return ( >; setScanIdToDelete: React.Dispatch>; - setNodeIdToDelete: React.Dispatch>; setStartScanInfo: React.Dispatch< React.SetStateAction<{ start: boolean; @@ -246,6 +270,7 @@ const ActionDropdown = ({ const fetcher = useFetcher(); const [open, setOpen] = useState(false); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); const onDownloadAction = useCallback(() => { downloadScan({ @@ -260,54 +285,76 @@ const ActionDropdown = ({ }, [fetcher]); return ( - - { - if (!isScanComplete(scanStatus)) return; - e.preventDefault(); - onDownloadAction(); - }} - disabled={!isScanComplete(scanStatus)} - > - Download report - - { - e.preventDefault(); - if (isScanInProgress(scanStatus)) return; - setStartScanInfo({ - start: true, - nodeId, - nodeType, - }); - }} - disabled={isScanInProgress(scanStatus)} - > - Start scan - - { - setScanIdToDelete(scanId); - setNodeIdToDelete(nodeId); - setShowDeleteDialog(true); - }} - > - - Delete - - - - } - > - {trigger} - + <> + {openStopScanModal && ( + + )} + + + { + if (!isScanComplete(scanStatus)) return; + e.preventDefault(); + onDownloadAction(); + }} + disabled={!isScanComplete(scanStatus)} + > + Download report + + { + e.preventDefault(); + if (isScanInProgress(scanStatus)) return; + setStartScanInfo({ + start: true, + nodeId, + nodeType, + }); + }} + disabled={isScanInProgress(scanStatus) || isScanStopping(scanStatus)} + > + Start scan + + {isScanInProgress(scanStatus) && ( + { + e.preventDefault(); + setOpenStopScanModal(true); + }} + disabled={!isScanInProgress(scanStatus)} + > + Cancel scan + + )} + + { + setScanIdToDelete(scanId); + setShowDeleteDialog(true); + }} + > + + Delete + + + + } + > + {trigger} + + ); }; @@ -541,7 +588,13 @@ const Filters = () => { ); }; -const ScansTable = () => { +const ScansTable = ({ + rowSelectionState, + setRowSelectionState, +}: { + rowSelectionState: RowSelectionState; + setRowSelectionState: React.Dispatch>; +}) => { const [searchParams, setSearchParams] = useSearchParams(); const { data } = useSuspenseQuery({ ...queries.secret.scanList({ @@ -569,12 +622,16 @@ const ScansTable = () => { nodeId: '', nodeType: '', }); - const [nodeIdToDelete, setNodeIdToDelete] = useState(''); const columnHelper = createColumnHelper(); const columns = useMemo(() => { const columns = [ + getRowSelectionColumn(columnHelper, { + size: 45, + minSize: 45, + maxSize: 45, + }), columnHelper.display({ id: 'actions', enableSorting: false, @@ -585,7 +642,6 @@ const ScansTable = () => { nodeType={cell.row.original.node_type} scanStatus={cell.row.original.status} setScanIdToDelete={setScanIdToDelete} - setNodeIdToDelete={setNodeIdToDelete} setShowDeleteDialog={setShowDeleteDialog} setStartScanInfo={setStartScanInfo} trigger={ @@ -824,9 +880,11 @@ const ScansTable = () => { {showDeleteDialog && ( { + // + }} /> )} {startScanInfo.start && ( @@ -844,8 +902,12 @@ const ScansTable = () => { showAdvancedOptions: true, scanType: ScanTypeEnum.SecretScan, data: { - nodeIds: [startScanInfo.nodeId], - nodeType: startScanInfo.nodeType, + nodes: [ + { + nodeId: startScanInfo.nodeId, + nodeType: startScanInfo.nodeType, + }, + ], }, } as ConfigureScanModalProps['scanOptions'] } @@ -854,6 +916,17 @@ const ScansTable = () => { { + return JSON.stringify({ + scanId: row.scan_id, + nodeId: row.node_id, + nodeType: row.node_type, + updatedAt: row.updated_at, + }); + }} enablePagination manualPagination enableColumnResizing @@ -910,6 +983,90 @@ const ScansTable = () => { ); }; + +const BulkActions = ({ + selectedRows, + setIdsToDelete, + setShowDeleteDialog, + setShowCancelScanDialog, + setRowSelectionState, +}: { + selectedRows: { + scanId: string; + nodeId: string; + nodeType: string; + }[]; + setIdsToDelete: React.Dispatch>; + setShowDeleteDialog: React.Dispatch>; + setShowCancelScanDialog: React.Dispatch>; + setRowSelectionState: React.Dispatch>; +}) => { + const [openStartScan, setOpenStartScan] = useState(false); + return ( + <> + {openStartScan && ( + setOpenStartScan(false)} + onSuccess={() => setRowSelectionState({})} + scanOptions={ + { + showAdvancedOptions: true, + scanType: ScanTypeEnum.SecretScan, + data: { + nodes: selectedRows.map((row) => { + return { + nodeId: row.nodeId, + nodeType: row.nodeType, + }; + }), + }, + } as ConfigureScanModalProps['scanOptions'] + } + /> + )} + + + + + + ); +}; + const SecretScans = () => { const [searchParams] = useSearchParams(); @@ -918,6 +1075,23 @@ const SecretScans = () => { queryKey: queries.secret.scanList._def, }); + const [rowSelectionState, setRowSelectionState] = useState({}); + const [idsToDelete, setIdsToDelete] = useState([]); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [showCancelScanDialog, setShowCancelScanDialog] = useState(false); + + const selectedRows = useMemo< + { + scanId: string; + nodeId: string; + nodeType: string; + }[] + >(() => { + return Object.keys(rowSelectionState).map((item) => { + return JSON.parse(item); + }); + }, [rowSelectionState]); + return (
@@ -939,9 +1113,16 @@ const SecretScans = () => {
+
+ {filtersExpanded ? : null} }> - + + {showDeleteDialog && ( + { + setRowSelectionState({}); + }} + /> + )} + {showCancelScanDialog ? ( + row.scanId)} + scanType={ScanTypeEnum.SecretScan} + onCancelScanSuccess={() => { + setRowSelectionState({}); + }} + /> + ) : null}
); diff --git a/deepfence_frontend/apps/dashboard/src/features/settings/pages/ScanHistoryAndDbManagement.tsx b/deepfence_frontend/apps/dashboard/src/features/settings/pages/ScanHistoryAndDbManagement.tsx index 8b4a8e60e8..fac870da67 100644 --- a/deepfence_frontend/apps/dashboard/src/features/settings/pages/ScanHistoryAndDbManagement.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/settings/pages/ScanHistoryAndDbManagement.tsx @@ -74,7 +74,7 @@ const action = async ({ request }: ActionFunctionArgs): Promise { + const [searchParams] = useSearchParams(); return useSuspenseQuery({ - ...queries.setting.listUserActivityLogs(), + ...queries.setting.listUserActivityLogs({ + page: getPageFromSearchParams(searchParams), + pageSize: parseInt(searchParams.get('size') ?? String(DEFAULT_PAGE_SIZE)), + }), + keepPreviousData: true, }); }; const AuditTable = () => { const columnHelper = createColumnHelper(); const { copy, isCopied } = useCopyToClipboardState(); - const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [searchParams, setSearchParams] = useSearchParams(); const { data } = useUserActivityLogs(); const columns = useMemo(() => { @@ -41,6 +48,7 @@ const AuditTable = () => { minSize: 30, size: 35, maxSize: 40, + enableSorting: false, }), columnHelper.accessor('event', { cell: (cell) => cell.getValue(), @@ -48,6 +56,7 @@ const AuditTable = () => { minSize: 30, size: 30, maxSize: 40, + enableSorting: false, }), columnHelper.accessor('action', { cell: (cell) => , @@ -55,6 +64,7 @@ const AuditTable = () => { minSize: 20, size: 25, maxSize: 30, + enableSorting: false, }), columnHelper.accessor('email', { cell: (cell) => cell.getValue(), @@ -62,6 +72,7 @@ const AuditTable = () => { minSize: 30, size: 50, maxSize: 60, + enableSorting: false, }), columnHelper.accessor('role', { cell: (cell) => cell.getValue(), @@ -69,6 +80,7 @@ const AuditTable = () => { minSize: 30, size: 30, maxSize: 35, + enableSorting: false, }), columnHelper.accessor('resources', { cell: (cell) => { @@ -102,6 +114,7 @@ const AuditTable = () => { minSize: 30, size: 30, maxSize: 40, + enableSorting: false, }), ]; return columns; @@ -123,12 +136,35 @@ const AuditTable = () => { data={data.data || []} columns={columns} enablePagination - pageSize={pageSize} enablePageResize + manualPagination + enableColumnResizing + approximatePagination + totalRows={data?.pagination?.totalRows} + pageSize={parseInt(searchParams.get('size') ?? String(DEFAULT_PAGE_SIZE))} + pageIndex={data?.pagination?.currentPage} + onPaginationChange={(updaterOrValue) => { + let newPageIndex = 0; + if (typeof updaterOrValue === 'function') { + newPageIndex = updaterOrValue({ + pageIndex: data?.pagination?.currentPage ?? 0, + pageSize: parseInt(searchParams.get('size') ?? String(DEFAULT_PAGE_SIZE)), + }).pageIndex; + } else { + newPageIndex = updaterOrValue.pageIndex; + } + setSearchParams((prev) => { + prev.set('page', String(newPageIndex)); + return prev; + }); + }} onPageResize={(newSize) => { - setPageSize(newSize); + setSearchParams((prev) => { + prev.set('size', String(newSize)); + prev.delete('page'); + return prev; + }); }} - enableSorting noDataElement={} /> )} diff --git a/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Header.tsx b/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Header.tsx index f87f870a4f..68220ecdc7 100644 --- a/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Header.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Header.tsx @@ -91,8 +91,10 @@ export const Header = ({ e.preventDefault(); onStartScanClick({ data: { - nodeIds: [nodeId], - nodeType, + nodes: [nodeId].map((nodeId) => ({ + nodeId, + nodeType, + })), }, showAdvancedOptions: true, scanType: ScanTypeEnum.VulnerabilityScan, @@ -109,8 +111,10 @@ export const Header = ({ e.preventDefault(); onStartScanClick({ data: { - nodeIds: [nodeId], - nodeType, + nodes: [nodeId].map((nodeId) => ({ + nodeId, + nodeType, + })), }, scanType: ScanTypeEnum.SecretScan, showAdvancedOptions: true, @@ -127,8 +131,10 @@ export const Header = ({ e.preventDefault(); onStartScanClick({ data: { - nodeIds: [nodeId], - nodeType, + nodes: [nodeId].map((nodeId) => ({ + nodeId, + nodeType, + })), }, scanType: ScanTypeEnum.MalwareScan, showAdvancedOptions: true, diff --git a/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Metadata.tsx b/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Metadata.tsx index 3d3b7f938e..d393058c21 100644 --- a/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Metadata.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/topology/components/node-details/Metadata.tsx @@ -1,5 +1,15 @@ import { upperCase } from 'lodash-es'; +import { convertSeconds } from '@/utils/date'; + +const uptimeText = (uptime: boolean | string) => { + return typeof uptime === 'string' ? `since ${convertSeconds(+uptime)}` : ''; +}; + +const timeFormatKey = { + uptime: 'uptime', +}; + export const Metadata = ({ data, title, @@ -22,7 +32,7 @@ export const Metadata = ({ {toTopologyMetadataKey(key)}
- {data[key]} + {key in timeFormatKey ? uptimeText(data[key]) : data[key]}
))} diff --git a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/ContainersTable.tsx b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/ContainersTable.tsx index 3f39355a79..5ba89e4e11 100644 --- a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/ContainersTable.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/ContainersTable.tsx @@ -122,8 +122,12 @@ const BulkActions = ({ nodeIds }: { nodeIds: string[] }) => { showAdvancedOptions: nodeIds.length === 1, scanType: ScanTypeEnum.VulnerabilityScan, data: { - nodeIds, - nodeType: VulnerabilityScanNodeTypeEnum.container, + nodes: nodeIds.map((nodeId) => { + return { + nodeId, + nodeType: VulnerabilityScanNodeTypeEnum.container, + }; + }), }, }); }} @@ -138,8 +142,12 @@ const BulkActions = ({ nodeIds }: { nodeIds: string[] }) => { showAdvancedOptions: nodeIds.length === 1, scanType: ScanTypeEnum.SecretScan, data: { - nodeIds, - nodeType: SecretScanNodeTypeEnum.container, + nodes: nodeIds.map((nodeId) => { + return { + nodeId, + nodeType: SecretScanNodeTypeEnum.container, + }; + }), }, }); }} @@ -154,8 +162,12 @@ const BulkActions = ({ nodeIds }: { nodeIds: string[] }) => { showAdvancedOptions: nodeIds.length === 1, scanType: ScanTypeEnum.MalwareScan, data: { - nodeIds, - nodeType: MalwareScanNodeTypeEnum.container, + nodes: nodeIds.map((nodeId) => { + return { + nodeId, + nodeType: MalwareScanNodeTypeEnum.container, + }; + }), }, }); }} diff --git a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/HostsTable.tsx b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/HostsTable.tsx index 3fdcf43828..92009653aa 100644 --- a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/HostsTable.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/HostsTable.tsx @@ -131,8 +131,12 @@ const BulkActions = ({ showAdvancedOptions: nodesWithAgentRunning.length === 1, scanType: ScanTypeEnum.VulnerabilityScan, data: { - nodeIds: nodesWithAgentRunning.map((node) => node.nodeId), - nodeType: VulnerabilityScanNodeTypeEnum.host, + nodes: nodesWithAgentRunning.map((node) => { + return { + nodeId: node.nodeId, + nodeType: VulnerabilityScanNodeTypeEnum.host, + }; + }), }, }); }} @@ -147,8 +151,12 @@ const BulkActions = ({ showAdvancedOptions: nodesWithAgentRunning.length === 1, scanType: ScanTypeEnum.SecretScan, data: { - nodeIds: nodesWithAgentRunning.map((node) => node.nodeId), - nodeType: SecretScanNodeTypeEnum.host, + nodes: nodesWithAgentRunning.map((node) => { + return { + nodeId: node.nodeId, + nodeType: SecretScanNodeTypeEnum.host, + }; + }), }, }); }} @@ -163,8 +171,12 @@ const BulkActions = ({ showAdvancedOptions: nodesWithAgentRunning.length === 1, scanType: ScanTypeEnum.MalwareScan, data: { - nodeIds: nodesWithAgentRunning.map((node) => node.nodeId), - nodeType: MalwareScanNodeTypeEnum.host, + nodes: nodesWithAgentRunning.map((node) => { + return { + nodeId: node.nodeId, + nodeType: MalwareScanNodeTypeEnum.host, + }; + }), }, }); }} diff --git a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/KubernetesTable.tsx b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/KubernetesTable.tsx index 4dd6a71610..2d8668a699 100644 --- a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/KubernetesTable.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/KubernetesTable.tsx @@ -219,8 +219,12 @@ const BulkActions = ({ showAdvancedOptions: nodesWithAgentRunning.length === 1, scanType: ScanTypeEnum.VulnerabilityScan, data: { - nodeIds: nodesWithAgentRunning.map((node) => node.nodeId), - nodeType: VulnerabilityScanNodeTypeEnum.kubernetes_cluster, + nodes: nodesWithAgentRunning.map((node) => { + return { + nodeId: node.nodeId, + nodeType: VulnerabilityScanNodeTypeEnum.kubernetes_cluster, + }; + }), }, }); }} @@ -235,8 +239,12 @@ const BulkActions = ({ showAdvancedOptions: nodesWithAgentRunning.length === 1, scanType: ScanTypeEnum.SecretScan, data: { - nodeIds: nodesWithAgentRunning.map((node) => node.nodeId), - nodeType: SecretScanNodeTypeEnum.kubernetes_cluster, + nodes: nodesWithAgentRunning.map((node) => { + return { + nodeId: node.nodeId, + nodeType: SecretScanNodeTypeEnum.kubernetes_cluster, + }; + }), }, }); }} @@ -251,8 +259,12 @@ const BulkActions = ({ showAdvancedOptions: nodesWithAgentRunning.length === 1, scanType: ScanTypeEnum.MalwareScan, data: { - nodeIds: nodesWithAgentRunning.map((node) => node.nodeId), - nodeType: MalwareScanNodeTypeEnum.kubernetes_cluster, + nodes: nodesWithAgentRunning.map((node) => { + return { + nodeId: node.nodeId, + nodeType: MalwareScanNodeTypeEnum.kubernetes_cluster, + }; + }), }, }); }} diff --git a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/PodsTable.tsx b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/PodsTable.tsx index 64464edef2..89e3e75c14 100644 --- a/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/PodsTable.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/topology/data-components/tables/PodsTable.tsx @@ -311,8 +311,12 @@ const BulkActions = ({ nodes }: { nodes: string[] }) => { showAdvancedOptions: nodes.length === 1, scanType: ScanTypeEnum.VulnerabilityScan, data: { - nodeIds: nodes, - nodeType: VulnerabilityScanNodeTypeEnum.pod, + nodes: nodes.map((nodeId) => { + return { + nodeId, + nodeType: VulnerabilityScanNodeTypeEnum.pod, + }; + }), }, }); }} @@ -327,8 +331,12 @@ const BulkActions = ({ nodes }: { nodes: string[] }) => { showAdvancedOptions: nodes.length === 1, scanType: ScanTypeEnum.SecretScan, data: { - nodeIds: nodes, - nodeType: SecretScanNodeTypeEnum.pod, + nodes: nodes.map((nodeId) => { + return { + nodeId, + nodeType: SecretScanNodeTypeEnum.pod, + }; + }), }, }); }} @@ -343,8 +351,12 @@ const BulkActions = ({ nodes }: { nodes: string[] }) => { showAdvancedOptions: nodes.length === 1, scanType: ScanTypeEnum.MalwareScan, data: { - nodeIds: nodes, - nodeType: MalwareScanNodeTypeEnum.pod, + nodes: nodes.map((nodeId) => { + return { + nodeId, + nodeType: MalwareScanNodeTypeEnum.pod, + }; + }), }, }); }} diff --git a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/MostExploitableVulnerabilities.tsx b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/MostExploitableVulnerabilities.tsx index 0e48e57366..041247865d 100644 --- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/MostExploitableVulnerabilities.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/MostExploitableVulnerabilities.tsx @@ -19,6 +19,10 @@ import { import { ModelVulnerability } from '@/api/generated'; import { DFLink } from '@/components/DFLink'; import { FilterBadge } from '@/components/filters/FilterBadge'; +import { SearchableClusterList } from '@/components/forms/SearchableClusterList'; +import { SearchableContainerList } from '@/components/forms/SearchableContainerList'; +import { SearchableHostList } from '@/components/forms/SearchableHostList'; +import { SearchableImageList } from '@/components/forms/SearchableImageList'; import { FilterIcon } from '@/components/icons/common/Filter'; import { PopOutIcon } from '@/components/icons/common/PopOut'; import { TimesIcon } from '@/components/icons/common/Times'; @@ -26,6 +30,7 @@ import { CveCVSSScore, SeverityBadge } from '@/components/SeverityBadge'; import { VulnerabilityIcon } from '@/components/sideNavigation/icons/Vulnerability'; import { TruncatedText } from '@/components/TruncatedText'; import { queries } from '@/queries'; +import { ScanTypeEnum } from '@/types/common'; const DEFAULT_PAGE_SIZE = 10; @@ -126,6 +131,89 @@ const Filters = () => { ); })} + { + setSearchParams((prev) => { + prev.delete('hosts'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('hosts'); + value.forEach((host) => { + prev.append('hosts', host); + }); + prev.delete('page'); + return prev; + }); + }} + /> + { + setSearchParams((prev) => { + prev.delete('containers'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('containers'); + value.forEach((container) => { + prev.append('containers', container); + }); + prev.delete('page'); + return prev; + }); + }} + /> + { + setSearchParams((prev) => { + prev.delete('containerImages'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('containerImages'); + value.forEach((containerImage) => { + prev.append('containerImages', containerImage); + }); + prev.delete('page'); + return prev; + }); + }} + /> + { + setSearchParams((prev) => { + prev.delete('clusters'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('clusters'); + value.forEach((cluster) => { + prev.append('clusters', cluster); + }); + prev.delete('page'); + return prev; + }); + }} + /> {appliedFilterCount > 0 ? (
@@ -290,6 +378,10 @@ const MostExploitTable = () => { ...queries.vulnerability.mostExploitableVulnerabilities({ liveConnection: searchParams.getAll('liveConnection'), severity: searchParams.getAll('severity'), + hostIds: searchParams.getAll('hosts'), + containerIds: searchParams.getAll('containers'), + containerImageIds: searchParams.getAll('containerImages'), + clusterIds: searchParams.getAll('clusters'), }), }); diff --git a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/UniqueVulnerabilities.tsx b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/UniqueVulnerabilities.tsx index 7b7bb35ceb..e5f1d33cc3 100644 --- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/UniqueVulnerabilities.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/UniqueVulnerabilities.tsx @@ -20,6 +20,10 @@ import { import { ModelVulnerability } from '@/api/generated'; import { DFLink } from '@/components/DFLink'; import { FilterBadge } from '@/components/filters/FilterBadge'; +import { SearchableClusterList } from '@/components/forms/SearchableClusterList'; +import { SearchableContainerList } from '@/components/forms/SearchableContainerList'; +import { SearchableHostList } from '@/components/forms/SearchableHostList'; +import { SearchableImageList } from '@/components/forms/SearchableImageList'; import { FilterIcon } from '@/components/icons/common/Filter'; import { PopOutIcon } from '@/components/icons/common/PopOut'; import { TimesIcon } from '@/components/icons/common/Times'; @@ -27,6 +31,7 @@ import { CveCVSSScore, SeverityBadge } from '@/components/SeverityBadge'; import { VulnerabilityIcon } from '@/components/sideNavigation/icons/Vulnerability'; import { TruncatedText } from '@/components/TruncatedText'; import { queries } from '@/queries'; +import { ScanTypeEnum } from '@/types/common'; import { getOrderFromSearchParams, useSortingState } from '@/utils/table'; const DEFAULT_PAGE_SIZE = 10; @@ -128,6 +133,89 @@ const Filters = () => { ); })} + { + setSearchParams((prev) => { + prev.delete('hosts'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('hosts'); + value.forEach((host) => { + prev.append('hosts', host); + }); + prev.delete('page'); + return prev; + }); + }} + /> + { + setSearchParams((prev) => { + prev.delete('containers'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('containers'); + value.forEach((container) => { + prev.append('containers', container); + }); + prev.delete('page'); + return prev; + }); + }} + /> + { + setSearchParams((prev) => { + prev.delete('containerImages'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('containerImages'); + value.forEach((containerImage) => { + prev.append('containerImages', containerImage); + }); + prev.delete('page'); + return prev; + }); + }} + /> + { + setSearchParams((prev) => { + prev.delete('clusters'); + prev.delete('page'); + return prev; + }); + }} + onChange={(value) => { + setSearchParams((prev) => { + prev.delete('clusters'); + value.forEach((cluster) => { + prev.append('clusters', cluster); + }); + prev.delete('page'); + return prev; + }); + }} + />
{appliedFilterCount > 0 ? (
@@ -284,6 +372,10 @@ const UniqueTable = () => { page: parseInt(searchParams.get('page') ?? '0', 10), order: getOrderFromSearchParams(searchParams), severity: searchParams.getAll('severity'), + hostIds: searchParams.getAll('hosts'), + containerIds: searchParams.getAll('containers'), + containerImageIds: searchParams.getAll('containerImages'), + clusterIds: searchParams.getAll('clusters'), }), keepPreviousData: true, }); diff --git a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityDetailModal.tsx b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityDetailModal.tsx index 38b347a77d..67cf8d7db9 100644 --- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityDetailModal.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityDetailModal.tsx @@ -20,6 +20,7 @@ import { PopOutIcon } from '@/components/icons/common/PopOut'; import { CveCVSSScore, SeverityBadge } from '@/components/SeverityBadge'; import { VulnerabilityIcon } from '@/components/sideNavigation/icons/Vulnerability'; import { queries } from '@/queries'; +import { formatMilliseconds } from '@/utils/date'; import { replacebyUppercaseCharacters } from '@/utils/label'; import { usePageNavigation } from '@/utils/usePageNavigation'; @@ -36,6 +37,10 @@ function processLabel(labelKey: string) { return replacebyUppercaseCharacters(labelKey); } +const timeFormatKey = { + updated_at: 'updated_at', +}; + const Header = () => { const { data: { data: vulnerabilities }, @@ -166,11 +171,11 @@ const DetailsComponent = () => { const omitFields: (keyof ModelVulnerability)[] = [ 'cve_id', - 'updated_at', 'cve_description', 'cve_link', 'cve_severity', 'cve_cvss_score', + 'resources', 'urls', ]; @@ -207,11 +212,23 @@ const DetailsComponent = () => {
- {valueAsStr} + {key in timeFormatKey ? formatMilliseconds(+valueAsStr) : valueAsStr}
); })} +
+
+
+ Resources +
+ +
+
+ {cve.resources?.toString()} +
+
+ {cve.urls?.length ? (
URLs
diff --git a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx index 68a18ff714..7b26ad2c04 100644 --- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx @@ -36,6 +36,7 @@ import { import { getScanResultsApiClient } from '@/api/api'; import { + ModelScanInfo, ModelVulnerability, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, @@ -56,12 +57,15 @@ import { FilterIcon } from '@/components/icons/common/Filter'; import { PopOutIcon } from '@/components/icons/common/PopOut'; import { TimesIcon } from '@/components/icons/common/Times'; import { TrashLineIcon } from '@/components/icons/common/TrashLine'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanHistoryDropdown } from '@/components/scan-history/HistoryList'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { ScanStatusInError, ScanStatusInProgress, ScanStatusNoData, + ScanStatusStopped, + ScanStatusStopping, } from '@/components/ScanStatusMessage'; import { CveCVSSScore, SeverityBadge } from '@/components/SeverityBadge'; import { VulnerabilityIcon } from '@/components/sideNavigation/icons/Vulnerability'; @@ -78,7 +82,13 @@ import { get403Message } from '@/utils/403'; import { apiWrapper } from '@/utils/api'; import { formatMilliseconds } from '@/utils/date'; import { abbreviateNumber } from '@/utils/number'; -import { isScanComplete, isScanFailed, isScanInProgress } from '@/utils/scan'; +import { + isScanComplete, + isScanFailed, + isScanInProgress, + isScanStopped, + isScanStopping, +} from '@/utils/scan'; import { getOrderFromSearchParams, getPageFromSearchParams, @@ -534,6 +544,7 @@ const HistoryControls = () => { const { navigate, goBack } = usePageNavigation(); const { downloadScan } = useDownloadScan(); + const [openStopScanModal, setOpenStopScanModal] = useState(false); const [showScanCompareModal, setShowScanCompareModal] = useState(false); const [scanIdToDelete, setScanIdToDelete] = useState(null); @@ -609,7 +620,16 @@ const HistoryControls = () => { compareInput={compareInput} /> )} -
+
+ {openStopScanModal && ( + + )} + ({ id: item.scanId, @@ -672,7 +692,7 @@ const HistoryControls = () => { )}
- {!isScanInProgress(status ?? '') && ( + {!isScanInProgress(status ?? '') ? ( <>
@@ -726,6 +746,19 @@ const HistoryControls = () => {
+ ) : ( + )}
@@ -1175,6 +1208,20 @@ const TablePlaceholder = ({
); } + if (isScanStopped(scanStatus)) { + return ( +
+ +
+ ); + } + if (isScanStopping(scanStatus)) { + return ( +
+ +
+ ); + } if (isScanInProgress(scanStatus)) { return (
@@ -1472,15 +1519,15 @@ const SeverityCounts = ({ ); }; -const SeverityCountWidget = () => { - const { - data: { data, scanStatusResult }, - } = useScanResults(); - - const severityCounts: { - [k: string]: number; - } = data?.severityCounts ?? {}; - +const ScanStatusWrapper = ({ + children, + scanStatusResult, + displayNoData, +}: { + children: React.ReactNode; + scanStatusResult: ModelScanInfo | undefined; + displayNoData?: boolean; +}) => { if (isScanFailed(scanStatusResult?.status ?? '')) { return (
@@ -1488,47 +1535,19 @@ const SeverityCountWidget = () => {
); } - if (isScanInProgress(scanStatusResult?.status ?? '')) { + + if (isScanStopped(scanStatusResult?.status ?? '')) { return (
- +
); } - return ( -
-
- -
-
-
- {keys(severityCounts).length === 0 ? ( -
- -
- ) : ( -
- -
- )} -
-
-
- ); -}; - -const Top5Widget = () => { - const { data } = useTop5Vulnerabilities(); - const [searchParams] = useSearchParams(); - - const { data: scanResult } = useScanResults(); - const { scanStatusResult } = scanResult; - - if (isScanFailed(scanStatusResult?.status ?? '')) { + if (isScanStopping(scanStatusResult?.status ?? '')) { return (
- +
); } @@ -1540,8 +1559,7 @@ const Top5Widget = () => {
); } - - if (!data.data || data.data?.length === 0) { + if (displayNoData) { return (
@@ -1549,43 +1567,90 @@ const Top5Widget = () => { ); } + return <>{children}; +}; +const SeverityCountWidget = () => { + const { + data: { data, scanStatusResult }, + } = useScanResults(); + + const severityCounts: { + [k: string]: number; + } = data?.severityCounts ?? {}; + return ( -
- - {data.data?.map((cve) => { - return ( - - + + ); + })} + +
- -
- + +
+
+ +
+
+
+ {keys(severityCounts).length === 0 ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+
+
+ ); +}; + +const Top5Widget = () => { + const { data } = useTop5Vulnerabilities(); + const [searchParams] = useSearchParams(); + + const { data: scanResult } = useScanResults(); + const { scanStatusResult } = scanResult; + + return ( + + + + {data.data?.map((cve) => { + return ( + + + + - - - - ); - })} - -
+ +
+ +
+
+ +
+
+
+
+
-
- +
+
+
- -
-
- -
-
-
- -
-
+
+ ); }; @@ -1644,35 +1709,14 @@ const TopAttackPathsWidget = () => { const { data } = useScanResults(); const nodeId = data?.scanStatusResult?.node_id; - if (isScanFailed(data?.scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - if (isScanInProgress(data?.scanStatusResult?.status ?? '')) { - return ( -
- -
- ); - } - - if (!nodeId) { - return ( -
-
- -
-
No data available
-
- ); - } return (
- + + +
); }; diff --git a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScans.tsx b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScans.tsx index 34a937f8ca..8543a32f2a 100644 --- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScans.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScans.tsx @@ -20,7 +20,9 @@ import { createColumnHelper, Dropdown, DropdownItem, + getRowSelectionColumn, Modal, + RowSelectionState, SortingState, Table, TableSkeleton, @@ -28,7 +30,7 @@ import { import { getScanResultsApiClient, getVulnerabilityApiClient } from '@/api/api'; import { - ModelVulnerabilityScanConfigLanguageLanguageEnum, + ModelBulkDeleteScansRequestScanTypeEnum, UtilsReportFiltersNodeTypeEnum, UtilsReportFiltersScanTypeEnum, } from '@/api/generated'; @@ -46,6 +48,8 @@ import { EllipsisIcon } from '@/components/icons/common/Ellipsis'; import { ErrorStandardLineIcon } from '@/components/icons/common/ErrorStandardLine'; import { FilterIcon } from '@/components/icons/common/Filter'; import { TimesIcon } from '@/components/icons/common/Times'; +import { TrashLineIcon } from '@/components/icons/common/TrashLine'; +import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm'; import { ScanStatusBadge } from '@/components/ScanStatusBadge'; import { VulnerabilityIcon } from '@/components/sideNavigation/icons/Vulnerability'; import { TruncatedText } from '@/components/TruncatedText'; @@ -64,6 +68,7 @@ import { isNeverScanned, isScanComplete, isScanInProgress, + isScanStopping, SCAN_STATUS_GROUPS, VulnerabilityScanGroupedStatus, } from '@/utils/scan'; @@ -91,19 +96,29 @@ const action = async ({ > => { const formData = await request.formData(); const actionType = formData.get('actionType'); - const scanId = formData.get('scanId'); - const nodeId = formData.get('nodeId'); - if (!actionType || !scanId || !nodeId) { - throw new Error('Invalid action'); - } + const scanIds = formData.getAll('scanId'); + if (!actionType || scanIds.length === 0) { + throw new Error('Action type and scan id is required'); + } if (actionType === ActionEnumType.DELETE) { const resultApi = apiWrapper({ - fn: getScanResultsApiClient().deleteScanResultsForScanID, + fn: getScanResultsApiClient().bulkDeleteScans, }); const result = await resultApi({ - scanId: scanId.toString(), - scanType: ScanTypeEnum.VulnerabilityScan, + modelBulkDeleteScansRequest: { + filters: { + compare_filter: null, + contains_filter: { + filter_in: { + node_id: scanIds, + }, + }, + order_filter: { order_fields: [] }, + match_filter: { filter_in: {} }, + }, + scan_type: ModelBulkDeleteScansRequestScanTypeEnum.Vulnerability, + }, }); if (!result.ok) { if (result.error.response.status === 400 || result.error.response.status === 409) { @@ -125,7 +140,7 @@ const action = async ({ fn: getVulnerabilityApiClient().downloadSBOM, }); const result = await resultApi({ - modelSbomRequest: { scan_id: scanId.toString() }, + modelSbomRequest: { scan_id: scanIds[0].toString() }, }); if (!result.ok) { if (result.error.response.status === 400 || result.error.response.status === 409) { @@ -155,14 +170,14 @@ const action = async ({ const DeleteConfirmationModal = ({ showDialog, - scanId, - nodeId, + scanIds, setShowDialog, + onDeleteSuccess, }: { showDialog: boolean; - scanId: string; - nodeId: string; + scanIds: string[]; setShowDialog: React.Dispatch>; + onDeleteSuccess: () => void; }) => { const fetcher = useFetcher(); @@ -170,15 +185,24 @@ const DeleteConfirmationModal = ({ (actionType: string) => { const formData = new FormData(); formData.append('actionType', actionType); - formData.append('scanId', scanId); - formData.append('nodeId', nodeId); + scanIds.forEach((scanId) => formData.append('scanId', scanId)); fetcher.submit(formData, { method: 'post', }); }, - [scanId, nodeId, fetcher], + [scanIds, fetcher], ); + useEffect(() => { + if ( + fetcher.state === 'idle' && + fetcher.data?.success && + fetcher.data.action === ActionEnumType.DELETE + ) { + onDeleteSuccess(); + } + }, [fetcher]); + return ( >; setScanIdToDelete: React.Dispatch>; - setNodeIdToDelete: React.Dispatch>; setSelectedNode: React.Dispatch< React.SetStateAction<{ nodeName: string; @@ -275,6 +297,7 @@ const ActionDropdown = ({ }) => { const fetcher = useFetcher(); const [open, setOpen] = useState(false); + const [openStopScanModal, setOpenStopScanModal] = useState(false); const { downloadScan } = useDownloadScan(); const onDownloadAction = useCallback( (actionType?: ActionEnumType) => { @@ -303,81 +326,109 @@ const ActionDropdown = ({ }, [fetcher]); return ( - - { - if (!isScanComplete(scanStatus)) return; - e.preventDefault(); - onDownloadAction(); - }} - disabled={!isScanComplete(scanStatus)} - > - Download Report - - { - if (!isScanComplete(scanStatus)) return; - e.preventDefault(); - onDownloadAction(ActionEnumType.DOWNLOAD_SBOM); - }} - disabled={!isScanComplete(scanStatus)} - > - Download SBOM - - { - e.preventDefault(); - if (!isScanComplete(scanStatus)) return; - setSelectedNode({ - scanId, - nodeName: nodeName?.length ? nodeName : nodeId, - }); - }} - disabled={!isScanComplete(scanStatus)} - > - View SBOM - - { - e.preventDefault(); - if (isScanInProgress(scanStatus)) return; - setStartScanInfo({ - start: true, - nodeId, - nodeType, - }); - }} - disabled={isScanInProgress(scanStatus)} - > - Start scan - - { - setScanIdToDelete(scanId); - setNodeIdToDelete(nodeId); - setShowDeleteDialog(true); - }} - disabled={isScanInProgress(scanStatus)} - > - - Delete - - - - } - > - {trigger} - + <> + {openStopScanModal && ( + + )} + + + { + if (!isScanComplete(scanStatus)) return; + e.preventDefault(); + onDownloadAction(); + }} + disabled={!isScanComplete(scanStatus)} + > + Download Report + + { + if (!isScanComplete(scanStatus)) return; + e.preventDefault(); + onDownloadAction(ActionEnumType.DOWNLOAD_SBOM); + }} + disabled={!isScanComplete(scanStatus)} + > + Download SBOM + + { + e.preventDefault(); + if (!isScanComplete(scanStatus)) return; + setSelectedNode({ + scanId, + nodeName: nodeName?.length ? nodeName : nodeId, + }); + }} + disabled={!isScanComplete(scanStatus)} + > + View SBOM + + { + e.preventDefault(); + if (isScanInProgress(scanStatus)) return; + setStartScanInfo({ + start: true, + nodeId, + nodeType, + }); + }} + disabled={isScanInProgress(scanStatus) || isScanStopping(scanStatus)} + > + Start scan + + {isScanInProgress(scanStatus) && ( + { + e.preventDefault(); + setOpenStopScanModal(true); + }} + disabled={!isScanInProgress(scanStatus)} + > + Cancel scan + + )} + + { + setScanIdToDelete(scanId); + setShowDeleteDialog(true); + }} + disabled={isScanInProgress(scanStatus)} + > + + Delete + + + + } + > + {trigger} + + ); }; -const ScansTable = () => { +const ScansTable = ({ + rowSelectionState, + setRowSelectionState, +}: { + rowSelectionState: RowSelectionState; + setRowSelectionState: React.Dispatch>; +}) => { const [searchParams, setSearchParams] = useSearchParams(); const { data } = useSuspenseQuery({ ...queries.vulnerability.scanList({ @@ -406,7 +457,6 @@ const ScansTable = () => { nodeType: '', }); const [scanIdToDelete, setScanIdToDelete] = useState(''); - const [nodeIdToDelete, setNodeIdToDelete] = useState(''); const [selectedNode, setSelectedNode] = useState<{ nodeName: string; @@ -417,6 +467,11 @@ const ScansTable = () => { const columns = useMemo(() => { const columns = [ + getRowSelectionColumn(columnHelper, { + size: 45, + minSize: 45, + maxSize: 45, + }), columnHelper.display({ id: 'actions', enableSorting: false, @@ -428,7 +483,6 @@ const ScansTable = () => { nodeName={cell.row.original.node_name} scanStatus={cell.row.original.status} setScanIdToDelete={setScanIdToDelete} - setNodeIdToDelete={setNodeIdToDelete} setShowDeleteDialog={setShowDeleteDialog} setSelectedNode={setSelectedNode} setStartScanInfo={setStartScanInfo} @@ -684,9 +738,11 @@ const ScansTable = () => { {showDeleteDialog && ( { + // + }} /> )} {startScanInfo.start && ( @@ -704,8 +760,12 @@ const ScansTable = () => { showAdvancedOptions: true, scanType: ScanTypeEnum.VulnerabilityScan, data: { - nodeIds: [startScanInfo.nodeId], - nodeType: startScanInfo.nodeType, + nodes: [ + { + nodeId: startScanInfo.nodeId, + nodeType: startScanInfo.nodeType, + }, + ], }, } as ConfigureScanModalProps['scanOptions'] } @@ -725,6 +785,17 @@ const ScansTable = () => { { + return JSON.stringify({ + scanId: row.scan_id, + nodeId: row.node_id, + nodeType: row.node_type, + updatedAt: row.updated_at, + }); + }} enablePagination manualPagination enableColumnResizing @@ -1013,6 +1084,95 @@ const Filters = () => { ); }; +const BulkActions = ({ + selectedRows, + setIdsToDelete, + setShowDeleteDialog, + setShowCancelScanDialog, + setRowSelectionState, +}: { + selectedRows: { + scanId: string; + nodeId: string; + nodeType: string; + }[]; + setIdsToDelete: React.Dispatch>; + setShowDeleteDialog: React.Dispatch>; + setShowCancelScanDialog: React.Dispatch>; + setRowSelectionState: React.Dispatch>; +}) => { + const [openStartScan, setOpenStartScan] = useState(false); + return ( + <> + {openStartScan && ( + { + setOpenStartScan(false); + }} + onSuccess={() => { + setRowSelectionState({}); + }} + scanOptions={ + { + showAdvancedOptions: true, + scanType: ScanTypeEnum.VulnerabilityScan, + data: { + nodes: selectedRows.map((row) => { + return { + nodeId: row.nodeId, + nodeType: row.nodeType, + }; + }), + }, + } as ConfigureScanModalProps['scanOptions'] + } + /> + )} + + + + + + ); +}; + const VulnerabilityScans = () => { const isFetching = useIsFetching({ queryKey: queries.vulnerability.scanList._def, @@ -1020,6 +1180,23 @@ const VulnerabilityScans = () => { const [filtersExpanded, setFiltersExpanded] = useState(false); const [searchParams] = useSearchParams(); + const [rowSelectionState, setRowSelectionState] = useState({}); + const [idsToDelete, setIdsToDelete] = useState([]); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [showCancelScanDialog, setShowCancelScanDialog] = useState(false); + + const selectedRows = useMemo< + { + scanId: string; + nodeId: string; + nodeType: string; + }[] + >(() => { + return Object.keys(rowSelectionState).map((item) => { + return JSON.parse(item); + }); + }, [rowSelectionState]); + return (
@@ -1040,9 +1217,16 @@ const VulnerabilityScans = () => {
+
{filtersExpanded ? : null} }> - + + {showDeleteDialog && ( + { + setRowSelectionState({}); + }} + /> + )} + {showCancelScanDialog ? ( + row.scanId)} + scanType={ScanTypeEnum.VulnerabilityScan} + onCancelScanSuccess={() => { + setRowSelectionState({}); + }} + /> + ) : null}
); diff --git a/deepfence_frontend/apps/dashboard/src/queries/setting.ts b/deepfence_frontend/apps/dashboard/src/queries/setting.ts index 12a3bebc81..ae24e14ecf 100644 --- a/deepfence_frontend/apps/dashboard/src/queries/setting.ts +++ b/deepfence_frontend/apps/dashboard/src/queries/setting.ts @@ -1,6 +1,7 @@ import { createQueryKeys } from '@lukemorales/query-key-factory'; import { getDiagnosisApiClient, getSettingsApiClient, getUserApiClient } from '@/api/api'; +import { ModelGetAuditLogsRequest } from '@/api/generated'; import { get403Message } from '@/utils/403'; import { apiWrapper } from '@/utils/api'; @@ -30,14 +31,34 @@ export const settingQueries = createQueryKeys('setting', { }, }; }, - listUserActivityLogs: () => { + listUserActivityLogs: (filters: { page: number; pageSize: number }) => { return { - queryKey: ['listUserActivityLogs'], + queryKey: [{ filters }], queryFn: async () => { + const { page, pageSize } = filters; + const logsReq: ModelGetAuditLogsRequest = { + window: { + offset: page * pageSize, + size: pageSize, + }, + }; + const userApi = apiWrapper({ fn: getSettingsApiClient().getUserActivityLogs, }); - const userResponse = await userApi(); + const userResponse = await userApi({ + modelGetAuditLogsRequest: logsReq, + }); + + const logsCountApi = apiWrapper({ + fn: getSettingsApiClient().getUserActivityLogCount, + }); + const logsCount = await logsCountApi(); + + if (!logsCount.ok) { + throw logsCount.error; + } + if (!userResponse.ok) { if (userResponse.error.response.status === 400) { return { @@ -54,6 +75,10 @@ export const settingQueries = createQueryKeys('setting', { return { data: userResponse.value, + pagination: { + currentPage: page, + totalRows: logsCount.value.count, + }, }; }, }; diff --git a/deepfence_frontend/apps/dashboard/src/queries/vulnerability.tsx b/deepfence_frontend/apps/dashboard/src/queries/vulnerability.tsx index 1e1c8e0512..3f212869da 100644 --- a/deepfence_frontend/apps/dashboard/src/queries/vulnerability.tsx +++ b/deepfence_frontend/apps/dashboard/src/queries/vulnerability.tsx @@ -470,8 +470,22 @@ export const vulnerabilityQueries = createQueryKeys('vulnerability', { pageSize: number; severity: string[]; liveConnection: string[]; + hostIds: string[]; + containerIds: string[]; + containerImageIds: string[]; + clusterIds: string[]; }) => { - const { page = 1, order, severity, liveConnection, pageSize } = filters; + const { + page = 1, + order, + severity, + liveConnection, + pageSize, + hostIds, + containerIds, + containerImageIds, + clusterIds, + } = filters; return { queryKey: [{ filters }], queryFn: async () => { @@ -502,6 +516,60 @@ export const vulnerabilityQueries = createQueryKeys('vulnerability', { }, window: { offset: page * pageSize, size: pageSize }, }; + const nodeIds = [...hostIds, ...containerIds, ...containerImageIds]; + + if (nodeIds.length || clusterIds.length) { + const nodeTypes = []; + if (hostIds.length) { + nodeTypes.push('host'); + } + if (containerIds.length) { + nodeTypes.push('container'); + } + if (containerImageIds.length) { + nodeTypes.push('container_image'); + } + + const containsFilter = { + filter_in: {}, + }; + if (nodeIds.length) { + containsFilter.filter_in = { + node_id: nodeIds, + node_type: nodeTypes, + }; + } + if (clusterIds.length) { + containsFilter.filter_in = { + ...containsFilter.filter_in, + kubernetes_cluster_id: clusterIds, + }; + } + searchVulnerabilitiesRequestParams.related_node_filter = { + relation_ship: 'DETECTED', + node_filter: { + filters: { + contains_filter: { filter_in: {} }, + order_filter: { order_fields: [] }, + match_filter: { filter_in: {} }, + compare_filter: null, + }, + in_field_filter: null, + window: { + offset: 0, + size: 0, + }, + }, + next_filter: { + relation_ship: 'SCANNED', + node_filter: { + filters: { + contains_filter: containsFilter, + }, + }, + }, + }; + } if (severity.length) { searchVulnerabilitiesRequestParams.node_filter.filters.contains_filter.filter_in![ @@ -564,8 +632,19 @@ export const vulnerabilityQueries = createQueryKeys('vulnerability', { mostExploitableVulnerabilities: (filters: { severity: string[]; liveConnection: string[]; + hostIds: string[]; + containerIds: string[]; + containerImageIds: string[]; + clusterIds: string[]; }) => { - const { severity, liveConnection } = filters; + const { + severity, + liveConnection, + hostIds, + containerIds, + containerImageIds, + clusterIds, + } = filters; return { queryKey: [{ filters }], queryFn: async () => { @@ -621,6 +700,61 @@ export const vulnerabilityQueries = createQueryKeys('vulnerability', { window: { offset: 0, size: 1000 }, }; + const nodeIds = [...hostIds, ...containerIds, ...containerImageIds]; + + if (nodeIds.length || clusterIds.length) { + const nodeTypes = []; + if (hostIds.length) { + nodeTypes.push('host'); + } + if (containerIds.length) { + nodeTypes.push('container'); + } + if (containerImageIds.length) { + nodeTypes.push('container_image'); + } + + const containsFilter = { + filter_in: {}, + }; + if (nodeIds.length) { + containsFilter.filter_in = { + node_id: nodeIds, + node_type: nodeTypes, + }; + } + if (clusterIds.length) { + containsFilter.filter_in = { + ...containsFilter.filter_in, + kubernetes_cluster_id: clusterIds, + }; + } + searchVulnerabilitiesRequestParams.related_node_filter = { + relation_ship: 'DETECTED', + node_filter: { + filters: { + contains_filter: { filter_in: {} }, + order_filter: { order_fields: [] }, + match_filter: { filter_in: {} }, + compare_filter: null, + }, + in_field_filter: null, + window: { + offset: 0, + size: 0, + }, + }, + next_filter: { + relation_ship: 'SCANNED', + node_filter: { + filters: { + contains_filter: containsFilter, + }, + }, + }, + }; + } + const searchVulnerabilitiesApi = apiWrapper({ fn: getSearchApiClient().searchVulnerabilities, }); diff --git a/deepfence_frontend/apps/dashboard/src/routes/private.tsx b/deepfence_frontend/apps/dashboard/src/routes/private.tsx index 9852cb6e68..abba8ad2b8 100644 --- a/deepfence_frontend/apps/dashboard/src/routes/private.tsx +++ b/deepfence_frontend/apps/dashboard/src/routes/private.tsx @@ -5,6 +5,7 @@ import { FiveZeroZero } from '@/components/error/500'; import { scanPostureApiAction } from '@/components/scan-configure-forms/ComplianceScanConfigureForm'; import { scanMalwareApiAction } from '@/components/scan-configure-forms/MalwareScanConfigureForm'; import { scanSecretApiAction } from '@/components/scan-configure-forms/SecretScanConfigureForm'; +import { actionStopScan } from '@/components/scan-configure-forms/StopScanForm'; import { scanVulnerabilityApiAction } from '@/components/scan-configure-forms/VulnerabilityScanConfigureForm'; import { module as logoutAction } from '@/features/auth/data-components/logoutAction'; import { authenticatedRootLoader } from '@/features/common/data-component/authenticatedRoot/authenticatedRootLoader'; @@ -574,6 +575,10 @@ export const privateRoutes: CustomRouteObject[] = [ path: 'scan/posture', action: scanPostureApiAction, }, + { + path: 'scan/stop/:scanType', + action: actionStopScan, + }, { path: 'registries/add-connector', action: registryConnectorActionApi, diff --git a/deepfence_frontend/apps/dashboard/src/types/common.ts b/deepfence_frontend/apps/dashboard/src/types/common.ts index 58c44a9b89..6d937a400a 100644 --- a/deepfence_frontend/apps/dashboard/src/types/common.ts +++ b/deepfence_frontend/apps/dashboard/src/types/common.ts @@ -53,6 +53,7 @@ export enum ScanStatusEnum { complete = 'COMPLETE', error = 'ERROR', neverScanned = 'NEVER_SCANNED', + stopped = 'CANCELLED', } export const RegistryType = { diff --git a/deepfence_frontend/apps/dashboard/src/utils/date.ts b/deepfence_frontend/apps/dashboard/src/utils/date.ts index 6269650719..84491c5c42 100644 --- a/deepfence_frontend/apps/dashboard/src/utils/date.ts +++ b/deepfence_frontend/apps/dashboard/src/utils/date.ts @@ -14,3 +14,30 @@ export const formatMilliseconds = (date: number | Date | string, format?: string export const formatToRelativeTimeFromNow = (date: string | Date | number) => { return dayjs(date).fromNow(); }; + +export function convertSeconds(seconds: number): string { + const daysInAYear = 365.25; // Accounting for leap years + + if (seconds < 60) { + return `${seconds} seconds`; + } else if (seconds < 3600) { + const minutes = Math.floor(seconds / 60); + return `${minutes} minute${minutes !== 1 ? 's' : ''}`; + } else if (seconds < 86400) { + const hours = Math.floor(seconds / 3600); + return `${hours} hour${hours !== 1 ? 's' : ''}`; + } else if (seconds < 31557600) { + const days = Math.floor(seconds / 86400); + return `${days} day${days !== 1 ? 's' : ''}`; + } else { + const years = seconds / (86400 * daysInAYear); + const fractionalYears = seconds % (86400 * daysInAYear); + let fractionalSeconds = ''; + if (fractionalYears > 0) { + fractionalSeconds = convertSeconds(fractionalYears); + } + const displayYears = Math.floor(years); + + return `${displayYears} year${displayYears !== 1 ? 's' : ''} ${fractionalSeconds}`; + } +} diff --git a/deepfence_frontend/apps/dashboard/src/utils/scan.ts b/deepfence_frontend/apps/dashboard/src/utils/scan.ts index ddf89446e5..97e7a4b28f 100644 --- a/deepfence_frontend/apps/dashboard/src/utils/scan.ts +++ b/deepfence_frontend/apps/dashboard/src/utils/scan.ts @@ -24,7 +24,27 @@ export const isNeverScanned = (status: string): boolean => { }; export const isScanInProgress = (status: string): boolean => { - if (!isScanComplete(status) && !isScanFailed(status) && !isNeverScanned(status)) { + if ( + !isScanComplete(status) && + !isScanFailed(status) && + !isNeverScanned(status) && + !isScanStopped(status) && + !isScanStopping(status) + ) { + return true; + } + return false; +}; + +export const isScanStopped = (status: string): boolean => { + if (status.length && ScanStatusEnum.stopped === status) { + return true; + } + return false; +}; + +export const isScanStopping = (status: string): boolean => { + if (status.length && VULNERABILITY_SCAN_STATUS_GROUPS.cancelling.includes(status)) { return true; } return false; @@ -71,6 +91,8 @@ export enum VulnerabilityScanGroupedStatus { 'inProgress' = 'inProgress', 'error' = 'error', 'complete' = 'complete', + 'cancelled' = 'cancelled', + 'cancelling' = 'cancelling', } export const VULNERABILITY_SCAN_STATUS_GROUPS: Record< @@ -82,6 +104,8 @@ export const VULNERABILITY_SCAN_STATUS_GROUPS: Record< inProgress: ['IN_PROGRESS', 'GENERATING_SBOM', 'GENERATED_SBOM', 'SCAN_IN_PROGRESS'], error: ['ERROR'], complete: ['COMPLETE'], + cancelled: ['CANCELLED'], + cancelling: ['CANCEL_PENDING', 'CANCELLING'], }; export enum SecretScanGroupedStatus { @@ -90,6 +114,8 @@ export enum SecretScanGroupedStatus { 'inProgress' = 'inProgress', 'error' = 'error', 'complete' = 'complete', + 'cancelled' = 'cancelled', + 'cancelling' = 'cancelling', } export const SECRET_SCAN_STATUS_GROUPS: Record< @@ -101,6 +127,8 @@ export const SECRET_SCAN_STATUS_GROUPS: Record< inProgress: ['IN_PROGRESS'], error: ['ERROR'], complete: ['COMPLETE'], + cancelled: ['CANCELLED'], + cancelling: ['CANCEL_PENDING', 'CANCELLING'], }; export enum MalwareScanGroupedStatus { @@ -109,6 +137,8 @@ export enum MalwareScanGroupedStatus { 'inProgress' = 'inProgress', 'error' = 'error', 'complete' = 'complete', + 'cancelled' = 'cancelled', + 'cancelling' = 'cancelling', } export const MALWARE_SCAN_STATUS_GROUPS: Record< @@ -120,6 +150,8 @@ export const MALWARE_SCAN_STATUS_GROUPS: Record< inProgress: ['IN_PROGRESS'], error: ['ERROR'], complete: ['COMPLETE'], + cancelled: ['CANCELLED'], + cancelling: ['CANCEL_PENDING', 'CANCELLING'], }; export enum ComplianceScanGroupedStatus { @@ -128,6 +160,8 @@ export enum ComplianceScanGroupedStatus { 'inProgress' = 'inProgress', 'error' = 'error', 'complete' = 'complete', + 'cancelled' = 'cancelled', + 'cancelling' = 'cancelling', } export const COMPLIANCE_SCAN_STATUS_GROUPS: Record< @@ -139,6 +173,8 @@ export const COMPLIANCE_SCAN_STATUS_GROUPS: Record< inProgress: ['IN_PROGRESS', 'SCAN_IN_PROGRESS'], error: ['ERROR'], complete: ['COMPLETE'], + cancelled: ['CANCELLED'], + cancelling: ['CANCEL_PENDING', 'CANCELLING'], }; export const SCAN_STATUS_GROUPS = [ @@ -162,4 +198,12 @@ export const SCAN_STATUS_GROUPS = [ label: 'Complete', value: 'complete', }, + { + label: 'Cancelled', + value: 'cancelled', + }, + { + label: 'Cancelling', + value: 'cancelling', + }, ]; diff --git a/deepfence_server/apiDocs/docs.go b/deepfence_server/apiDocs/docs.go index df46731d37..bfcc625e1b 100644 --- a/deepfence_server/apiDocs/docs.go +++ b/deepfence_server/apiDocs/docs.go @@ -32,6 +32,7 @@ const ( tagReports = "Reports" tagSettings = "Settings" tagDiffAdd = "Diff Add" + tagCompletion = "Completion" securityName = "bearer_token" ) diff --git a/deepfence_server/apiDocs/operation.go b/deepfence_server/apiDocs/operation.go index 85c050763a..ba34ad7fb0 100644 --- a/deepfence_server/apiDocs/operation.go +++ b/deepfence_server/apiDocs/operation.go @@ -6,6 +6,7 @@ import ( "github.com/deepfence/ThreatMapper/deepfence_server/diagnosis" "github.com/deepfence/ThreatMapper/deepfence_server/ingesters" . "github.com/deepfence/ThreatMapper/deepfence_server/model" + . "github.com/deepfence/ThreatMapper/deepfence_server/reporters/completion" . "github.com/deepfence/ThreatMapper/deepfence_server/reporters/graph" . "github.com/deepfence/ThreatMapper/deepfence_server/reporters/lookup" . "github.com/deepfence/ThreatMapper/deepfence_server/reporters/search" @@ -734,9 +735,12 @@ func (d *OpenApiDocs) AddSettingsOperations() { d.AddOperation("updateSetting", http.MethodPatch, "/deepfence/settings/global-settings/{id}", "Update setting", "Update setting", http.StatusNoContent, []string{tagSettings}, bearerToken, new(SettingUpdateRequest), nil) - d.AddOperation("getUserActivityLogs", http.MethodGet, "/deepfence/settings/user-activity-log", - "Get activity logs", "Get activity logs for all users", - http.StatusOK, []string{tagSettings}, bearerToken, nil, new([]postgresqldb.GetAuditLogsRow)) + d.AddOperation("getUserAuditLogs", http.MethodPost, "/deepfence/settings/user-audit-log", + "Get user audit logs", "Get audit logs for all users", + http.StatusOK, []string{tagSettings}, bearerToken, new(GetAuditLogsRequest), new([]postgresqldb.GetAuditLogsRow)) + d.AddOperation("getUserAuditLogsCount", http.MethodGet, "/deepfence/settings/user-audit-log/count", + "Get user audit logs count", "Get user audit logs count", + http.StatusOK, []string{tagSettings}, bearerToken, nil, new(SearchCountResp)) // Scheduled tasks d.AddOperation("getScheduledTasks", http.MethodGet, "/deepfence/scheduled-task", @@ -772,3 +776,9 @@ func (d *OpenApiDocs) AddDiffAddOperations() { "Get Cloud Compliance Diff", "Get Cloud Compliance Diff between two scans", http.StatusOK, []string{tagDiffAdd}, bearerToken, new(ScanCompareReq), new(ScanCompareResCloudCompliance)) } + +func (d *OpenApiDocs) AddCompletionOperations() { + d.AddOperation("completeProcessInfo", http.MethodPost, "/deepfence/complete/process", + "Get Completion for process fields", "Complete process info", + http.StatusOK, []string{tagCompletion}, bearerToken, new(CompletionNodeFieldReq), new(CompletionNodeFieldRes)) +} diff --git a/deepfence_server/handler/audit_log.go b/deepfence_server/handler/audit_log.go index db340f6f50..0ebb3d80f9 100644 --- a/deepfence_server/handler/audit_log.go +++ b/deepfence_server/handler/audit_log.go @@ -7,6 +7,7 @@ import ( "time" "github.com/deepfence/ThreatMapper/deepfence_server/model" + reporters_search "github.com/deepfence/ThreatMapper/deepfence_server/reporters/search" "github.com/deepfence/ThreatMapper/deepfence_utils/directory" "github.com/deepfence/ThreatMapper/deepfence_utils/log" postgresql_db "github.com/deepfence/ThreatMapper/deepfence_utils/postgresql/postgresql-db" @@ -155,7 +156,36 @@ func (h *Handler) AddAuditLog(namespace string, params postgresql_db.CreateAudit return nil } +func (h *Handler) GetAuditLogsCount(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + pgClient, err := directory.PostgresClient(ctx) + if err != nil { + log.Error().Err(err).Msg("failed to get db connection") + h.respondError(err, w) + return + } + + count, err := pgClient.CountAuditLogs(ctx) + if err != nil { + log.Error().Err(err).Msg("failed to run CountAuditLogs query") + h.respondError(err, w) + return + } + + httpext.JSON(w, http.StatusOK, reporters_search.SearchCountResp{ + Count: int(count), + }) +} + func (h *Handler) GetAuditLogs(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + var req model.GetAuditLogsRequest + err := httpext.DecodeJSON(r, httpext.NoQueryParams, MaxPostRequestSize, &req) + if err != nil { + h.respondError(&BadDecoding{err}, w) + return + } + ctx := r.Context() pgClient, err := directory.PostgresClient(ctx) if err != nil { @@ -164,7 +194,10 @@ func (h *Handler) GetAuditLogs(w http.ResponseWriter, r *http.Request) { return } - auditLogs, err := pgClient.GetAuditLogs(ctx) + auditLogs, err := pgClient.GetAuditLogs(ctx, postgresql_db.GetAuditLogsParams{ + Offset: int32(req.Window.Offset), + Limit: int32(req.Window.Size), + }) if err != nil { log.Error().Msgf("%v", err) h.respondError(err, w) diff --git a/deepfence_server/handler/completion.go b/deepfence_server/handler/completion.go new file mode 100644 index 0000000000..e333fe7967 --- /dev/null +++ b/deepfence_server/handler/completion.go @@ -0,0 +1,39 @@ +package handler + +import ( + "net/http" + + "github.com/deepfence/ThreatMapper/deepfence_server/model" + "github.com/deepfence/ThreatMapper/deepfence_server/reporters" + "github.com/deepfence/ThreatMapper/deepfence_server/reporters/completion" + "github.com/deepfence/ThreatMapper/deepfence_utils/log" + httpext "github.com/go-playground/pkg/v5/net/http" +) + +func (h *Handler) CompleteProcessInfo(w http.ResponseWriter, r *http.Request) { + genericCompleteInfoHandler[model.Process](w, r, h) +} + +func genericCompleteInfoHandler[T reporters.Cypherable](w http.ResponseWriter, r *http.Request, h *Handler) { + defer r.Body.Close() + var req completion.CompletionNodeFieldReq + err := httpext.DecodeJSON(r, httpext.NoQueryParams, MaxPostRequestSize, &req) + if err != nil { + h.respondError(&BadDecoding{err}, w) + return + } + + entries, err := completion.FieldValueCompletion[T](r.Context(), req) + if err != nil { + log.Error().Msg(err.Error()) + h.respondError(err, w) + return + } + + err = httpext.JSON(w, http.StatusOK, completion.CompletionNodeFieldRes{ + PossibleValues: entries, + }) + if err != nil { + log.Error().Msg(err.Error()) + } +} diff --git a/deepfence_server/handler/scan_reports.go b/deepfence_server/handler/scan_reports.go index 82ea87b5ed..3838877d2a 100644 --- a/deepfence_server/handler/scan_reports.go +++ b/deepfence_server/handler/scan_reports.go @@ -407,8 +407,10 @@ func (h *Handler) StartComplianceScanHandler(w http.ResponseWriter, r *http.Requ h.respondError(err, w) return } - } else { + } else if len(cloudNodeIds) > 0 { nodes = cloudNodeIds + } else { + nodes = reqs.NodeIds } var scanTrigger model.NodeIdentifier diff --git a/deepfence_server/main.go b/deepfence_server/main.go index f290a3667b..cfbc6f892f 100644 --- a/deepfence_server/main.go +++ b/deepfence_server/main.go @@ -323,6 +323,7 @@ func initializeOpenApiDocs(openApiDocs *apiDocs.OpenApiDocs) { openApiDocs.AddReportsOperations() openApiDocs.AddSettingsOperations() openApiDocs.AddDiffAddOperations() + openApiDocs.AddCompletionOperations() } func initializeInternalOpenApiDocs(openApiDocs *apiDocs.OpenApiDocs) { diff --git a/deepfence_server/model/setting.go b/deepfence_server/model/setting.go index a62bfdf125..942c36db56 100644 --- a/deepfence_server/model/setting.go +++ b/deepfence_server/model/setting.go @@ -58,6 +58,10 @@ type SettingsResponse struct { Description string `json:"description" required:"true"` } +type GetAuditLogsRequest struct { + Window FetchWindow `json:"window" required:"true"` +} + type SettingUpdateRequest struct { ID int64 `path:"id" validate:"required" required:"true"` Key string `json:"key" validate:"required,oneof=console_url inactive_delete_scan_results" required:"true" enum:"console_url,inactive_delete_scan_results"` diff --git a/deepfence_server/reporters/completion/completion.go b/deepfence_server/reporters/completion/completion.go new file mode 100644 index 0000000000..b2a5a59937 --- /dev/null +++ b/deepfence_server/reporters/completion/completion.go @@ -0,0 +1,70 @@ +package completion + +import ( + "context" + "time" + + "github.com/deepfence/ThreatMapper/deepfence_server/model" + "github.com/deepfence/ThreatMapper/deepfence_server/reporters" + "github.com/deepfence/ThreatMapper/deepfence_utils/directory" + "github.com/neo4j/neo4j-go-driver/v4/neo4j" + "github.com/rs/zerolog/log" +) + +type CompletionNodeFieldReq struct { + Completion string `json:"completion" required:"true"` + FieldName string `json:"field_name" required:"true"` + Window model.FetchWindow `json:"window" required:"true"` +} + +type CompletionNodeFieldRes struct { + PossibleValues []string `json:"possible_values" required:"true"` +} + +func FieldValueCompletion[T reporters.Cypherable](ctx context.Context, req CompletionNodeFieldReq) ([]string, error) { + res := []string{} + + var dummy T + + driver, err := directory.Neo4jClient(ctx) + if err != nil { + return res, err + } + + session, err := driver.Session(neo4j.AccessModeRead) + if err != nil { + return res, err + } + defer session.Close() + + tx, err := session.BeginTransaction(neo4j.WithTxTimeout(30 * time.Second)) + if err != nil { + return res, err + } + defer tx.Close() + + query := ` + MATCH (n:` + dummy.NodeType() + `) + WHERE n.` + req.FieldName + ` =~ '^` + req.Completion + `.*' + RETURN DISTINCT n.` + req.FieldName + + log.Debug().Msgf("completion query: \n%v", query) + r, err := tx.Run(query, + map[string]interface{}{}) + + if err != nil { + return res, err + } + + recs, err := r.Collect() + + if err != nil { + return res, err + } + + for i := range recs { + res = append(res, recs[i].Values[0].(string)) + } + + return res, nil +} diff --git a/deepfence_server/router/router.go b/deepfence_server/router/router.go index 5afcb9d8f5..58ef716e39 100644 --- a/deepfence_server/router/router.go +++ b/deepfence_server/router/router.go @@ -206,7 +206,10 @@ func SetupRoutes(r *chi.Mux, serverPort string, serveOpenapiDocs bool, ingestC c }) r.Route("/settings", func(r chi.Router) { - r.Get("/user-activity-log", dfHandler.AuthHandler(ResourceSettings, PermissionRead, dfHandler.GetAuditLogs)) + r.Route("/user-audit-log", func(r chi.Router) { + r.Post("/", dfHandler.AuthHandler(ResourceSettings, PermissionRead, dfHandler.GetAuditLogs)) + r.Get("/count", dfHandler.AuthHandler(ResourceSettings, PermissionRead, dfHandler.GetAuditLogsCount)) + }) r.Route("/global-settings", func(r chi.Router) { r.Get("/", dfHandler.AuthHandler(ResourceSettings, PermissionRead, dfHandler.GetGlobalSettings)) r.Patch("/{id}", dfHandler.AuthHandler(ResourceSettings, PermissionWrite, dfHandler.UpdateGlobalSettings)) @@ -247,6 +250,10 @@ func SetupRoutes(r *chi.Mux, serverPort string, serveOpenapiDocs bool, ingestC c r.Post("/cloud-compliances", dfHandler.GetCloudCompliances) }) + r.Route("/complete", func(r chi.Router) { + r.Post("/process", dfHandler.CompleteProcessInfo) + }) + r.Route("/search", func(r chi.Router) { r.Post("/hosts", dfHandler.SearchHosts) r.Post("/containers", dfHandler.SearchContainers) diff --git a/deepfence_utils/controls/workload_allocator_test.go b/deepfence_utils/controls/workload_allocator_test.go new file mode 100644 index 0000000000..8504c528b6 --- /dev/null +++ b/deepfence_utils/controls/workload_allocator_test.go @@ -0,0 +1,27 @@ +package controls + +import ( + "testing" + + "gotest.tools/assert" +) + +func TestWorkloadAlloc(t *testing.T) { + alloc := NewWorkloadAllocator(5) + alloc.Reserve(3) + assert.Equal(t, alloc.MaxAllocable(), int32(2), "Reserved space not equal") + alloc.Reserve(2) + assert.Equal(t, alloc.MaxAllocable(), int32(0), "Reserved space not equal") + alloc.Free() + assert.Equal(t, alloc.MaxAllocable(), int32(1), "Reserved space not equal") +} + +func TestWorkloadOvercommit(t *testing.T) { + alloc := NewWorkloadAllocator(5) + alloc.Reserve(6) + assert.Equal(t, alloc.MaxAllocable(), int32(0), "Reserved space not equal") + alloc.Free() + assert.Equal(t, alloc.MaxAllocable(), int32(0), "Reserved space not equal") + alloc.Free() + assert.Equal(t, alloc.MaxAllocable(), int32(1), "Reserved space not equal") +} diff --git a/deepfence_utils/go.mod b/deepfence_utils/go.mod index 81fe88ccfe..3818f18445 100644 --- a/deepfence_utils/go.mod +++ b/deepfence_utils/go.mod @@ -18,6 +18,7 @@ require ( github.com/twmb/franz-go/pkg/kadm v1.9.0 go.opentelemetry.io/otel v1.18.0 go.opentelemetry.io/otel/trace v1.18.0 + gotest.tools v2.2.0+incompatible ) require ( @@ -29,6 +30,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect @@ -44,6 +46,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rs/xid v1.5.0 // indirect github.com/segmentio/asm v1.2.0 // indirect diff --git a/deepfence_utils/go.sum b/deepfence_utils/go.sum index c6bf65f9d1..d31e9e046b 100644 --- a/deepfence_utils/go.sum +++ b/deepfence_utils/go.sum @@ -50,6 +50,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= @@ -110,6 +111,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -267,3 +269,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/deepfence_utils/postgresql/postgresql-db/db.go b/deepfence_utils/postgresql/postgresql-db/db.go index 3e56a3615c..16bc59cfee 100644 --- a/deepfence_utils/postgresql/postgresql-db/db.go +++ b/deepfence_utils/postgresql/postgresql-db/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.20.0 +// sqlc v1.21.0 package postgresql_db diff --git a/deepfence_utils/postgresql/postgresql-db/models.go b/deepfence_utils/postgresql/postgresql-db/models.go index 6340876e58..8da0a45762 100644 --- a/deepfence_utils/postgresql/postgresql-db/models.go +++ b/deepfence_utils/postgresql/postgresql-db/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.20.0 +// sqlc v1.21.0 package postgresql_db diff --git a/deepfence_utils/postgresql/postgresql-db/queries.sql.go b/deepfence_utils/postgresql/postgresql-db/queries.sql.go index 4c18be5846..d9d6fea8c7 100644 --- a/deepfence_utils/postgresql/postgresql-db/queries.sql.go +++ b/deepfence_utils/postgresql/postgresql-db/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.20.0 +// sqlc v1.21.0 // source: queries.sql package postgresql_db @@ -42,6 +42,18 @@ func (q *Queries) CountActiveUsers(ctx context.Context) (int64, error) { return count, err } +const countAuditLogs = `-- name: CountAuditLogs :one +SELECT count(*) +FROM audit_log +` + +func (q *Queries) CountAuditLogs(ctx context.Context) (int64, error) { + row := q.db.QueryRowContext(ctx, countAuditLogs) + var count int64 + err := row.Scan(&count) + return count, err +} + const countCompanies = `-- name: CountCompanies :one SELECT count(*) FROM company @@ -1030,9 +1042,15 @@ SELECT l.event, l.user_role as role, l.created_at FROM audit_log l -ORDER BY l.created_at DESC +ORDER BY id DESC +OFFSET $1 LIMIT $2 ` +type GetAuditLogsParams struct { + Offset int32 `json:"offset"` + Limit int32 `json:"limit"` +} + type GetAuditLogsRow struct { Event string `json:"event"` Action string `json:"action"` @@ -1043,8 +1061,8 @@ type GetAuditLogsRow struct { CreatedAt time.Time `json:"created_at"` } -func (q *Queries) GetAuditLogs(ctx context.Context) ([]GetAuditLogsRow, error) { - rows, err := q.db.QueryContext(ctx, getAuditLogs) +func (q *Queries) GetAuditLogs(ctx context.Context, arg GetAuditLogsParams) ([]GetAuditLogsRow, error) { + rows, err := q.db.QueryContext(ctx, getAuditLogs, arg.Offset, arg.Limit) if err != nil { return nil, err } diff --git a/deepfence_utils/postgresql/queries.sql b/deepfence_utils/postgresql/queries.sql index 9e08192755..15e2637486 100644 --- a/deepfence_utils/postgresql/queries.sql +++ b/deepfence_utils/postgresql/queries.sql @@ -533,7 +533,12 @@ SELECT l.event, l.user_role as role, l.created_at FROM audit_log l -ORDER BY l.created_at DESC; +ORDER BY id DESC +OFFSET $1 LIMIT $2; + +-- name: CountAuditLogs :one +SELECT count(*) +FROM audit_log; -- name: GetAuditLogsLast5Minutes :many SELECT l.event, diff --git a/deepfence_utils/utils/ingesters/vulnerabilities.go b/deepfence_utils/utils/ingesters/vulnerabilities.go index 66c9435656..dec02d6911 100644 --- a/deepfence_utils/utils/ingesters/vulnerabilities.go +++ b/deepfence_utils/utils/ingesters/vulnerabilities.go @@ -7,72 +7,75 @@ type VulnerabilityScanStatus struct { } type Vulnerability struct { - ScanId string `json:"scan_id"` - Cve_id string `json:"cve_id"` - Cve_severity string `json:"cve_severity"` - Cve_caused_by_package string `json:"cve_caused_by_package"` - Cve_caused_by_package_path string `json:"cve_caused_by_package_path"` - Cve_container_layer string `json:"cve_container_layer"` - Cve_fixed_in string `json:"cve_fixed_in"` - Cve_link string `json:"cve_link"` - Cve_description string `json:"cve_description"` - Cve_cvss_score float64 `json:"cve_cvss_score"` - Cve_overall_score float64 `json:"cve_overall_score"` - Cve_attack_vector string `json:"cve_attack_vector"` - URLs []string `json:"urls"` - ExploitPOC string `json:"exploit_poc"` - ParsedAttackVector string `json:"parsed_attack_vector"` - ExploitabilityScore int `json:"exploitability_score"` - InitExploitabilityScore int `json:"init_exploitability_score"` - HasLiveConnection bool `json:"has_live_connection"` + ScanId string `json:"scan_id"` + CveId string `json:"cve_id"` + CveType string `json:"cve_type"` + CveSeverity string `json:"cve_severity"` + CveCausedByPackage string `json:"cve_caused_by_package"` + CveCausedByPackagePath string `json:"cve_caused_by_package_path"` + CveContainerLayer string `json:"cve_container_layer"` + CveFixedIn string `json:"cve_fixed_in"` + CveLink string `json:"cve_link"` + CveDescription string `json:"cve_description"` + CveCvssScore float64 `json:"cve_cvss_score"` + CveOverallScore float64 `json:"cve_overall_score"` + CveAttackVector string `json:"cve_attack_vector"` + URLs []string `json:"urls"` + ExploitPOC string `json:"exploit_poc"` + ParsedAttackVector string `json:"parsed_attack_vector"` + ExploitabilityScore int `json:"exploitability_score"` + InitExploitabilityScore int `json:"init_exploitability_score"` + HasLiveConnection bool `json:"has_live_connection"` } type VulnerabilityRule struct { - Cve_id string `json:"cve_id"` - Cve_severity string `json:"cve_severity"` - Cve_fixed_in string `json:"cve_fixed_in"` - Cve_link string `json:"cve_link"` - Cve_description string `json:"cve_description"` - Cve_cvss_score float64 `json:"cve_cvss_score"` - Cve_overall_score float64 `json:"cve_overall_score"` - Cve_attack_vector string `json:"cve_attack_vector"` + CveId string `json:"cve_id"` + CveType string `json:"cve_type"` + CveSeverity string `json:"cve_severity"` + CveFixedIn string `json:"cve_fixed_in"` + CveLink string `json:"cve_link"` + CveDescription string `json:"cve_description"` + CveCvssScore float64 `json:"cve_cvss_score"` + CveOverallScore float64 `json:"cve_overall_score"` + CveAttackVector string `json:"cve_attack_vector"` URLs []string `json:"urls"` ExploitPOC string `json:"exploit_poc"` ParsedAttackVector string `json:"parsed_attack_vector"` } type VulnerabilityData struct { - Cve_id string `json:"cve_id"` - Cve_severity string `json:"cve_severity"` - Cve_caused_by_package string `json:"cve_caused_by_package"` - Cve_caused_by_package_path string `json:"cve_caused_by_package_path"` - Cve_container_layer string `json:"cve_container_layer"` - Cve_link string `json:"cve_link"` - ExploitabilityScore int `json:"exploitability_score"` - InitExploitabilityScore int `json:"init_exploitability_score"` - HasLiveConnection bool `json:"has_live_connection"` + CveId string `json:"cve_id"` + CveSeverity string `json:"cve_severity"` + CveCausedByPackage string `json:"cve_caused_by_package"` + CveCausedByPackagePath string `json:"cve_caused_by_package_path"` + CveContainerLayer string `json:"cve_container_layer"` + CveLink string `json:"cve_link"` + ExploitabilityScore int `json:"exploitability_score"` + InitExploitabilityScore int `json:"init_exploitability_score"` + HasLiveConnection bool `json:"has_live_connection"` } func (c Vulnerability) Split() (VulnerabilityData, VulnerabilityRule) { return VulnerabilityData{ - Cve_id: c.Cve_id, - Cve_severity: c.Cve_severity, - Cve_caused_by_package: c.Cve_caused_by_package, - Cve_caused_by_package_path: c.Cve_caused_by_package_path, - Cve_container_layer: c.Cve_container_layer, - Cve_link: c.Cve_link, - ExploitabilityScore: c.ExploitabilityScore, - InitExploitabilityScore: c.InitExploitabilityScore, - HasLiveConnection: c.HasLiveConnection, + CveId: c.CveId, + CveSeverity: c.CveSeverity, + CveCausedByPackage: c.CveCausedByPackage, + CveCausedByPackagePath: c.CveCausedByPackagePath, + CveContainerLayer: c.CveContainerLayer, + CveLink: c.CveLink, + ExploitabilityScore: c.ExploitabilityScore, + InitExploitabilityScore: c.InitExploitabilityScore, + HasLiveConnection: c.HasLiveConnection, }, VulnerabilityRule{ - Cve_id: c.Cve_id, - Cve_severity: c.Cve_severity, - Cve_fixed_in: c.Cve_fixed_in, - Cve_link: c.Cve_link, - Cve_description: c.Cve_description, - Cve_cvss_score: c.Cve_cvss_score, - Cve_overall_score: c.Cve_overall_score, - Cve_attack_vector: c.Cve_attack_vector, + CveId: c.CveId, + CveType: c.CveType, + CveSeverity: c.CveSeverity, + CveFixedIn: c.CveFixedIn, + CveLink: c.CveLink, + CveDescription: c.CveDescription, + CveCvssScore: c.CveCvssScore, + CveOverallScore: c.CveOverallScore, + CveAttackVector: c.CveAttackVector, URLs: c.URLs, ExploitPOC: c.ExploitPOC, ParsedAttackVector: c.ParsedAttackVector, diff --git a/deepfence_worker/go.mod b/deepfence_worker/go.mod index 0ccc85a1a4..90fa9aff82 100644 --- a/deepfence_worker/go.mod +++ b/deepfence_worker/go.mod @@ -4,6 +4,8 @@ go 1.20 replace github.com/deepfence/golang_deepfence_sdk/client => ../golang_deepfence_sdk/client/ +replace github.com/deepfence/golang_deepfence_sdk/utils => ../golang_deepfence_sdk/utils + replace github.com/deepfence/ThreatMapper/deepfence_utils => ../deepfence_utils/ replace github.com/deepfence/df-utils => ../deepfence_agent/tools/apache/deepfence/df-utils @@ -29,6 +31,7 @@ require ( github.com/deepfence/ThreatMapper/deepfence_utils v0.0.0-00010101000000-000000000000 github.com/deepfence/YaraHunter v0.0.0-00010101000000-000000000000 github.com/deepfence/agent-plugins-grpc v1.1.0 + github.com/deepfence/golang_deepfence_sdk/utils v0.0.0-20230929125743-1713a043efe5 github.com/deepfence/package-scanner v0.0.0-00010101000000-000000000000 github.com/hibiken/asynq v0.24.1 github.com/kelseyhightower/envconfig v1.4.0 @@ -79,11 +82,10 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/containerd/ttrpc v1.2.2 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/cyphar/filepath-securejoin v0.2.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/deepfence/golang_deepfence_sdk/client v0.0.0-20230817093436-faaacc23dfd8 // indirect - github.com/deepfence/golang_deepfence_sdk/utils v0.0.0-20230817093436-faaacc23dfd8 // indirect github.com/deepfence/vessel v0.11.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/cli v24.0.2+incompatible // indirect diff --git a/deepfence_worker/go.sum b/deepfence_worker/go.sum index 4de1508c3a..fec5d13422 100644 --- a/deepfence_worker/go.sum +++ b/deepfence_worker/go.sum @@ -178,16 +178,14 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/deepfence/golang_deepfence_sdk/utils v0.0.0-20230817093436-faaacc23dfd8 h1:dR65mjFib0ygj9kyn45q+64PuJfYsYBiRt08RWNklPw= -github.com/deepfence/golang_deepfence_sdk/utils v0.0.0-20230817093436-faaacc23dfd8/go.mod h1:C3CqMr7oE9RmHZWXIVDWFLuGaNDDaoSBSlILLQJxlew= github.com/deepfence/vessel v0.11.1 h1:RSnPHv/HX9Vrcujxzp6l4cjzF7a/34lVvh+jr8Hq8YA= github.com/deepfence/vessel v0.11.1/go.mod h1:uSMZ7HZePuQzHH2kKdRJ/r8kYPz9ZgkffYhFiccmeHk= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= diff --git a/deepfence_worker/main.go b/deepfence_worker/main.go index 891147a9f9..5c93a148a6 100644 --- a/deepfence_worker/main.go +++ b/deepfence_worker/main.go @@ -2,7 +2,9 @@ package main import ( "context" + "io/ioutil" "os" + "path" "time" "github.com/deepfence/ThreatMapper/deepfence_utils/directory" @@ -58,6 +60,14 @@ func main() { log.Fatal().Msg(err.Error()) } + dir, err := ioutil.ReadDir("/tmp") + if err != nil { + log.Fatal().Msg(err.Error()) + } + for _, d := range dir { + os.RemoveAll(path.Join([]string{"tmp", d.Name()}...)) + } + log.Info().Msgf("config: %+v", cfg) if cfg.Debug { diff --git a/deepfence_worker/tasks/malwarescan/malwarescan.go b/deepfence_worker/tasks/malwarescan/malwarescan.go index d06ed47712..50830d7970 100644 --- a/deepfence_worker/tasks/malwarescan/malwarescan.go +++ b/deepfence_worker/tasks/malwarescan/malwarescan.go @@ -3,12 +3,15 @@ package malwarescan import ( "context" "encoding/json" + "fmt" "io/ioutil" "os" "os/exec" "sync" + "time" "github.com/deepfence/YaraHunter/pkg/output" + "github.com/deepfence/golang_deepfence_sdk/utils/tasks" "github.com/hibiken/asynq" "github.com/deepfence/ThreatMapper/deepfence_worker/cronjobs" @@ -69,8 +72,9 @@ func (s MalwareScan) StopMalwareScan(ctx context.Context, task *asynq.Task) erro return nil } - scanner := obj.(*malwareScan.Scanner) - scanner.Stopped.Store(true) + scanner := obj.(*tasks.ScanContext) + scanner.StopTriggered.Store(true) + scanner.Cancel() log.Error().Msgf("Stop request submitted, ScanID: %s", scanID) return nil @@ -80,67 +84,73 @@ func (s MalwareScan) StopMalwareScan(ctx context.Context, task *asynq.Task) erro func (s MalwareScan) StartMalwareScan(ctx context.Context, task *asynq.Task) error { defer cronjobs.ScanWorkloadAllocator.Free() + var err error tenantID, err := directory.ExtractNamespace(ctx) if err != nil { return err } - if len(tenantID) == 0 { - log.Error().Msg("tenant-id/namespace is empty") - return nil - } log.Info().Msgf("message tenant id %s", string(tenantID)) - rh := []kgo.RecordHeader{ - {Key: "namespace", Value: []byte(tenantID)}, - } - log.Info().Msgf(" payload: %s ", string(task.Payload())) var params utils.MalwareScanParameters if err := json.Unmarshal(task.Payload(), ¶ms); err != nil { - log.Error().Msg(err.Error()) - return nil + return err } + res, scanCtx := tasks.StartStatusReporter(params.ScanId, + func(status tasks.ScanStatus) error { + sb, err := json.Marshal(status) + if err != nil { + log.Error().Msgf("%v", err) + return err + } + + s.ingestC <- &kgo.Record{ + Topic: utils.MALWARE_SCAN_STATUS, + Value: sb, + Headers: []kgo.RecordHeader{{Key: "namespace", Value: []byte(tenantID)}}, + } + return nil + }, tasks.StatusValues{ + IN_PROGRESS: utils.SCAN_STATUS_INPROGRESS, + CANCELLED: utils.SCAN_STATUS_CANCELLED, + FAILED: utils.SCAN_STATUS_FAILED, + SUCCESS: utils.SCAN_STATUS_SUCCESS, + }, + time.Minute*10, + ) + + ScanMap.Store(params.ScanId, scanCtx) + + defer func() { + log.Info().Msgf("Removing from scan map, scan_id: %s", params.ScanId) + ScanMap.Delete(params.ScanId) + res <- err + close(res) + }() + if params.RegistryId == "" { - log.Error().Msgf("registry id is empty in params %+v", params) - return nil + return fmt.Errorf("registry id is empty in params %+v: %w", params, err) } opts, yaraconfig, yr = initMalwareScanner() yrScanner, err := yr.NewScanner() if err != nil { - SendScanStatus(s.ingestC, NewMalwareScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error()), rh) - log.Error().Msg(err.Error()) - return nil + return err } // scanResult, err := malwareScan.ExtractAndScanFromTar(dir, imagename) malwareScanner := malwareScan.New(opts, yaraconfig, yrScanner, params.ScanId) - //Set this "hardErr" variable to appropriate error if - //an error has resulted in early abort/return from this function - var hardErr error - res := StartStatusReporter(context.Background(), malwareScanner, params, s.ingestC, rh) - ScanMap.Store(params.ScanId, malwareScanner) - - defer func() { - log.Info().Msgf("Removing from scan map, scan_id: %s", params.ScanId) - ScanMap.Delete(params.ScanId) - res <- hardErr - close(res) - }() - // send inprogress status - malwareScanner.ScanStatusChan <- true + scanCtx.Checkpoint("After initialization") // get registry credentials authDir, creds, err := workerUtils.GetConfigFileFromRegistry(ctx, params.RegistryId) if err != nil { - log.Error().Msg(err.Error()) - hardErr = err - return nil + return err } defer func() { @@ -167,8 +177,7 @@ func (s MalwareScan) StartMalwareScan(ctx context.Context, task *asynq.Task) err dir, err := ioutil.TempDir("/tmp", "malwarescan-*") if err != nil { - // return nil - log.Error().Msgf(err.Error()) + return err } defer os.RemoveAll(dir) @@ -185,26 +194,21 @@ func (s MalwareScan) StartMalwareScan(ctx context.Context, task *asynq.Task) err log.Info().Msgf("command: %s", cmd.String()) if out, err := workerUtils.RunCommand(cmd); err != nil { - hardErr = err log.Error().Err(err).Msg(cmd.String()) log.Error().Msgf("output: %s", out.String()) - return nil + return err } - malwareScanner.ScanStatusChan <- true + scanCtx.Checkpoint("After skopeo download") if err != nil { - log.Error().Msg(err.Error()) - hardErr = err - return nil + return err } // set imageName in malwareScanner malwareScanner.SetImageName(imageName) - scanResult, err := malwareScanner.ExtractAndScanFromTar(dir) + scanResult, err := malwareScanner.ExtractAndScanFromTar(scanCtx, dir) if err != nil { - hardErr = err - log.Error().Msg(err.Error()) - return nil + return err } type malwareScanResult struct { @@ -223,7 +227,7 @@ func (s MalwareScan) StartMalwareScan(ctx context.Context, task *asynq.Task) err s.ingestC <- &kgo.Record{ Topic: utils.MALWARE_SCAN, Value: cb, - Headers: rh, + Headers: []kgo.RecordHeader{{Key: "namespace", Value: []byte(tenantID)}}, } } } diff --git a/deepfence_worker/tasks/malwarescan/utils.go b/deepfence_worker/tasks/malwarescan/utils.go deleted file mode 100644 index 5250ffd6fa..0000000000 --- a/deepfence_worker/tasks/malwarescan/utils.go +++ /dev/null @@ -1,108 +0,0 @@ -package malwarescan - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/deepfence/ThreatMapper/deepfence_utils/log" - "github.com/deepfence/ThreatMapper/deepfence_utils/utils" - "github.com/deepfence/YaraHunter/pkg/scan" - "github.com/twmb/franz-go/pkg/kgo" -) - -type MalwareScanStatus struct { - utils.MalwareScanParameters - ScanStatus string `json:"scan_status,omitempty"` - ScanMessage string `json:"scan_message,omitempty"` -} - -func NewMalwareScanStatus(params utils.MalwareScanParameters, Status string, msg string) MalwareScanStatus { - return MalwareScanStatus{MalwareScanParameters: params, ScanStatus: Status, ScanMessage: msg} -} - -func SendScanStatus(ingestC chan *kgo.Record, status MalwareScanStatus, rh []kgo.RecordHeader) error { - sb, err := json.Marshal(status) - if err != nil { - log.Error().Msg(err.Error()) - return err - } else { - ingestC <- &kgo.Record{ - Topic: utils.MALWARE_SCAN_STATUS, - Value: sb, - Headers: rh, - } - } - return nil -} - -func StartStatusReporter(ctx context.Context, scanner *scan.Scanner, - params utils.MalwareScanParameters, ingestC chan *kgo.Record, - rh []kgo.RecordHeader) chan error { - - res := make(chan error) - scan_id := scanner.ScanID - - //If we don't get any active status back within threshold, - //we consider the scan job as dead - threshold := 600 - go func() { - ticker := time.NewTicker(1 * time.Second) - var err error - ts := time.Now() - var counter uint64 - log.Info().Msgf("StatusReporter started, scan_id: %s", scan_id) - loop: - for { - select { - case err = <-res: - break loop - case <-ctx.Done(): - err = ctx.Err() - break loop - case <-scanner.ScanStatusChan: - ts = time.Now() - case <-ticker.C: - if scanner.Stopped.Load() == true { - log.Info().Msgf("Scanner job stopped, Scan id: %s", scan_id) - break loop - } - - counter++ - - //We perform the check once per 30 seconds - if counter%30 != 0 { - continue - } - - elapsed := int(time.Since(ts).Seconds()) - if elapsed > threshold { - err = fmt.Errorf("Scan job aborted due to inactivity") - log.Error().Msgf("Scanner job aborted due to inactivity, scan_id: %s", scan_id) - - scanner.Aborted.Store(true) - break loop - } else { - SendScanStatus(ingestC, NewMalwareScanStatus(params, - utils.SCAN_STATUS_INPROGRESS, ""), rh) - } - } - } - - if scanner.Stopped.Load() == true { - SendScanStatus(ingestC, NewMalwareScanStatus(params, - utils.SCAN_STATUS_CANCELLED, "Scan stopped by user"), rh) - } else if err != nil { - SendScanStatus(ingestC, NewMalwareScanStatus(params, - utils.SCAN_STATUS_FAILED, err.Error()), rh) - } else { - SendScanStatus(ingestC, NewMalwareScanStatus(params, - utils.SCAN_STATUS_SUCCESS, ""), rh) - } - - log.Info().Msgf("StatusReporter finished, scan_id: %s", scan_id) - }() - - return res -} diff --git a/deepfence_worker/tasks/sbom/generate_sbom.go b/deepfence_worker/tasks/sbom/generate_sbom.go index 4d285e2a1e..c270705fd5 100644 --- a/deepfence_worker/tasks/sbom/generate_sbom.go +++ b/deepfence_worker/tasks/sbom/generate_sbom.go @@ -9,12 +9,14 @@ import ( "path" "strings" "sync" + "time" "github.com/deepfence/ThreatMapper/deepfence_utils/directory" "github.com/deepfence/ThreatMapper/deepfence_utils/log" "github.com/deepfence/ThreatMapper/deepfence_utils/utils" "github.com/deepfence/ThreatMapper/deepfence_worker/cronjobs" workerUtils "github.com/deepfence/ThreatMapper/deepfence_worker/utils" + "github.com/deepfence/golang_deepfence_sdk/utils/tasks" "github.com/deepfence/package-scanner/sbom/syft" psUtils "github.com/deepfence/package-scanner/utils" "github.com/hibiken/asynq" @@ -63,7 +65,10 @@ func StopVulnerabilityScan(ctx context.Context, task *asynq.Task) error { func (s SbomGenerator) GenerateSbom(ctx context.Context, task *asynq.Task) error { defer cronjobs.ScanWorkloadAllocator.Free() - var params utils.SbomParameters + var ( + params utils.SbomParameters + err error + ) tenantID, err := directory.ExtractNamespace(ctx) if err != nil { @@ -79,6 +84,35 @@ func (s SbomGenerator) GenerateSbom(ctx context.Context, task *asynq.Task) error {Key: "namespace", Value: []byte(tenantID)}, } + res, scanCtx := tasks.StartStatusReporter(params.ScanId, + func(status tasks.ScanStatus) error { + sb, err := json.Marshal(status) + if err != nil { + return err + } + s.ingestC <- &kgo.Record{ + Topic: utils.VULNERABILITY_SCAN_STATUS, + Value: sb, + Headers: rh, + } + return nil + }, tasks.StatusValues{ + IN_PROGRESS: utils.SCAN_STATUS_INPROGRESS, + CANCELLED: utils.SCAN_STATUS_CANCELLED, + FAILED: utils.SCAN_STATUS_FAILED, + SUCCESS: utils.SCAN_STATUS_SUCCESS, + }, + time.Minute*20, + ) + log.Info().Msgf("Adding scanid to map:%s", params.ScanId) + scanMap.Store(params.ScanId, scanCtx) + defer func() { + log.Info().Msgf("Removing scaind from map:%s", params.ScanId) + scanMap.Delete(params.ScanId) + res <- err + close(res) + }() + worker, err := directory.Worker(ctx) if err != nil { return err @@ -87,43 +121,20 @@ func (s SbomGenerator) GenerateSbom(ctx context.Context, task *asynq.Task) error log.Info().Msgf("payload: %s ", string(task.Payload())) if err := json.Unmarshal(task.Payload(), ¶ms); err != nil { - log.Error().Msg(err.Error()) - SendScanStatus(s.ingestC, NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil), rh) - return nil + return err } if params.RegistryId == "" { log.Error().Msgf("registry id is empty in params %+v", params) - SendScanStatus(s.ingestC, NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, - "registry id is empty in params", nil), rh) - return nil + return err } - statusChan := make(chan SbomScanStatus) - var wg sync.WaitGroup - wg.Add(1) - StartStatusReporter("SBOM_GENERATION", statusChan, s.ingestC, rh, params, &wg) - defer wg.Wait() - - // send inprogress status - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_INPROGRESS, "", nil) - // get registry credentials authFile, creds, err := workerUtils.GetConfigFileFromRegistry(ctx, params.RegistryId) if err != nil { - log.Error().Msg(err.Error()) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } - log.Info().Msgf("Adding scanid to map:%s", params.ScanId) - ctxSbom, cancel := context.WithCancel(context.Background()) - scanMap.Store(params.ScanId, cancel) - defer func(scanId string) { - log.Info().Msgf("Removing scaind from map:%s", scanId) - scanMap.Delete(scanId) - }(params.ScanId) - defer func() { log.Info().Msgf("remove auth directory %s", authFile) if authFile == "" { @@ -164,18 +175,11 @@ func (s SbomGenerator) GenerateSbom(ctx context.Context, task *asynq.Task) error log.Debug().Msgf("config: %+v", cfg) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_INPROGRESS, "", nil) + scanCtx.Checkpoint("Before generating SBOM") - rawSbom, err := syft.GenerateSBOM(ctxSbom, cfg) + rawSbom, err := syft.GenerateSBOM(scanCtx.Context, cfg) if err != nil { - if ctxSbom.Err() == context.Canceled { - log.Error().Msgf("Stopping GenerateSBOM as per user request, scanID:%s", params.ScanId) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_CANCELLED, err.Error(), nil) - } else { - log.Error().Msg(err.Error()) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - } - return nil + return err } gzpb64Sbom := bytes.Buffer{} @@ -183,17 +187,17 @@ func (s SbomGenerator) GenerateSbom(ctx context.Context, task *asynq.Task) error _, err = gzipwriter.Write(rawSbom) if err != nil { log.Error().Msg(err.Error()) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } gzipwriter.Close() + scanCtx.Checkpoint("Before storing to minio") + // upload sbom to minio mc, err := directory.MinioClient(ctx) if err != nil { log.Error().Msg(err.Error()) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } sbomFile := path.Join("/sbom/", utils.ScanIdReplacer.Replace(params.ScanId)+".json.gz") @@ -228,8 +232,7 @@ func (s SbomGenerator) GenerateSbom(ctx context.Context, task *asynq.Task) error if logError == true { log.Error().Msg(err.Error()) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } } @@ -237,8 +240,6 @@ func (s SbomGenerator) GenerateSbom(ctx context.Context, task *asynq.Task) error // write sbom to minio and return details another task will scan sbom - statusChan <- NewSbomScanStatus(params, SBOM_GENERATED, "", nil) - params.SBOMFilePath = sbomFile payload, err := json.Marshal(params) diff --git a/deepfence_worker/tasks/sbom/scan_sbom.go b/deepfence_worker/tasks/sbom/scan_sbom.go index 13c68cf75b..2b6228729c 100644 --- a/deepfence_worker/tasks/sbom/scan_sbom.go +++ b/deepfence_worker/tasks/sbom/scan_sbom.go @@ -9,7 +9,6 @@ import ( "os" "path" "regexp" - "sync" "time" "github.com/anchore/syft/syft/formats" @@ -18,6 +17,7 @@ import ( "github.com/deepfence/ThreatMapper/deepfence_utils/directory" "github.com/deepfence/ThreatMapper/deepfence_utils/log" "github.com/deepfence/ThreatMapper/deepfence_utils/utils" + "github.com/deepfence/golang_deepfence_sdk/utils/tasks" psOutput "github.com/deepfence/package-scanner/output" ps "github.com/deepfence/package-scanner/scanner" "github.com/deepfence/package-scanner/scanner/grype" @@ -84,6 +84,7 @@ func (b UnzippedFile) Close() error { func (s SbomParser) ScanSBOM(ctx context.Context, task *asynq.Task) error { + var err error tenantID, err := directory.ExtractNamespace(ctx) if err != nil { return err @@ -99,30 +100,50 @@ func (s SbomParser) ScanSBOM(ctx context.Context, task *asynq.Task) error { {Key: "namespace", Value: []byte(tenantID)}, } - log.Info().Msgf("payload: %s ", string(task.Payload())) - var params utils.SbomParameters if err := json.Unmarshal(task.Payload(), ¶ms); err != nil { log.Error().Msg(err.Error()) - SendScanStatus(s.ingestC, NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil), rh) return nil } - statusChan := make(chan SbomScanStatus) - var wg sync.WaitGroup - wg.Add(1) - StartStatusReporter("SCAN_SBOM", statusChan, s.ingestC, rh, params, &wg) - defer wg.Wait() + res, scanCtx := tasks.StartStatusReporter(params.ScanId, + func(status tasks.ScanStatus) error { + sb, err := json.Marshal(status) + if err != nil { + return err + } + s.ingestC <- &kgo.Record{ + Topic: utils.VULNERABILITY_SCAN_STATUS, + Value: sb, + Headers: rh, + } + return nil + }, tasks.StatusValues{ + IN_PROGRESS: utils.SCAN_STATUS_INPROGRESS, + CANCELLED: utils.SCAN_STATUS_CANCELLED, + FAILED: utils.SCAN_STATUS_FAILED, + SUCCESS: utils.SCAN_STATUS_SUCCESS, + }, + time.Minute*20, + ) + log.Info().Msgf("Adding scanid to map:%s", params.ScanId) + scanMap.Store(params.ScanId, scanCtx) + defer func() { + log.Info().Msgf("Removing scaind from map:%s", params.ScanId) + scanMap.Delete(params.ScanId) + res <- err + close(res) + }() + + log.Info().Msgf("payload: %s ", string(task.Payload())) // send inprogress status - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_INPROGRESS, "", nil) mc, err := directory.MinioClient(ctx) if err != nil { log.Error().Msg(err.Error()) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } sbomFilePath := path.Join("/tmp", utils.ScanIdReplacer.Replace(params.ScanId)+".json") @@ -135,8 +156,7 @@ func (s SbomParser) ScanSBOM(ctx context.Context, task *asynq.Task) error { err = mc.DownloadFileTo(context.Background(), params.SBOMFilePath, sbomFile, minio.GetObjectOptions{}) if err != nil { log.Error().Msg(err.Error()) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } defer func() { log.Info().Msgf("remove sbom file %s", sbomFilePath) @@ -150,8 +170,7 @@ func (s SbomParser) ScanSBOM(ctx context.Context, task *asynq.Task) error { vulnerabilities, err := grype.Scan(grypeBin, grypeConfig, sbomFilePath, &env) if err != nil { log.Error().Msgf("error: %s output: %s", err.Error(), string(vulnerabilities)) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } cfg := psUtils.Config{ @@ -167,8 +186,7 @@ func (s SbomParser) ScanSBOM(ctx context.Context, task *asynq.Task) error { report, err := grype.PopulateFinalReport(vulnerabilities, cfg) if err != nil { log.Error().Msgf("error on generate vulnerability report: %s", err) - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_FAILED, err.Error(), nil) - return nil + return err } details := psOutput.CountBySeverity(&report) @@ -189,36 +207,17 @@ func (s SbomParser) ScanSBOM(ctx context.Context, task *asynq.Task) error { } } - // scan status - info := model.ScanInfo{ - ScanId: params.ScanId, - Status: utils.SCAN_STATUS_SUCCESS, - UpdatedAt: time.Now().Unix(), - NodeId: params.NodeId, - NodeType: params.NodeType, - NodeName: params.NodeId, - SeverityCounts: map[string]int32{ - "total": int32(details.Total), - psUtils.CRITICAL: int32(details.Severity.Critical), - psUtils.HIGH: int32(details.Severity.High), - psUtils.MEDIUM: int32(details.Severity.Medium), - psUtils.LOW: int32(details.Severity.Low), - }, - } - - statusChan <- NewSbomScanStatus(params, utils.SCAN_STATUS_SUCCESS, "", &info) - // generate runtime sbom runtimeSbom, err := generateRuntimeSBOM(sbomFilePath, report) if err != nil { log.Error().Err(err).Msgf("failed to generate runtime sbom") - return nil + return err } runtimeSbomBytes, err := json.Marshal(runtimeSbom) if err != nil { log.Error().Err(err).Msgf("failed to marshal runtime sbom") - return nil + return err } runtimeSbomPath := path.Join("/sbom/", "runtime-"+utils.ScanIdReplacer.Replace(params.ScanId)+".json") @@ -226,7 +225,7 @@ func (s SbomParser) ScanSBOM(ctx context.Context, task *asynq.Task) error { minio.PutObjectOptions{ContentType: "application/json"}) if err != nil { log.Error().Err(err).Msgf("failed to upload runtime sbom") - return nil + return err } log.Info().Msgf("scan_id: %s, runtime sbom minio file info: %+v", params.ScanId, uploadInfo) diff --git a/deepfence_worker/tasks/sbom/utils.go b/deepfence_worker/tasks/sbom/utils.go deleted file mode 100644 index e8c1541223..0000000000 --- a/deepfence_worker/tasks/sbom/utils.go +++ /dev/null @@ -1,82 +0,0 @@ -package sbom - -import ( - "encoding/json" - "sync" - "time" - - "github.com/deepfence/ThreatMapper/deepfence_server/model" - "github.com/deepfence/ThreatMapper/deepfence_utils/log" - "github.com/deepfence/ThreatMapper/deepfence_utils/utils" - "github.com/twmb/franz-go/pkg/kgo" -) - -const SBOM_GENERATED = "SBOM_GENERATED" - -type SbomScanStatus struct { - utils.SbomParameters - ScanStatus string `json:"scan_status,omitempty"` - ScanMessage string `json:"scan_message,omitempty"` - ScanInfo *model.ScanInfo `json:"scan_info,omitempty"` -} - -func NewSbomScanStatus(params utils.SbomParameters, status string, msg string, info *model.ScanInfo) SbomScanStatus { - return SbomScanStatus{SbomParameters: params, ScanStatus: status, ScanMessage: msg, ScanInfo: info} -} - -func SendScanStatus(ingestC chan *kgo.Record, status SbomScanStatus, rh []kgo.RecordHeader) error { - sb, err := json.Marshal(status) - if err != nil { - log.Error().Msg(err.Error()) - return err - } else { - ingestC <- &kgo.Record{ - Topic: utils.VULNERABILITY_SCAN_STATUS, - Value: sb, - Headers: rh, - } - } - return nil -} - -func StartStatusReporter(title string, statusChan chan SbomScanStatus, ingestC chan *kgo.Record, - rh []kgo.RecordHeader, params utils.SbomParameters, wg *sync.WaitGroup) { - - go func() { - log.Info().Msgf("StatusReporter(%s) started, scanid: %s", title, params.ScanId) - defer wg.Done() - - ticker := time.NewTicker(30 * time.Second) - InProgressStatus := NewSbomScanStatus(params, utils.SCAN_STATUS_INPROGRESS, "", nil) - loop: - for { - select { - case statusIn := <-statusChan: - status := statusIn.ScanStatus - - if status == SBOM_GENERATED { - break loop - } - - err := SendScanStatus(ingestC, statusIn, rh) - if err != nil { - log.Error().Msgf("error sending scan status: %s, scanid: %s", - err.Error(), params.ScanId) - } - if status == utils.SCAN_STATUS_SUCCESS || - status == utils.SCAN_STATUS_FAILED || - status == utils.SCAN_STATUS_CANCELLED { - break loop - } - case <-ticker.C: - err := SendScanStatus(ingestC, InProgressStatus, rh) - if err != nil { - log.Error().Msgf("error sending periodic In_PROGRESS scan status: %s, scanid: %s", - err.Error(), params.ScanId) - } - } - } - - log.Info().Msgf("StatusReporter(%s) exited, scanid: %s", title, params.ScanId) - }() -} diff --git a/deepfence_worker/tasks/scans/status.go b/deepfence_worker/tasks/scans/status.go index 631d71bfb8..9c79ff7060 100644 --- a/deepfence_worker/tasks/scans/status.go +++ b/deepfence_worker/tasks/scans/status.go @@ -44,7 +44,7 @@ func UpdatePodScanStatus(ctx context.Context, task *asynq.Task) error { MATCH (s:` + string(event.ScanType) + `{node_id: row.scan_id})-[:SCANNED]->(c:Container) WHERE c.pod_id IS NOT NULL MATCH (n:Pod{node_id: c.pod_id}) - SET n.` + ingestersUtil.ScanStatusField[event.ScanType] + `=row.scan_status` + SET n.` + ingestersUtil.ScanStatusField[event.ScanType] + `=s.scan_status` log.Debug().Msgf("query: %v", query) _, err = session.Run(query, diff --git a/deepfence_worker/tasks/secretscan/secretscan.go b/deepfence_worker/tasks/secretscan/secretscan.go index 5f08884b91..034e4ba283 100644 --- a/deepfence_worker/tasks/secretscan/secretscan.go +++ b/deepfence_worker/tasks/secretscan/secretscan.go @@ -7,10 +7,10 @@ import ( "os" "os/exec" "sync" + "time" "github.com/deepfence/SecretScanner/core" "github.com/deepfence/SecretScanner/output" - "github.com/deepfence/SecretScanner/scan" secretScan "github.com/deepfence/SecretScanner/scan" "github.com/deepfence/SecretScanner/signature" "github.com/deepfence/ThreatMapper/deepfence_utils/directory" @@ -19,6 +19,7 @@ import ( "github.com/deepfence/ThreatMapper/deepfence_worker/cronjobs" workerUtils "github.com/deepfence/ThreatMapper/deepfence_worker/utils" pb "github.com/deepfence/agent-plugins-grpc/srcgo" + tasks "github.com/deepfence/golang_deepfence_sdk/utils/tasks" "github.com/hibiken/asynq" "github.com/twmb/franz-go/pkg/kgo" ) @@ -57,8 +58,9 @@ func (s SecretScan) StopSecretScan(ctx context.Context, task *asynq.Task) error return nil } - scanCtx := obj.(*scan.ScanContext) - scanCtx.Stopped.Store(true) + scanCtx := obj.(*tasks.ScanContext) + scanCtx.StopTriggered.Store(true) + scanCtx.Cancel() log.Error().Msgf("SecretScanner::Stop request submitted, ScanID: %s", scanID) return nil @@ -77,10 +79,6 @@ func (s SecretScan) StartSecretScan(ctx context.Context, task *asynq.Task) error } log.Info().Msgf("message tenant id %s", string(tenantID)) - rh := []kgo.RecordHeader{ - {Key: "namespace", Value: []byte(tenantID)}, - } - log.Info().Msgf("payload: %s ", string(task.Payload())) var params utils.SecretScanParameters @@ -98,8 +96,26 @@ func (s SecretScan) StartSecretScan(ctx context.Context, task *asynq.Task) error //Set this "hardErr" variable to appropriate error if //an error has caused used to abort/return from this function var hardErr error - scanCtx := scan.NewScanContext(params.ScanId) - res := StartStatusReporter(context.Background(), scanCtx, params, s.ingestC, rh) + res, scanCtx := tasks.StartStatusReporter(params.ScanId, + func(status tasks.ScanStatus) error { + sb, err := json.Marshal(status) + if err != nil { + return err + } + s.ingestC <- &kgo.Record{ + Topic: utils.SECRET_SCAN_STATUS, + Value: sb, + Headers: []kgo.RecordHeader{{Key: "namespace", Value: []byte(tenantID)}}, + } + return nil + }, tasks.StatusValues{ + IN_PROGRESS: utils.SCAN_STATUS_INPROGRESS, + CANCELLED: utils.SCAN_STATUS_CANCELLED, + FAILED: utils.SCAN_STATUS_FAILED, + SUCCESS: utils.SCAN_STATUS_SUCCESS, + }, + time.Minute*20, + ) ScanMap.Store(params.ScanId, scanCtx) @@ -111,7 +127,7 @@ func (s SecretScan) StartSecretScan(ctx context.Context, task *asynq.Task) error }() // send inprogress status - scanCtx.ScanStatusChan <- true + scanCtx.Checkpoint("After initialization") // get registry credentials authDir, creds, err := workerUtils.GetConfigFileFromRegistry(ctx, params.RegistryId) @@ -145,8 +161,7 @@ func (s SecretScan) StartSecretScan(ctx context.Context, task *asynq.Task) error dir, err := ioutil.TempDir("/tmp", "secretscan-*") if err != nil { - // return nil - log.Error().Msgf(err.Error()) + return err } defer os.RemoveAll(dir) @@ -171,9 +186,9 @@ func (s SecretScan) StartSecretScan(ctx context.Context, task *asynq.Task) error return nil } - // init secret scan - scanCtx.ScanStatusChan <- true + scanCtx.Checkpoint("After skopeo download") + // init secret scan scanResult, err := secretScan.ExtractAndScanFromTar(dir, imageName, scanCtx) if err != nil { log.Error().Msg(err.Error()) @@ -197,7 +212,7 @@ func (s SecretScan) StartSecretScan(ctx context.Context, task *asynq.Task) error s.ingestC <- &kgo.Record{ Topic: utils.SECRET_SCAN, Value: cb, - Headers: rh, + Headers: []kgo.RecordHeader{{Key: "namespace", Value: []byte(tenantID)}}, } } } diff --git a/deepfence_worker/tasks/secretscan/utils.go b/deepfence_worker/tasks/secretscan/utils.go deleted file mode 100644 index fd2fae642a..0000000000 --- a/deepfence_worker/tasks/secretscan/utils.go +++ /dev/null @@ -1,113 +0,0 @@ -package secretscan - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/deepfence/SecretScanner/scan" - "github.com/deepfence/ThreatMapper/deepfence_utils/log" - "github.com/deepfence/ThreatMapper/deepfence_utils/utils" - "github.com/twmb/franz-go/pkg/kgo" -) - -type SecretScanStatus struct { - utils.SecretScanParameters - ScanStatus string `json:"scan_status,omitempty"` - ScanMessage string `json:"scan_message,omitempty"` -} - -func NewSecretScanStatus(params utils.SecretScanParameters, Status string, msg string) SecretScanStatus { - return SecretScanStatus{SecretScanParameters: params, ScanStatus: Status, ScanMessage: msg} -} - -func SendScanStatus(ingestC chan *kgo.Record, status SecretScanStatus, rh []kgo.RecordHeader) error { - sb, err := json.Marshal(status) - if err != nil { - log.Error().Msg(err.Error()) - return err - } else { - ingestC <- &kgo.Record{ - Topic: utils.SECRET_SCAN_STATUS, - Value: sb, - Headers: rh, - } - } - return nil -} - -func StartStatusReporter(ctx context.Context, scanCtx *scan.ScanContext, - params utils.SecretScanParameters, ingestC chan *kgo.Record, - rh []kgo.RecordHeader) chan error { - - res := make(chan error) - scan_id := scanCtx.ScanID - - //If we don't get any active status back within threshold, - //we consider the scan job as dead - threshold := 600 - go func() { - ticker := time.NewTicker(1 * time.Second) - var err, abort error - ts := time.Now() - var counter uint64 - log.Info().Msgf("StatusReporter started, scan_id: %s", scan_id) - loop: - for { - select { - case err = <-res: - break loop - case <-ctx.Done(): - abort = ctx.Err() - break loop - case <-scanCtx.ScanStatusChan: - ts = time.Now() - case <-ticker.C: - if scanCtx.Stopped.Load() == true { - log.Info().Msgf("Scanner job stopped, Scan id: %s", scan_id) - break loop - } - - counter++ - //log.Info().Msgf("VARUN::: ScanID: %s, counter is: %d", scan_id, counter) - - //We perform the check once per 30 seconds - if counter%30 != 0 { - continue - } - - elapsed := int(time.Since(ts).Seconds()) - if elapsed > threshold { - err = fmt.Errorf("Scan job aborted due to inactivity") - log.Error().Msgf("Scan job aborted due to inactivity, Scan id: %s", - scan_id) - - scanCtx.Aborted.Store(true) - break loop - } else { - SendScanStatus(ingestC, NewSecretScanStatus(params, - utils.SCAN_STATUS_INPROGRESS, ""), rh) - - } - } - } - - if abort != nil { - SendScanStatus(ingestC, NewSecretScanStatus(params, - utils.SCAN_STATUS_FAILED, abort.Error()), rh) - } else if scanCtx.Stopped.Load() == true { - SendScanStatus(ingestC, NewSecretScanStatus(params, - utils.SCAN_STATUS_CANCELLED, "Scan stopped by user"), rh) - } else if err != nil { - SendScanStatus(ingestC, NewSecretScanStatus(params, - utils.SCAN_STATUS_FAILED, err.Error()), rh) - } else { - SendScanStatus(ingestC, NewSecretScanStatus(params, - utils.SCAN_STATUS_SUCCESS, ""), rh) - } - - log.Info().Msgf("StatusReporter finished, scan_id: %s", scan_id) - }() - return res -} diff --git a/deepfence_worker/worker.go b/deepfence_worker/worker.go index 2211e9bac6..635810dc33 100644 --- a/deepfence_worker/worker.go +++ b/deepfence_worker/worker.go @@ -68,7 +68,7 @@ func skipRetryCallbackWrapper(taskCallback wtils.WorkerHandler) wtils.WorkerHand } } -func (w *Worker) AddHandler( +func (w *Worker) AddRetryableHandler( task string, taskCallback wtils.WorkerHandler, ) error { @@ -82,7 +82,7 @@ func (w *Worker) AddHandler( // CronJobHandler do not retry on failure // The job will simply be tried again later on. -func (w *Worker) AddCronJobHandler( +func (w *Worker) AddOneShotHandler( task string, taskCallback wtils.WorkerHandler, ) error { @@ -142,58 +142,58 @@ func NewWorker(ns directory.NamespaceID, cfg config) (Worker, context.CancelFunc namespace: ns, } - worker.AddCronJobHandler(utils.CleanUpGraphDBTask, cronjobs.CleanUpDB) + worker.AddOneShotHandler(utils.CleanUpGraphDBTask, cronjobs.CleanUpDB) - worker.AddCronJobHandler(utils.ComputeThreatTask, cronjobs.ComputeThreat) + worker.AddOneShotHandler(utils.ComputeThreatTask, cronjobs.ComputeThreat) - worker.AddCronJobHandler(utils.RetryFailedScansTask, cronjobs.RetryScansDB) + worker.AddOneShotHandler(utils.RetryFailedScansTask, cronjobs.RetryScansDB) - worker.AddCronJobHandler(utils.RetryFailedUpgradesTask, cronjobs.RetryUpgradeAgent) + worker.AddOneShotHandler(utils.RetryFailedUpgradesTask, cronjobs.RetryUpgradeAgent) - worker.AddCronJobHandler(utils.CleanUpPostgresqlTask, cronjobs.CleanUpPostgresDB) + worker.AddOneShotHandler(utils.CleanUpPostgresqlTask, cronjobs.CleanUpPostgresDB) - worker.AddCronJobHandler(utils.CleanupDiagnosisLogs, cronjobs.CleanUpDiagnosisLogs) + worker.AddOneShotHandler(utils.CleanupDiagnosisLogs, cronjobs.CleanUpDiagnosisLogs) - worker.AddCronJobHandler(utils.CheckAgentUpgradeTask, cronjobs.CheckAgentUpgrade) + worker.AddOneShotHandler(utils.CheckAgentUpgradeTask, cronjobs.CheckAgentUpgrade) - worker.AddCronJobHandler(utils.TriggerConsoleActionsTask, cronjobs.TriggerConsoleControls) + worker.AddOneShotHandler(utils.TriggerConsoleActionsTask, cronjobs.TriggerConsoleControls) - worker.AddCronJobHandler(utils.ScheduledTasks, cronjobs.RunScheduledTasks) + worker.AddOneShotHandler(utils.ScheduledTasks, cronjobs.RunScheduledTasks) - worker.AddCronJobHandler(utils.SyncRegistryTask, cronjobs.SyncRegistry) + worker.AddRetryableHandler(utils.SyncRegistryTask, cronjobs.SyncRegistry) - worker.AddCronJobHandler(utils.CloudComplianceTask, cronjobs.AddCloudControls) + worker.AddRetryableHandler(utils.CloudComplianceTask, cronjobs.AddCloudControls) - worker.AddCronJobHandler(utils.CachePostureProviders, cronjobs.CachePostureProviders) + worker.AddOneShotHandler(utils.CachePostureProviders, cronjobs.CachePostureProviders) - worker.AddCronJobHandler(utils.SendNotificationTask, cronjobs.SendNotifications) + worker.AddOneShotHandler(utils.SendNotificationTask, cronjobs.SendNotifications) - worker.AddCronJobHandler(utils.ReportGeneratorTask, reports.GenerateReport) + worker.AddRetryableHandler(utils.ReportGeneratorTask, reports.GenerateReport) - worker.AddCronJobHandler(utils.ReportCleanUpTask, cronjobs.CleanUpReports) + worker.AddOneShotHandler(utils.ReportCleanUpTask, cronjobs.CleanUpReports) - worker.AddCronJobHandler(utils.LinkCloudResourceTask, cronjobs.LinkCloudResources) + worker.AddOneShotHandler(utils.LinkCloudResourceTask, cronjobs.LinkCloudResources) - worker.AddCronJobHandler(utils.LinkNodesTask, cronjobs.LinkNodes) + worker.AddOneShotHandler(utils.LinkNodesTask, cronjobs.LinkNodes) // sbom - worker.AddHandler(utils.ScanSBOMTask, sbom.NewSBOMScanner(ingestC).ScanSBOM) + worker.AddRetryableHandler(utils.ScanSBOMTask, sbom.NewSBOMScanner(ingestC).ScanSBOM) - worker.AddHandler(utils.GenerateSBOMTask, sbom.NewSbomGenerator(ingestC).GenerateSbom) + worker.AddOneShotHandler(utils.GenerateSBOMTask, sbom.NewSbomGenerator(ingestC).GenerateSbom) - worker.AddHandler(utils.SecretScanTask, secretscan.NewSecretScanner(ingestC).StartSecretScan) + worker.AddOneShotHandler(utils.SecretScanTask, secretscan.NewSecretScanner(ingestC).StartSecretScan) - worker.AddHandler(utils.StopSecretScanTask, secretscan.NewSecretScanner(ingestC).StopSecretScan) + worker.AddOneShotHandler(utils.StopSecretScanTask, secretscan.NewSecretScanner(ingestC).StopSecretScan) - worker.AddHandler(utils.MalwareScanTask, malwarescan.NewMalwareScanner(ingestC).StartMalwareScan) + worker.AddOneShotHandler(utils.MalwareScanTask, malwarescan.NewMalwareScanner(ingestC).StartMalwareScan) - worker.AddHandler(utils.StopMalwareScanTask, malwarescan.NewMalwareScanner(ingestC).StopMalwareScan) + worker.AddOneShotHandler(utils.StopMalwareScanTask, malwarescan.NewMalwareScanner(ingestC).StopMalwareScan) - worker.AddHandler(utils.StopVulnerabilityScanTask, sbom.StopVulnerabilityScan) + worker.AddOneShotHandler(utils.StopVulnerabilityScanTask, sbom.StopVulnerabilityScan) - worker.AddHandler(utils.UpdateCloudResourceScanStatusTask, scans.UpdateCloudResourceScanStatus) + worker.AddRetryableHandler(utils.UpdateCloudResourceScanStatusTask, scans.UpdateCloudResourceScanStatus) - worker.AddHandler(utils.UpdatePodScanStatusTask, scans.UpdatePodScanStatus) + worker.AddRetryableHandler(utils.UpdatePodScanStatusTask, scans.UpdatePodScanStatus) return worker, cancel, nil } diff --git a/deployment-scripts/helm-charts/index.yaml b/deployment-scripts/helm-charts/index.yaml index ce4128c2db..5fddd1ad24 100644 --- a/deployment-scripts/helm-charts/index.yaml +++ b/deployment-scripts/helm-charts/index.yaml @@ -11,6 +11,21 @@ entries: urls: - deepfence-agent-2.0.0.tgz version: 2.0.0 + - apiVersion: v2 + appVersion: 1.5.0 + created: "2023-09-29T19:30:09.962553+05:30" + dependencies: + - condition: deployKubernetesScanner + name: deepfence-k8s-scanner + repository: https://deepfence-helm-charts.s3.amazonaws.com/deepfence-k8s-scanner + version: 0.1.0 + description: Deepfence Agent - Helm chart for Kubernetes + digest: f302ab1bdd9fb3fa834178cd007c1404ee072aa75f0fc9e71ba1e51ea7a0c3b7 + name: deepfence-agent + type: application + urls: + - deepfence-agent-1.5.0.tgz + version: 1.5.0 - apiVersion: v2 appVersion: 1.4.2 created: "2023-01-12T18:17:57.272773+05:30" @@ -197,6 +212,16 @@ entries: urls: - deepfence-console-2.0.0.tgz version: 2.0.0 + - apiVersion: v2 + appVersion: 1.5.0 + created: "2023-09-29T19:30:09.965637+05:30" + description: Deepfence Console - Helm chart for Kubernetes + digest: 4816561b6d915ef038bf5a899c60a4b97570041a4e56c22c19bc4da16210993b + name: deepfence-console + type: application + urls: + - deepfence-console-1.5.2.tgz + version: 1.5.2 - apiVersion: v2 appVersion: 1.4.2 created: "2023-01-12T18:17:57.277377+05:30" @@ -388,6 +413,16 @@ entries: urls: - deepfence-router-2.0.0.tgz version: 2.0.0 + - apiVersion: v2 + appVersion: 1.5.0 + created: "2023-09-29T19:30:09.966255+05:30" + description: Deepfence Router - Helm chart for Kubernetes + digest: f580b1ae2b425fa380271fa49d257a2186968efdb1406f1e9b0306bd441bfec4 + name: deepfence-router + type: application + urls: + - deepfence-router-1.5.0.tgz + version: 1.5.0 - apiVersion: v2 appVersion: 1.4.2 created: "2023-01-12T18:17:57.278349+05:30" @@ -488,4 +523,4 @@ entries: urls: - deepfence-router-1.0.0.tgz version: 1.0.0 -generated: "2023-09-19T22:47:44.415974+05:30" +generated: "2023-09-29T19:30:09.961121+05:30" diff --git a/docs/docs/architecture/cloudscanner.md b/docs/docs/architecture/cloudscanner.md index 86ed114a13..8a050f1ad7 100644 --- a/docs/docs/architecture/cloudscanner.md +++ b/docs/docs/architecture/cloudscanner.md @@ -25,7 +25,7 @@ Each Cloud Scanner task runs in your cloud environment, gathering inventory and Cloud Scanner tasks are deployed using the appropriate Terraform module for each cloud, and are configured with the address and API key of your management console. They 'phone home' to your management console and take instructions on demand; they do not listen for remote connections or control. :::info -Refer to the Installation Documentation to [Learn how to install Cloud Scanner tasks](/docs/cloudscanner) +Refer to the Installation Documentation to [Learn how to install Cloud Scanner tasks](/docs/v2.0/cloudscanner) ::: diff --git a/docs/docs/architecture/sensors.md b/docs/docs/architecture/sensors.md index f762d3fddb..13bd0b3c1f 100644 --- a/docs/docs/architecture/sensors.md +++ b/docs/docs/architecture/sensors.md @@ -14,5 +14,5 @@ The sensors support the following production platforms: * **AWS Fargate** The sensor is deployed as a daemon service alongside each serverless instance. :::info -Refer to the Installation Documentation to [Learn how to install Sensor Agents](/docs/sensors) +Refer to the Installation Documentation to [Learn how to install Sensor Agents](/docs/v2.0/sensors) ::: \ No newline at end of file diff --git a/docs/docs/cloudscanner/aws.md b/docs/docs/cloudscanner/aws.md index e731b5ed93..acec23db3c 100644 --- a/docs/docs/cloudscanner/aws.md +++ b/docs/docs/cloudscanner/aws.md @@ -102,7 +102,7 @@ Controls are grouped into **benchmarks**. Where multiple benchmarks are availabl When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v2.0/operations/compliance). :::tip Maximizing Coverage For maximum coverage, you can use both Cloud Scanner and local Sensor Agent compliance scans together. You could scan your AWS infrastructure using Cloud Scanner, and [scan selected VMs deployed within AWS](other) using the Sensor Agent. diff --git a/docs/docs/cloudscanner/azure.md b/docs/docs/cloudscanner/azure.md index e5a051917a..5325fd2244 100644 --- a/docs/docs/cloudscanner/azure.md +++ b/docs/docs/cloudscanner/azure.md @@ -45,7 +45,7 @@ Controls are grouped into **benchmarks**. Where multiple benchmarks are availabl When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v2.0/operations/compliance). :::tip Maximizing Coverage For maximum coverage, you can use both Cloud Scanner and local Sensor Agent compliance scans together. You could scan your Azure infrastructure using Cloud Scanner, and [scan selected VMs deployed within Azure](other) using the Sensor Agent. diff --git a/docs/docs/cloudscanner/gcp.md b/docs/docs/cloudscanner/gcp.md index a1781fe283..6ce8b9184b 100644 --- a/docs/docs/cloudscanner/gcp.md +++ b/docs/docs/cloudscanner/gcp.md @@ -70,7 +70,7 @@ Controls are grouped into **benchmarks**. Where multiple benchmarks are availabl When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v2.0/operations/compliance). :::tip Maximizing Coverage For maximum coverage, you can use both Cloud Scanner and local Sensor Agent compliance scans together. You could scan your GCP infrastructure using Cloud Scanner, and [scan selected VMs deployed within GCP](other) using the Sensor Agent. diff --git a/docs/docs/cloudscanner/index.md b/docs/docs/cloudscanner/index.md index e77634c98e..5e02371c47 100644 --- a/docs/docs/cloudscanner/index.md +++ b/docs/docs/cloudscanner/index.md @@ -10,7 +10,7 @@ The Cloud Scanner task interacts with the local cloud APIs under the instruction ## Before You Begin -Review the architecture for compliance scanning, as described in [Architecture: Cloud Scanner task](/docs/architecture/cloudscanner). +Review the architecture for compliance scanning, as described in [Architecture: Cloud Scanner task](/docs/v2.0/architecture/cloudscanner). ## Configuring Cloud Posture Management diff --git a/docs/docs/cloudscanner/other.md b/docs/docs/cloudscanner/other.md index 10558a569a..9074a6e04c 100644 --- a/docs/docs/cloudscanner/other.md +++ b/docs/docs/cloudscanner/other.md @@ -6,7 +6,7 @@ title: Other Platforms ThreatMapper can perform compliance posture scanning on linux hosts and Kubernetes master and worker nodes. -Scanning is done directly, using a local [Sensor Agent](/docs/sensors) rather than by using the Cloud Scanner task employed by the cloud platform integrations. +Scanning is done directly, using a local [Sensor Agent](/docs/v2.0/sensors) rather than by using the Cloud Scanner task employed by the cloud platform integrations. ## What Compliance Scans are Performed? @@ -15,7 +15,7 @@ The sensor agent has direct visibility into the configuration of the base operat When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v2.0/operations/compliance). :::tip Maximizing Coverage diff --git a/docs/docs/console/index.md b/docs/docs/console/index.md index a3346ce5fc..86a0daa543 100644 --- a/docs/docs/console/index.md +++ b/docs/docs/console/index.md @@ -8,9 +8,9 @@ The ThreatMapper Management Console ("Console") is a standalone application, imp ## Before You Begin -Review the architecture for the Management Console, as described in [Architecture: Management Console](/docs/architecture/console). +Review the architecture for the Management Console, as described in [Architecture: Management Console](/docs/v2.0/architecture/console). -Review the requirements for the Management Console, as described in [System Requirements](/docs/console/requirements). +Review the requirements for the Management Console, as described in [System Requirements](/docs/v2.0/console/requirements). ## Installing the Management Console diff --git a/docs/docs/developers/build.md b/docs/docs/developers/build.md index cbf7b82c01..95830d45e8 100644 --- a/docs/docs/developers/build.md +++ b/docs/docs/developers/build.md @@ -33,7 +33,7 @@ cd ThreatMapper/deployment-scripts docker-compose -f docker-compose.yml up --detach ``` -Once started, you can point a web browser at `https://--IP-ADDRESS---/` to register a first user on the Deepfence Management Console. See [Initial Configuration](/docs/console/initial-configuration) for more information. +Once started, you can point a web browser at `https://--IP-ADDRESS---/` to register a first user on the Deepfence Management Console. See [Initial Configuration](/docs/v2.0/console/initial-configuration) for more information. To stop the Deepfence Management Console: diff --git a/docs/docs/developers/deploy-agent.md b/docs/docs/developers/deploy-agent.md index 238b7df946..e8bf6f079f 100644 --- a/docs/docs/developers/deploy-agent.md +++ b/docs/docs/developers/deploy-agent.md @@ -4,13 +4,13 @@ title: Deploy Sensors # Deploy custom ThreatMapper Sensor Agents -You should first [build the management console and agents](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/sensors/docker), [Kubernetes](/docs/sensors/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. +You should first [build the management console and agents](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/v2.0/sensors/docker), [Kubernetes](/docs/v2.0/sensors/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. ## Installing and Running the Sensor Agents on a Docker Host :::tip -Refer to the [Docker Installation Instructions](/docs/sensors/docker) along with the modifications below. +Refer to the [Docker Installation Instructions](/docs/v2.0/sensors/docker) along with the modifications below. ::: Execute the following command to install and start the sensors: @@ -42,7 +42,7 @@ docker run -dit \ ## Installing and Running the Sensor Agents in a Kubernetes Cluster :::tip -Refer to the [Kubernetes Installation Instructions](/docs/sensors/kubernetes) along with the modifications below. +Refer to the [Kubernetes Installation Instructions](/docs/v2.0/sensors/kubernetes) along with the modifications below. ::: You can use these instructions for helm-based installations in standalone and hosted Kubernetes clusters diff --git a/docs/docs/developers/deploy-console.md b/docs/docs/developers/deploy-console.md index d3f4d215ec..3dca30041f 100644 --- a/docs/docs/developers/deploy-console.md +++ b/docs/docs/developers/deploy-console.md @@ -4,14 +4,14 @@ title: Deploy Console # Deploy a custom ThreatMapper Console -You should first [build the management console](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/console/docker), [Kubernetes](/docs/console/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. +You should first [build the management console](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/v2.0/console/docker), [Kubernetes](/docs/v2.0/console/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. ## Installing and Running the Management Console on a Docker Host :::tip -Refer to the [Docker Installation Instructions](/docs/console/docker) along with the modifications below. +Refer to the [Docker Installation Instructions](/docs/v2.0/console/docker) along with the modifications below. ::: 1. Download the file [docker-compose.yml](https://github.com/deepfence/ThreatMapper/blob/release-2.0/deployment-scripts/docker-compose.yml) to the system that will host the Console @@ -31,12 +31,12 @@ Refer to the [Docker Installation Instructions](/docs/console/docker) along with ## Installing and Running the Management Console in a Kubernetes Cluster :::tip -Refer to the [Kubernetes Installation Instructions](/docs/console/kubernetes) along with the modifications below. +Refer to the [Kubernetes Installation Instructions](/docs/v2.0/console/kubernetes) along with the modifications below. ::: 1. Prepare the cluster, installing the storage driver and metrics service - Follow the instructions to install the OpenEBS storage and metrics server: [Installation Instructions](/docs/console/kubernetes) + Follow the instructions to install the OpenEBS storage and metrics server: [Installation Instructions](/docs/v2.0/console/kubernetes) 2. Install your Management Console diff --git a/docs/docs/integrations/index.md b/docs/docs/integrations/index.md index 38927d15bc..e097f40eec 100644 --- a/docs/docs/integrations/index.md +++ b/docs/docs/integrations/index.md @@ -4,7 +4,7 @@ title: Integrations # Integrations -You can integrate ThreatMapper with a variety of notification services. Any time a new vulnerability is detected (for example, during [CI](/docs/operations/scanning-ci) or an [automated scan](/docs/operations/scanning)), ThreatMapper will submit the details to the configured notification services. +You can integrate ThreatMapper with a variety of notification services. Any time a new vulnerability is detected (for example, during [CI](/docs/v2.0/operations/scanning-ci) or an [automated scan](/docs/v2.0/operations/scanning)), ThreatMapper will submit the details to the configured notification services. | ![Integrations](../img/integrations.png) | |:----------------------------------------------:| diff --git a/docs/docs/kubernetes-scanner/index.md b/docs/docs/kubernetes-scanner/index.md index 99b9099db7..5666f3c7e2 100644 --- a/docs/docs/kubernetes-scanner/index.md +++ b/docs/docs/kubernetes-scanner/index.md @@ -10,4 +10,4 @@ NSA & CISA Cybersecurity Technical Report describes the complexities of securely ## Configuring Kubernetes Scanner -Deepfence Kubernetes Scanner is installed with agent sensors. Follow the documentation [here](/docs/sensors/kubernetes) to install Deepfence agent sensors in the kubernetes cluster. \ No newline at end of file +Deepfence Kubernetes Scanner is installed with agent sensors. Follow the documentation [here](/docs/v2.0/sensors/kubernetes) to install Deepfence agent sensors in the kubernetes cluster. \ No newline at end of file diff --git a/docs/docs/operations/compliance.md b/docs/docs/operations/compliance.md index 15c2755dc8..a970664bda 100644 --- a/docs/docs/operations/compliance.md +++ b/docs/docs/operations/compliance.md @@ -38,7 +38,7 @@ The benchmarks available vary by cloud provider: Begin on the **Posture** page in the ThreatMapper console. -Select a cloud instance that you have [configured previously](/docs/cloudscanner/). You may have several instances of a given cloud type: +Select a cloud instance that you have [configured previously](/docs/v2.0/cloudscanner/). You may have several instances of a given cloud type: | ![Cloud Compliance Scan - Select](../img/compliance-scan-1.png) | |:---------------------------------------------------------------:| @@ -48,7 +48,7 @@ Select a cloud instance that you have [configured previously](/docs/cloudscanner |:---------------------------------------------------------------:| | Select target for Cloud Compliance Scan | -If you want to scan a host (Linux host or Kubernetes master or slave node), ensure that the [threatmapper sensor](/docs/sensors) is deployed on that host. +If you want to scan a host (Linux host or Kubernetes master or slave node), ensure that the [threatmapper sensor](/docs/v2.0/sensors) is deployed on that host. Select the compliance benchmarks you wish to run on the target cloud instance or host: diff --git a/docs/docs/sensors/docker.md b/docs/docs/sensors/docker.md index 3576e14839..98100904cc 100644 --- a/docs/docs/sensors/docker.md +++ b/docs/docs/sensors/docker.md @@ -6,7 +6,7 @@ title: Docker On a Linux-based Docker host, the ThreatMapper agents are deployed as a lightweight container. -Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/architecture#threatmapper-sensor-containers) for minimum supported platforms. +Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/v2.0/architecture#threatmapper-sensor-containers) for minimum supported platforms. For Windows Server hosts, experimental support exists, but it is not suitable for production use. diff --git a/docs/docs/sensors/index.md b/docs/docs/sensors/index.md index caf097ea49..88889c6989 100644 --- a/docs/docs/sensors/index.md +++ b/docs/docs/sensors/index.md @@ -10,11 +10,11 @@ A single ThreatMapper Console can manage multiple workload types, and on-premise ## Before You Begin -Before you install the Sensors, obtain the Management Console URL and API key as described in the [Initial Configuration](/docs/console/initial-configuration). +Before you install the Sensors, obtain the Management Console URL and API key as described in the [Initial Configuration](/docs/v2.0/console/initial-configuration). You should take care to install the sensor version that matches your Management Console version, as compatibility across versions is not guaranteed. -Review the architecture for the Sensor Agent, as described in [Architecture: Sensor Agent](/docs/architecture/sensors). +Review the architecture for the Sensor Agent, as described in [Architecture: Sensor Agent](/docs/v2.0/architecture/sensors). ## System Requirements diff --git a/docs/docs/sensors/linux-host.md b/docs/docs/sensors/linux-host.md index 83362cc45d..ba06e495a8 100644 --- a/docs/docs/sensors/linux-host.md +++ b/docs/docs/sensors/linux-host.md @@ -8,7 +8,7 @@ On a Linux-based bare-metal or virtual machine workload, the ThreatMapper sensor ## ThreatMapper Sensor Agents -Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/architecture#threatmapper-sensor-containers) for minimum supported platforms. +Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/v2.0/architecture#threatmapper-sensor-containers) for minimum supported platforms. Run the following command to start the Sensor Agent on the host. You can find the Deepfence API key under `Setting>User Management>API Key`. diff --git a/docs/docs/tips/automating-scans.md b/docs/docs/tips/automating-scans.md index e76db5a0e2..323905760f 100644 --- a/docs/docs/tips/automating-scans.md +++ b/docs/docs/tips/automating-scans.md @@ -15,7 +15,7 @@ ThreatMapper can scan your production platforms periodically, using the most up- ## Automating ThreatMapper -The results of automated scans are added to the **Vulnerability Scans** report, and can be raised through any configured [Notification](/docs/integrations) method. +The results of automated scans are added to the **Vulnerability Scans** report, and can be raised through any configured [Notification](/docs/v2.0/integrations) method. ThreatMapper presents a series of APIs that you can use to enumerate nodes and run scans: diff --git a/docs/versioned_docs/version-v1.5/architecture/cloudscanner.md b/docs/versioned_docs/version-v1.5/architecture/cloudscanner.md index 1e81a9b18a..ddad621e75 100644 --- a/docs/versioned_docs/version-v1.5/architecture/cloudscanner.md +++ b/docs/versioned_docs/version-v1.5/architecture/cloudscanner.md @@ -25,7 +25,7 @@ Each Cloud Scanner task runs in your cloud environment, gathering inventory and Cloud Scanner tasks are deployed using the appropriate Terraform module for each cloud, and are configured with the address and API key of your management console. They 'phone home' to your management console and take instructions on demand; they do not listen for remote connections or control. :::info -Refer to the Installation Documentation to [Learn how to install Cloud Scanner tasks](/docs/cloudscanner) +Refer to the Installation Documentation to [Learn how to install Cloud Scanner tasks](/docs/v1.5/cloudscanner) ::: diff --git a/docs/versioned_docs/version-v1.5/architecture/sensors.md b/docs/versioned_docs/version-v1.5/architecture/sensors.md index 65e7976e0f..7ab6394b1a 100644 --- a/docs/versioned_docs/version-v1.5/architecture/sensors.md +++ b/docs/versioned_docs/version-v1.5/architecture/sensors.md @@ -14,5 +14,5 @@ The sensors support the following production platforms: * **AWS Fargate** The sensor is deployed as a daemon service alongside each serverless instance. :::info -Refer to the Installation Documentation to [Learn how to install Sensor Agent containers](/docs/sensors) +Refer to the Installation Documentation to [Learn how to install Sensor Agent containers](/docs/v1.5/sensors) ::: \ No newline at end of file diff --git a/docs/versioned_docs/version-v1.5/cloudscanner/aws.md b/docs/versioned_docs/version-v1.5/cloudscanner/aws.md index 4c31e48c07..0ac18c16d4 100644 --- a/docs/versioned_docs/version-v1.5/cloudscanner/aws.md +++ b/docs/versioned_docs/version-v1.5/cloudscanner/aws.md @@ -102,7 +102,7 @@ Controls are grouped into **benchmarks**. Where multiple benchmarks are availabl When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v1.5/operations/compliance). :::tip Maximizing Coverage For maximum coverage, you can use both Cloud Scanner and local Sensor Agent compliance scans together. You could scan your AWS infrastructure using Cloud Scanner, and [scan selected VMs deployed within AWS](other) using the Sensor Agent. diff --git a/docs/versioned_docs/version-v1.5/cloudscanner/azure.md b/docs/versioned_docs/version-v1.5/cloudscanner/azure.md index 3f20a892b0..d9b3dd4bee 100644 --- a/docs/versioned_docs/version-v1.5/cloudscanner/azure.md +++ b/docs/versioned_docs/version-v1.5/cloudscanner/azure.md @@ -45,7 +45,7 @@ Controls are grouped into **benchmarks**. Where multiple benchmarks are availabl When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v1.5/operations/compliance). :::tip Maximizing Coverage For maximum coverage, you can use both Cloud Scanner and local Sensor Agent compliance scans together. You could scan your Azure infrastructure using Cloud Scanner, and [scan selected VMs deployed within Azure](other) using the Sensor Agent. diff --git a/docs/versioned_docs/version-v1.5/cloudscanner/gcp.md b/docs/versioned_docs/version-v1.5/cloudscanner/gcp.md index f753686d42..c640396e6b 100644 --- a/docs/versioned_docs/version-v1.5/cloudscanner/gcp.md +++ b/docs/versioned_docs/version-v1.5/cloudscanner/gcp.md @@ -70,7 +70,7 @@ Controls are grouped into **benchmarks**. Where multiple benchmarks are availabl When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v1.5/operations/compliance). :::tip Maximizing Coverage For maximum coverage, you can use both Cloud Scanner and local Sensor Agent compliance scans together. You could scan your GCP infrastructure using Cloud Scanner, and [scan selected VMs deployed within GCP](other) using the Sensor Agent. diff --git a/docs/versioned_docs/version-v1.5/cloudscanner/index.md b/docs/versioned_docs/version-v1.5/cloudscanner/index.md index e77634c98e..4e72755a0a 100644 --- a/docs/versioned_docs/version-v1.5/cloudscanner/index.md +++ b/docs/versioned_docs/version-v1.5/cloudscanner/index.md @@ -10,7 +10,7 @@ The Cloud Scanner task interacts with the local cloud APIs under the instruction ## Before You Begin -Review the architecture for compliance scanning, as described in [Architecture: Cloud Scanner task](/docs/architecture/cloudscanner). +Review the architecture for compliance scanning, as described in [Architecture: Cloud Scanner task](/docs/v1.5/architecture/cloudscanner). ## Configuring Cloud Posture Management diff --git a/docs/versioned_docs/version-v1.5/cloudscanner/other.md b/docs/versioned_docs/version-v1.5/cloudscanner/other.md index 10558a569a..f55a41ec73 100644 --- a/docs/versioned_docs/version-v1.5/cloudscanner/other.md +++ b/docs/versioned_docs/version-v1.5/cloudscanner/other.md @@ -6,7 +6,7 @@ title: Other Platforms ThreatMapper can perform compliance posture scanning on linux hosts and Kubernetes master and worker nodes. -Scanning is done directly, using a local [Sensor Agent](/docs/sensors) rather than by using the Cloud Scanner task employed by the cloud platform integrations. +Scanning is done directly, using a local [Sensor Agent](/docs/v1.5/sensors) rather than by using the Cloud Scanner task employed by the cloud platform integrations. ## What Compliance Scans are Performed? @@ -15,7 +15,7 @@ The sensor agent has direct visibility into the configuration of the base operat When you run a compliance scan, you can select which benchmarks you wish to measure against, and ThreatMapper will then evaluate the appropriate controls and present the results, by benchmark, once the scan has completed. -For full information, refer to [Operations: Compliance Scanning](/docs/operations/compliance). +For full information, refer to [Operations: Compliance Scanning](/docs/v1.5/operations/compliance). :::tip Maximizing Coverage diff --git a/docs/versioned_docs/version-v1.5/console/index.md b/docs/versioned_docs/version-v1.5/console/index.md index a3346ce5fc..5ebdda76b9 100644 --- a/docs/versioned_docs/version-v1.5/console/index.md +++ b/docs/versioned_docs/version-v1.5/console/index.md @@ -8,9 +8,9 @@ The ThreatMapper Management Console ("Console") is a standalone application, imp ## Before You Begin -Review the architecture for the Management Console, as described in [Architecture: Management Console](/docs/architecture/console). +Review the architecture for the Management Console, as described in [Architecture: Management Console](/docs/v1.5/architecture/console). -Review the requirements for the Management Console, as described in [System Requirements](/docs/console/requirements). +Review the requirements for the Management Console, as described in [System Requirements](/docs/v1.5/console/requirements). ## Installing the Management Console diff --git a/docs/versioned_docs/version-v1.5/developers/build.md b/docs/versioned_docs/version-v1.5/developers/build.md index 04ef0fff55..9c12cd2311 100644 --- a/docs/versioned_docs/version-v1.5/developers/build.md +++ b/docs/versioned_docs/version-v1.5/developers/build.md @@ -34,7 +34,7 @@ cd ThreatMapper/deployment-scripts docker-compose -f docker-compose.yml up --detach ``` -Once started, you can point a web browser at `https://--IP-ADDRESS---/` to register a first user on the Deepfence Management Console. See [Initial Configuration](/docs/console/initial-configuration) for more information. +Once started, you can point a web browser at `https://--IP-ADDRESS---/` to register a first user on the Deepfence Management Console. See [Initial Configuration](/docs/v1.5/console/initial-configuration) for more information. To stop the Deepfence Management Console: diff --git a/docs/versioned_docs/version-v1.5/developers/deploy-agent.md b/docs/versioned_docs/version-v1.5/developers/deploy-agent.md index 89197770e7..e1bdde5268 100644 --- a/docs/versioned_docs/version-v1.5/developers/deploy-agent.md +++ b/docs/versioned_docs/version-v1.5/developers/deploy-agent.md @@ -4,13 +4,13 @@ title: Deploy Sensors # Deploy custom ThreatMapper Sensor Agents -You should first [build the management console and agents](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/sensors/docker), [Kubernetes](/docs/sensors/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. +You should first [build the management console and agents](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/v1.5/sensors/docker), [Kubernetes](/docs/v1.5/sensors/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. ## Installing and Running the Sensor Agents on a Docker Host :::tip -Refer to the [Docker Installation Instructions](/docs/sensors/docker) along with the modifications below. +Refer to the [Docker Installation Instructions](/docs/v1.5/sensors/docker) along with the modifications below. ::: Execute the following command to install and start the sensors: @@ -30,7 +30,7 @@ docker run -dit --cpus=".2" --name=deepfence-agent --restart on-failure --pid=ho ## Installing and Running the Sensor Agents in a Kubernetes Cluster :::tip -Refer to the [Kubernetes Installation Instructions](/docs/sensors/kubernetes) along with the modifications below. +Refer to the [Kubernetes Installation Instructions](/docs/v1.5/sensors/kubernetes) along with the modifications below. ::: You can use these instructions for helm-based installations in standalone and hosted Kubernetes clusters diff --git a/docs/versioned_docs/version-v1.5/developers/deploy-console.md b/docs/versioned_docs/version-v1.5/developers/deploy-console.md index 3cb1dafc58..e7496f3d0a 100644 --- a/docs/versioned_docs/version-v1.5/developers/deploy-console.md +++ b/docs/versioned_docs/version-v1.5/developers/deploy-console.md @@ -4,14 +4,14 @@ title: Deploy Console # Deploy a custom ThreatMapper Console -You should first [build the management console](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/console/docker), [Kubernetes](/docs/console/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. +You should first [build the management console](build) and push the images to a suitable repository. You can then adapt the standard installation instructions ([Docker](/docs/v1.5/console/docker), [Kubernetes](/docs/v1.5/console/kubernetes)) to refer to your custom images rather than the Deepfence-provided ones. ## Installing and Running the Management Console on a Docker Host :::tip -Refer to the [Docker Installation Instructions](/docs/console/docker) along with the modifications below. +Refer to the [Docker Installation Instructions](/docs/v1.5/console/docker) along with the modifications below. ::: 1. Download the file [docker-compose.yml](https://github.com/deepfence/ThreatMapper/blob/release-1.5/deployment-scripts/docker-compose.yml) to the system that will host the Console @@ -31,12 +31,12 @@ Refer to the [Docker Installation Instructions](/docs/console/docker) along with ## Installing and Running the Management Console in a Kubernetes Cluster :::tip -Refer to the [Kubernetes Installation Instructions](/docs/console/kubernetes) along with the modifications below. +Refer to the [Kubernetes Installation Instructions](/docs/v1.5/console/kubernetes) along with the modifications below. ::: 1. Prepare the cluster, installing the storage driver and metrics service - Follow the instructions to install the OpenEBS storage and metrics server: [Installation Instructions](/docs/console/kubernetes) + Follow the instructions to install the OpenEBS storage and metrics server: [Installation Instructions](/docs/v1.5/console/kubernetes) 2. Install your Management Console diff --git a/docs/versioned_docs/version-v1.5/integrations/index.md b/docs/versioned_docs/version-v1.5/integrations/index.md index d2f71ba861..4a2a55f77a 100644 --- a/docs/versioned_docs/version-v1.5/integrations/index.md +++ b/docs/versioned_docs/version-v1.5/integrations/index.md @@ -4,7 +4,7 @@ title: Integrations # Integrations -You can integrate ThreatMapper with a variety of notification services. Any time a new vulnerability is detected (for example, during [CI](/docs/operations/scanning-ci) or an [automated scan](/docs/operations/scanning)), ThreatMapper will submit the details to the configured notification services. +You can integrate ThreatMapper with a variety of notification services. Any time a new vulnerability is detected (for example, during [CI](/docs/v1.5/operations/scanning-ci) or an [automated scan](/docs/v1.5/operations/scanning)), ThreatMapper will submit the details to the configured notification services. | ![Notifications](../img/notifications-2.jpg) | | :--: | diff --git a/docs/versioned_docs/version-v1.5/operations/compliance.md b/docs/versioned_docs/version-v1.5/operations/compliance.md index 13ae475589..3b89aef329 100644 --- a/docs/versioned_docs/version-v1.5/operations/compliance.md +++ b/docs/versioned_docs/version-v1.5/operations/compliance.md @@ -38,13 +38,13 @@ The benchmarks available vary by cloud provider: Begin on the **Posture** page in the ThreatMapper console. -Select a cloud instance that you have [configured previously](/docs/cloudscanner/). You may have several instances of a given cloud type: +Select a cloud instance that you have [configured previously](/docs/v1.5/cloudscanner/). You may have several instances of a given cloud type: | ![Cloud Compliance Scan - Select](../img/compliance-scan-1.jpg) | | :--: | | Select target for Cloud Compliance Scan | -If you want to scan a host (Linux host or Kubernetes master or slave node), ensure that the [threatmapper sensor](/docs/sensors) is deployed on that host. +If you want to scan a host (Linux host or Kubernetes master or slave node), ensure that the [threatmapper sensor](/docs/v1.5/sensors) is deployed on that host. Select the compliance benchmarks you wish to run on the target cloud instance or host: diff --git a/docs/versioned_docs/version-v1.5/sensors/docker.md b/docs/versioned_docs/version-v1.5/sensors/docker.md index 101f0d89a2..17327fa211 100644 --- a/docs/versioned_docs/version-v1.5/sensors/docker.md +++ b/docs/versioned_docs/version-v1.5/sensors/docker.md @@ -6,7 +6,7 @@ title: Docker On a Linux-based Docker host, the ThreatMapper agents are deployed as a lightweight container. -Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/architecture#threatmapper-sensor-containers) for minimum supported platforms. +Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/v1.5/architecture#threatmapper-sensor-containers) for minimum supported platforms. For Windows Server hosts, experimental support exists, but it is not suitable for production use. diff --git a/docs/versioned_docs/version-v1.5/sensors/index.md b/docs/versioned_docs/version-v1.5/sensors/index.md index 6a69491d13..6093d373d2 100644 --- a/docs/versioned_docs/version-v1.5/sensors/index.md +++ b/docs/versioned_docs/version-v1.5/sensors/index.md @@ -10,11 +10,11 @@ A single ThreatMapper Console can manage multiple workload types, and on-premise ## Before You Begin -Before you install the Sensors, obtain the Management Console URL and API key as described in the [Initial Configuration](/docs/console/initial-configuration). +Before you install the Sensors, obtain the Management Console URL and API key as described in the [Initial Configuration](/docs/v1.5/console/initial-configuration). You should take care to install the sensor version that matches your Management Console version, as compatibility across versions is not guaranteed. -Review the architecture for the Sensor Agent, as described in [Architecture: Sensor Agent container](/docs/architecture/sensors). +Review the architecture for the Sensor Agent, as described in [Architecture: Sensor Agent container](/docs/v1.5/architecture/sensors). ## System Requirements diff --git a/docs/versioned_docs/version-v1.5/sensors/linux-host.md b/docs/versioned_docs/version-v1.5/sensors/linux-host.md index ef4ea503d4..20ceaae5bf 100644 --- a/docs/versioned_docs/version-v1.5/sensors/linux-host.md +++ b/docs/versioned_docs/version-v1.5/sensors/linux-host.md @@ -8,7 +8,7 @@ On a Linux-based bare-metal or virtual machine workload, the ThreatMapper sensor ## ThreatMapper Sensor Agents -Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/architecture#threatmapper-sensor-containers) for minimum supported platforms. +Install a docker runtime on the Linux host. Refer to the [Prerequisites for the Sensor Agents](/docs/v1.5/architecture#threatmapper-sensor-containers) for minimum supported platforms. Run the following command to start the Sensor Agent on the host. You can find the Deepfence API key under `Setting>User Management>API Key`. diff --git a/docs/versioned_docs/version-v1.5/tips/automating-scans.md b/docs/versioned_docs/version-v1.5/tips/automating-scans.md index d8b0b34312..994e0ac4eb 100644 --- a/docs/versioned_docs/version-v1.5/tips/automating-scans.md +++ b/docs/versioned_docs/version-v1.5/tips/automating-scans.md @@ -15,7 +15,7 @@ ThreatMapper can scan your production platforms periodically, using the most up- ## Automating ThreatMapper -The results of automated scans are added to the **Vulnerability Scans** report, and can be raised through any configured [Notification](/docs/integrations) method. +The results of automated scans are added to the **Vulnerability Scans** report, and can be raised through any configured [Notification](/docs/v1.5/integrations) method. ThreatMapper presents a series of APIs that you can use to enumerate nodes and run scans: diff --git a/docs/vulnerability_feeds/listing.json b/docs/vulnerability_feeds/listing.json index 2811a04b6d..2554a388a9 100644 --- a/docs/vulnerability_feeds/listing.json +++ b/docs/vulnerability_feeds/listing.json @@ -2,54 +2,54 @@ "available": { "3": [ { - "built": "2023-09-23T00:58:11.841245121Z", + "built": "2023-09-30T00:58:58.300398362Z", "version": 3, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-09-23_00-49-20/threatintel-vuln-v3-2023-09-23_00-49-20.tar.gz", - "checksum": "93e215f80453a1c20940e4c2e5122bbed6be7e2689a8a6b7330bb451935b8205" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-09-30_00-49-15/threatintel-vuln-v3-2023-09-30_00-49-15.tar.gz", + "checksum": "7b7b7f60d3df85fc7ba8a8edcefa35b84a28d9e99c42eebe4f2a933b634786cc" }, { - "built": "2023-09-24T01:02:42.100811501Z", + "built": "2023-10-01T00:59:41.028222278Z", "version": 3, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-09-24_00-49-44/threatintel-vuln-v3-2023-09-24_00-49-44.tar.gz", - "checksum": "02d0ed2b0b40b10f10024b43921018cf6c4596306e40ccfd22bf47f39124b825" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-10-01_00-49-49/threatintel-vuln-v3-2023-10-01_00-49-49.tar.gz", + "checksum": "00bb239bbf7d224685ae7048f37882bd314b4b92116c25d27a5668b95e76d94f" }, { - "built": "2023-09-25T00:59:38.084109667Z", + "built": "2023-10-02T01:04:03.449260044Z", "version": 3, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-09-25_00-49-26/threatintel-vuln-v3-2023-09-25_00-49-26.tar.gz", - "checksum": "7afcfae8a64e98ffcd9376d27e1801ad1d3b976717ed3d6fa51a6ff47d12db9f" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-10-02_00-49-46/threatintel-vuln-v3-2023-10-02_00-49-46.tar.gz", + "checksum": "ce4b9f241cba4aa7d3650d60201d911d2aca2acc3732c9bd404479f5ac7ee255" }, { - "built": "2023-09-26T00:59:01.051389835Z", + "built": "2023-10-03T01:02:32.540646995Z", "version": 3, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-09-26_00-49-29/threatintel-vuln-v3-2023-09-26_00-49-29.tar.gz", - "checksum": "33785c4c16d4694baa16fb06480a34967666f29617d178a1dba7a2d8e01838d8" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2023-10-03_00-49-36/threatintel-vuln-v3-2023-10-03_00-49-36.tar.gz", + "checksum": "f595d7a1c4f84fba3c39c22d904ee6b63f30a511c88c0ea36d7867aa4f5f9fbd" } ], "5": [ { - "built": "2023-09-23T00:26:38.982019158Z", + "built": "2023-09-30T00:27:11.212831688Z", "version": 5, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-09-23_00-15-42/threatintel-vuln-v5-2023-09-23_00-15-42.tar.gz", - "checksum": "891a78743fa7532100ebdf621df106815c9e172d1e0b653b29a2d187377e3773" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-09-30_00-16-00/threatintel-vuln-v5-2023-09-30_00-16-00.tar.gz", + "checksum": "f5c42773e3482e8946a62b345e36c2a228be3ff5587adecc6c41aadf1208bdd2" }, { - "built": "2023-09-24T00:30:43.073275438Z", + "built": "2023-10-01T00:32:12.77025855Z", "version": 5, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-09-24_00-17-41/threatintel-vuln-v5-2023-09-24_00-17-41.tar.gz", - "checksum": "2edec6f1f47f398eb7b43f07d9fa36334032b63c74c8fdf13e4eee967f699997" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-10-01_00-19-31/threatintel-vuln-v5-2023-10-01_00-19-31.tar.gz", + "checksum": "5870c1765c57501415388adbdf425746e668b78a635df9e4548d26e07c3ff8cc" }, { - "built": "2023-09-25T00:28:28.606668527Z", + "built": "2023-10-02T00:29:42.361375008Z", "version": 5, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-09-25_00-16-47/threatintel-vuln-v5-2023-09-25_00-16-47.tar.gz", - "checksum": "dcb044045995e809c7d48ee6eb5ab6b459b94bd1a52f17377634b90a0fc47d10" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-10-02_00-17-17/threatintel-vuln-v5-2023-10-02_00-17-17.tar.gz", + "checksum": "f0f05d019fc138f42d90ed4250295419234789fb2179be653cfef19d976352d5" }, { - "built": "2023-09-26T00:29:05.402605991Z", + "built": "2023-10-03T00:28:45.557510582Z", "version": 5, - "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-09-26_00-16-34/threatintel-vuln-v5-2023-09-26_00-16-34.tar.gz", - "checksum": "db1db516cef61d2b8f12ac220a8cde654b0c1b8e4286dd75c909aa6b56f6212e" + "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2023-10-03_00-16-45/threatintel-vuln-v5-2023-10-03_00-16-45.tar.gz", + "checksum": "108bdccd7eff6b504457749a5875adcf6e6117617f520a527f8bd99249b4a739" } ] } diff --git a/golang_deepfence_sdk b/golang_deepfence_sdk index b19457df35..1713a043ef 160000 --- a/golang_deepfence_sdk +++ b/golang_deepfence_sdk @@ -1 +1 @@ -Subproject commit b19457df35dc1968f283a40a856573172dc45491 +Subproject commit 1713a043efe5bffa1b0f8ac8b7236056bc48b54a diff --git a/images/threatstryker.png b/images/threatstryker.png new file mode 100644 index 0000000000..d1cd7feffd Binary files /dev/null and b/images/threatstryker.png differ