diff --git a/deepfence_agent/Dockerfile.cloud-agent b/deepfence_agent/Dockerfile.cloud-agent
index 7ee0397a3e..22baac87ad 100644
--- a/deepfence_agent/Dockerfile.cloud-agent
+++ b/deepfence_agent/Dockerfile.cloud-agent
@@ -29,7 +29,11 @@ WORKDIR /opt/steampipe
USER deepfence
-ENV DF_INSTALL_DIR=/home/deepfence
+ENV DF_INSTALL_DIR=/home/deepfence \
+ STEAMPIPE_AWS_PLUGIN_VERSION=0.118.1 \
+ STEAMPIPE_GCP_PLUGIN_VERSION=0.43.0 \
+ STEAMPIPE_AZURE_PLUGIN_VERSION=0.49.0 \
+ STEAMPIPE_AZURE_AD_PLUGIN_VERSION=0.12.0
COPY supervisord-cloud.conf /home/deepfence/supervisord.conf
COPY --from=steampipe /usr/local/bin/steampipe /usr/local/bin/steampipe
@@ -37,7 +41,7 @@ COPY --from=steampipe /usr/local/bin/steampipe /usr/local/bin/steampipe
RUN steampipe service start \
&& steampipe plugin install steampipe \
# plugin version should be in sync with Deepfence fork https://github.com/deepfence/steampipe-plugin-aws
- && steampipe plugin install aws@0.118.1 gcp@0.43.0 azure@0.49.0 azuread@0.12.0 \
+ && steampipe plugin install aws@${STEAMPIPE_AWS_PLUGIN_VERSION} gcp@${STEAMPIPE_GCP_PLUGIN_VERSION} azure@${STEAMPIPE_AZURE_PLUGIN_VERSION} azuread@${STEAMPIPE_AZURE_AD_PLUGIN_VERSION} \
&& git clone https://github.com/turbot/steampipe-mod-aws-compliance.git --branch v0.79 --depth 1 \
&& git clone https://github.com/turbot/steampipe-mod-gcp-compliance.git --branch v0.21 --depth 1 \
&& git clone https://github.com/turbot/steampipe-mod-azure-compliance.git --branch v0.35 --depth 1 \
@@ -56,10 +60,10 @@ ENV PUBLISH_CLOUD_RESOURCES_INTERVAL_MINUTES=5 \
EXPOSE 8080
-COPY --from=steampipe /usr/local/bin/steampipe-plugin-aws.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/aws@latest/steampipe-plugin-aws.plugin
-COPY --from=steampipe /usr/local/bin/steampipe-plugin-gcp.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/gcp@latest/steampipe-plugin-gcp.plugin
-COPY --from=steampipe /usr/local/bin/steampipe-plugin-azure.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/azure@latest/steampipe-plugin-azure.plugin
-COPY --from=steampipe /usr/local/bin/steampipe-plugin-azuread.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/azuread@latest/steampipe-plugin-azuread.plugin
+COPY --from=steampipe /usr/local/bin/steampipe-plugin-aws.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/aws@${STEAMPIPE_AWS_PLUGIN_VERSION}/steampipe-plugin-aws.plugin
+COPY --from=steampipe /usr/local/bin/steampipe-plugin-gcp.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/gcp@${STEAMPIPE_GCP_PLUGIN_VERSION}/steampipe-plugin-gcp.plugin
+COPY --from=steampipe /usr/local/bin/steampipe-plugin-azure.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/azure@${STEAMPIPE_AZURE_PLUGIN_VERSION}/steampipe-plugin-azure.plugin
+COPY --from=steampipe /usr/local/bin/steampipe-plugin-azuread.plugin /home/deepfence/.steampipe/plugins/hub.steampipe.io/plugins/turbot/azuread@${STEAMPIPE_AZURE_AD_PLUGIN_VERSION}/steampipe-plugin-azuread.plugin
COPY plugins/cloud-scanner/cloud_scanner /home/deepfence/bin/cloud_scanner
diff --git a/deepfence_agent/plugins/YaraHunter b/deepfence_agent/plugins/YaraHunter
index 8e4d161ce8..3cef40ff4f 160000
--- a/deepfence_agent/plugins/YaraHunter
+++ b/deepfence_agent/plugins/YaraHunter
@@ -1 +1 @@
-Subproject commit 8e4d161ce812f31a0afd6d1a0d4f8c5440845f47
+Subproject commit 3cef40ff4f7408bca29613b304e277451bd3da4b
diff --git a/deepfence_agent/plugins/cloud-scanner b/deepfence_agent/plugins/cloud-scanner
index c25aedcfdb..402d361b8f 160000
--- a/deepfence_agent/plugins/cloud-scanner
+++ b/deepfence_agent/plugins/cloud-scanner
@@ -1 +1 @@
-Subproject commit c25aedcfdbc09a9deae9f40cdea9367a6b6ca658
+Subproject commit 402d361b8f543cc008ceb31bf44cc0fa6fcead16
diff --git a/deepfence_agent/plugins/yara-rules b/deepfence_agent/plugins/yara-rules
index 8217b51893..bc374a2ad6 160000
--- a/deepfence_agent/plugins/yara-rules
+++ b/deepfence_agent/plugins/yara-rules
@@ -1 +1 @@
-Subproject commit 8217b518934b556ee7f56e6e5fc3e05be8c8d9fa
+Subproject commit bc374a2ad6b15aa70894159b670bef03f2d482e7
diff --git a/deepfence_bootstrapper/router/cloud_scanner.go b/deepfence_bootstrapper/router/cloud_scanner.go
index a7166d927c..f0455c1000 100644
--- a/deepfence_bootstrapper/router/cloud_scanner.go
+++ b/deepfence_bootstrapper/router/cloud_scanner.go
@@ -89,7 +89,7 @@ func RefreshResources(req ctl.RefreshResourcesRequest) error {
return nil
}
-func GetCloudScannerJobCount() int32 {
+func GetCloudScannerJobCount(action ctl.ActionID) int32 {
conn, err := net.Dial("unix", CloudScannerSocketPath)
if err != nil {
log.Error().Err(err).Msgf("GetCloudScannerJobCount: error in creating cloud compliance scanner client with socket %s", CloudScannerSocketPath)
@@ -98,7 +98,7 @@ func GetCloudScannerJobCount() int32 {
defer conn.Close()
jobCountReq := map[string]interface{}{
- "action": ctl.CloudScannerJobCount,
+ "action": action,
}
jobCountReqBytes, err := json.Marshal(jobCountReq)
if err != nil {
@@ -128,49 +128,3 @@ func GetCloudScannerJobCount() int32 {
return jobCount
}
}
-
-func GetCloudNodeID() (string, error) {
- cloudNodeID := ""
- conn, err := net.Dial("unix", CloudScannerSocketPath)
- if err != nil {
- log.Error().Err(err).Msgf("Error creating cloud scanner client with socket %s", CloudScannerSocketPath)
- return cloudNodeID, err
- }
- defer conn.Close()
- reqMap := make(map[string]interface{})
- reqMap["GetCloudNodeID"] = true
- cloudNodeIDReq := map[string]interface{}{
- "args": reqMap,
- }
-
- cloudNodeIDReqBytes, err := json.Marshal(cloudNodeIDReq)
- if err != nil {
- log.Error().Err(err).Msg("Error in converting request into valid json")
- return cloudNodeID, err
- }
-
- _, err = conn.Write(cloudNodeIDReqBytes)
- if err != nil {
- log.Error().Err(err).Msgf("Error in writing data to unix socket %s", CloudScannerSocketPath)
- return cloudNodeID, err
- }
-
- responseTimeout := 10 * time.Second
- deadline := time.Now().Add(responseTimeout)
- buf := make([]byte, 1024)
- for {
- conn.SetReadDeadline(deadline)
- n, err := conn.Read(buf[:])
- if err != nil {
- log.Error().Err(err).Msg("Error in read")
- return cloudNodeID, err
- }
-
- count, err := fmt.Sscan(string(buf[0:n]), &cloudNodeID)
- if err != nil || count != 1 {
- return cloudNodeID, err
- }
- break
- }
- return cloudNodeID, err
-}
diff --git a/deepfence_bootstrapper/router/openapi_client_controls.go b/deepfence_bootstrapper/router/openapi_client_controls.go
index 9832d50f9f..235074e4af 100644
--- a/deepfence_bootstrapper/router/openapi_client_controls.go
+++ b/deepfence_bootstrapper/router/openapi_client_controls.go
@@ -119,25 +119,29 @@ func (ct *OpenapiClient) StartControlsWatching(nodeID string,
const (
MaxAgentWorkload = 2
- MaxCloudAgentWorkload = 1
+ MaxCloudAgentWorkload = 2
)
func GetScannersWorkloads(nodeType string) int32 {
- res := int32(0)
- var secret, malware, vuln, cloud int32
if nodeType == ctl.CLOUD_AGENT {
- cloud = GetCloudScannerJobCount()
+ var cloudPostureScan, cloudResourceRefreshCount int32
+
+ cloudPostureScan = GetCloudScannerJobCount(ctl.CloudScannerJobCount)
+ cloudResourceRefreshCount = GetCloudScannerJobCount(ctl.CloudScannerResourceRefreshCount)
+
+ log.Info().Msgf("workloads = cloud posture: %d, cloud resource refresh: %d", cloudPostureScan, cloudResourceRefreshCount)
+ return cloudPostureScan + cloudResourceRefreshCount
} else {
+ var secret, malware, vuln int32
+
secret = GetSecretScannerJobCount()
malware = GetMalwareScannerJobCount()
vuln = GetPackageScannerJobCount()
- }
- //TODO: Add more scanners workload
- log.Info().Msgf("workloads = vuln: %d, secret: %d, malware: %d, cloud: %d",
- vuln, secret, malware, cloud)
- res = secret + malware + vuln + cloud
- return res
+ //TODO: Add more scanners workload
+ log.Info().Msgf("workloads = vuln: %d, secret: %d, malware: %d", vuln, secret, malware)
+ return secret + malware + vuln
+ }
}
var upgrade atomic.Bool
diff --git a/deepfence_frontend/apps/dashboard/api-spec.json b/deepfence_frontend/apps/dashboard/api-spec.json
index 503677fd85..b1bd59b3d3 100644
--- a/deepfence_frontend/apps/dashboard/api-spec.json
+++ b/deepfence_frontend/apps/dashboard/api-spec.json
@@ -13616,9 +13616,11 @@
"node_id": { "type": "string" },
"node_name": { "type": "string" },
"refresh_message": { "type": "string" },
- "refresh_status": {
- "enum": ["STARTING", "IN_PROGRESS", "ERROR", "COMPLETE"],
- "type": "string"
+ "refresh_status": { "type": "string" },
+ "refresh_status_map": {
+ "type": "object",
+ "additionalProperties": { "type": "integer" },
+ "nullable": true
},
"scan_status_map": {
"type": "object",
@@ -14587,7 +14589,8 @@
"config": { "type": "object", "additionalProperties": {}, "nullable": true },
"filters": { "$ref": "#/components/schemas/ModelIntegrationFilters" },
"integration_type": { "type": "string" },
- "notification_type": { "type": "string" }
+ "notification_type": { "type": "string" },
+ "send_summary": { "type": "boolean" }
}
},
"ModelIntegrationFilters": {
@@ -14625,7 +14628,8 @@
"filters": { "$ref": "#/components/schemas/ModelIntegrationFilters" },
"id": { "type": "integer" },
"integration_type": { "type": "string" },
- "notification_type": { "type": "string" }
+ "notification_type": { "type": "string" },
+ "send_summary": { "type": "boolean" }
}
},
"ModelInviteUserRequest": {
diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelCloudNodeAccountInfo.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelCloudNodeAccountInfo.ts
index 29775ee60e..e493165119 100644
--- a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelCloudNodeAccountInfo.ts
+++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelCloudNodeAccountInfo.ts
@@ -84,7 +84,13 @@ export interface ModelCloudNodeAccountInfo {
* @type {string}
* @memberof ModelCloudNodeAccountInfo
*/
- refresh_status?: ModelCloudNodeAccountInfoRefreshStatusEnum;
+ refresh_status?: string;
+ /**
+ *
+ * @type {{ [key: string]: number; }}
+ * @memberof ModelCloudNodeAccountInfo
+ */
+ refresh_status_map?: { [key: string]: number; } | null;
/**
*
* @type {{ [key: string]: number; }}
@@ -113,17 +119,6 @@ export const ModelCloudNodeAccountInfoCloudProviderEnum = {
} as const;
export type ModelCloudNodeAccountInfoCloudProviderEnum = typeof ModelCloudNodeAccountInfoCloudProviderEnum[keyof typeof ModelCloudNodeAccountInfoCloudProviderEnum];
-/**
- * @export
- */
-export const ModelCloudNodeAccountInfoRefreshStatusEnum = {
- Starting: 'STARTING',
- InProgress: 'IN_PROGRESS',
- Error: 'ERROR',
- Complete: 'COMPLETE'
-} as const;
-export type ModelCloudNodeAccountInfoRefreshStatusEnum = typeof ModelCloudNodeAccountInfoRefreshStatusEnum[keyof typeof ModelCloudNodeAccountInfoRefreshStatusEnum];
-
/**
* Check if a given object implements the ModelCloudNodeAccountInfo interface.
@@ -155,6 +150,7 @@ export function ModelCloudNodeAccountInfoFromJSONTyped(json: any, ignoreDiscrimi
'node_name': !exists(json, 'node_name') ? undefined : json['node_name'],
'refresh_message': !exists(json, 'refresh_message') ? undefined : json['refresh_message'],
'refresh_status': !exists(json, 'refresh_status') ? undefined : json['refresh_status'],
+ 'refresh_status_map': !exists(json, 'refresh_status_map') ? undefined : json['refresh_status_map'],
'scan_status_map': !exists(json, 'scan_status_map') ? undefined : json['scan_status_map'],
'version': !exists(json, 'version') ? undefined : json['version'],
};
@@ -180,6 +176,7 @@ export function ModelCloudNodeAccountInfoToJSON(value?: ModelCloudNodeAccountInf
'node_name': value.node_name,
'refresh_message': value.refresh_message,
'refresh_status': value.refresh_status,
+ 'refresh_status_map': value.refresh_status_map,
'scan_status_map': value.scan_status_map,
'version': value.version,
};
diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationAddReq.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationAddReq.ts
index f5211dd456..7a68b9abfa 100644
--- a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationAddReq.ts
+++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationAddReq.ts
@@ -50,6 +50,12 @@ export interface ModelIntegrationAddReq {
* @memberof ModelIntegrationAddReq
*/
notification_type: string;
+ /**
+ *
+ * @type {boolean}
+ * @memberof ModelIntegrationAddReq
+ */
+ send_summary?: boolean;
}
/**
@@ -77,6 +83,7 @@ export function ModelIntegrationAddReqFromJSONTyped(json: any, ignoreDiscriminat
'filters': !exists(json, 'filters') ? undefined : ModelIntegrationFiltersFromJSON(json['filters']),
'integration_type': json['integration_type'],
'notification_type': json['notification_type'],
+ 'send_summary': !exists(json, 'send_summary') ? undefined : json['send_summary'],
};
}
@@ -93,6 +100,7 @@ export function ModelIntegrationAddReqToJSON(value?: ModelIntegrationAddReq | nu
'filters': ModelIntegrationFiltersToJSON(value.filters),
'integration_type': value.integration_type,
'notification_type': value.notification_type,
+ 'send_summary': value.send_summary,
};
}
diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationUpdateReq.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationUpdateReq.ts
index d6406f84a5..c000619da9 100644
--- a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationUpdateReq.ts
+++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelIntegrationUpdateReq.ts
@@ -56,6 +56,12 @@ export interface ModelIntegrationUpdateReq {
* @memberof ModelIntegrationUpdateReq
*/
notification_type?: string;
+ /**
+ *
+ * @type {boolean}
+ * @memberof ModelIntegrationUpdateReq
+ */
+ send_summary?: boolean;
}
/**
@@ -82,6 +88,7 @@ export function ModelIntegrationUpdateReqFromJSONTyped(json: any, ignoreDiscrimi
'id': !exists(json, 'id') ? undefined : json['id'],
'integration_type': !exists(json, 'integration_type') ? undefined : json['integration_type'],
'notification_type': !exists(json, 'notification_type') ? undefined : json['notification_type'],
+ 'send_summary': !exists(json, 'send_summary') ? undefined : json['send_summary'],
};
}
@@ -99,6 +106,7 @@ export function ModelIntegrationUpdateReqToJSON(value?: ModelIntegrationUpdateRe
'id': value.id,
'integration_type': value.integration_type,
'notification_type': value.notification_type,
+ 'send_summary': value.send_summary,
};
}
diff --git a/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx b/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx
index 690ab2128e..b9ec63aef9 100644
--- a/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx
+++ b/deepfence_frontend/apps/dashboard/src/components/ScanStatusBadge.tsx
@@ -1,6 +1,6 @@
import { capitalize } from 'lodash-es';
import { cn } from 'tailwind-preset';
-import { CircleSpinner } from 'ui-components';
+import { CircleSpinner, Tooltip } from 'ui-components';
import {
ErrorIcon,
@@ -22,10 +22,12 @@ export const ScanStatusBadge = ({
status,
className,
justIcon = false,
+ errorMessage,
}: {
status: string;
className?: string;
justIcon?: boolean;
+ errorMessage?: string;
}) => {
const wrapperClassName = cn(
'flex items-center gap-1.5 text-text-text-and-icon text-p4a',
@@ -49,10 +51,23 @@ export const ScanStatusBadge = ({
} else if (isScanFailed(status)) {
return (
-
-
-
- {!justIcon ?
: null}
+ {errorMessage ? (
+
+
+
+
+
+ {!justIcon ? : null}
+
+
+ ) : (
+ <>
+
+
+
+ {!justIcon ?
: null}
+ >
+ )}
);
} else if (isNeverScanned(status)) {
diff --git a/deepfence_frontend/apps/dashboard/src/features/integrations/components/integration-form/NotificationTypeField.tsx b/deepfence_frontend/apps/dashboard/src/features/integrations/components/integration-form/NotificationTypeField.tsx
index b77917ca28..9ae75b700e 100644
--- a/deepfence_frontend/apps/dashboard/src/features/integrations/components/integration-form/NotificationTypeField.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/integrations/components/integration-form/NotificationTypeField.tsx
@@ -1,14 +1,16 @@
import { useState } from 'react';
import { useParams } from 'react-router-dom';
-import { Listbox, ListboxOption } from 'ui-components';
+import { Checkbox, Listbox, ListboxOption, Tooltip } from 'ui-components';
import { ModelIntegrationListResp } from '@/api/generated';
+import { InfoStandardIcon } from '@/components/icons/common/InfoStandard';
import { ScanTypeEnum } from '@/types/common';
import { AdvancedFilters } from './AdvancedFilter';
import { FieldSelection } from './FieldSelection';
import { TextInputType } from './TextInputType';
import {
+ canSendScanSummary,
getNotificationPrettyName,
IntegrationType,
isCloudComplianceNotification,
@@ -18,6 +20,24 @@ import {
isVulnerabilityNotification,
} from './utils';
+const SendScanSummaryCheckbox = ({ sendSummaryOnly }: { sendSummaryOnly: boolean }) => {
+ const [checked, setChecked] = useState(sendSummaryOnly);
+ return (
+
+
setChecked(check)}
+ />
+
+
+
+
+
+
+ );
+};
export const NotificationTypeField = ({
fieldErrors,
defaultNotificationType,
@@ -80,6 +100,10 @@ export const NotificationTypeField = ({
) : null} */}
+ {canSendScanSummary(notificationType, integrationType) ? (
+
+ ) : null}
+
{isCloudComplianceNotification(notificationType) &&
integrationType !== IntegrationType.s3 && (
{
}
return notificationType;
};
+
+export const canSendScanSummary = (notificationType: string, integrationType: string) => {
+ return (
+ ((integrationType === IntegrationType.slack ||
+ integrationType === IntegrationType.microsoftTeams) &&
+ ['Secret', 'Vulnerability', 'Malware'].includes(notificationType)) ||
+ isComplianceNotification(notificationType) ||
+ isCloudComplianceNotification(notificationType)
+ );
+};
diff --git a/deepfence_frontend/apps/dashboard/src/features/integrations/components/report-form/AdvanceFilter.tsx b/deepfence_frontend/apps/dashboard/src/features/integrations/components/report-form/AdvanceFilter.tsx
index 7639d34bce..7887d4ab84 100644
--- a/deepfence_frontend/apps/dashboard/src/features/integrations/components/report-form/AdvanceFilter.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/integrations/components/report-form/AdvanceFilter.tsx
@@ -1,4 +1,3 @@
-import { upperCase } from 'lodash-es';
import { useMemo, useState } from 'react';
import { Listbox, ListboxOption } from 'ui-components';
diff --git a/deepfence_frontend/apps/dashboard/src/features/integrations/pages/IntegrationAdd.tsx b/deepfence_frontend/apps/dashboard/src/features/integrations/pages/IntegrationAdd.tsx
index d578d8bf5d..91b69f8c9a 100644
--- a/deepfence_frontend/apps/dashboard/src/features/integrations/pages/IntegrationAdd.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/integrations/pages/IntegrationAdd.tsx
@@ -316,6 +316,7 @@ const action = async ({ request, params }: ActionFunctionArgs): Promise {
- return (
-
-
Error
- {errorMessage ? (
-
-
-
-
-
- ) : (
-
-
-
- )}
-
- );
-};
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 dfb8315fe0..210298d737 100644
--- a/deepfence_frontend/apps/dashboard/src/features/postures/pages/Accounts.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/postures/pages/Accounts.tsx
@@ -1,6 +1,6 @@
import { useSuspenseQuery } from '@suspensive/react-query';
import { useIsFetching } from '@tanstack/react-query';
-import { capitalize, startCase } from 'lodash-es';
+import { capitalize } from 'lodash-es';
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import {
ActionFunctionArgs,
@@ -10,6 +10,7 @@ import {
useSearchParams,
} from 'react-router-dom';
import { toast } from 'sonner';
+import { cn } from 'tailwind-preset';
import {
Badge,
Breadcrumb,
@@ -65,7 +66,6 @@ import { getColorForCompliancePercent } from '@/constants/charts';
import { BreadcrumbWrapper } from '@/features/common/BreadcrumbWrapper';
import { useDownloadScan } from '@/features/common/data-component/downloadScanAction';
import { FilterWrapper } from '@/features/common/FilterWrapper';
-import { RefreshAccountStatusError } from '@/features/postures/components/RefreshAccountStatusError';
import { providersToNameMapping } from '@/features/postures/pages/Posture';
import {
getDeleteConfirmationDisplayName,
@@ -915,6 +915,7 @@ const ActionDropdown = ({
if (!scanId || !nodeType) return;
onTableAction(row, ActionEnumType.DELETE_SCAN);
}}
+ color="error"
>
Delete latest scan
@@ -1338,23 +1339,17 @@ const AccountTable = ({
cell: (info) => {
if (nodeType?.endsWith?.('_org')) {
const data = info.row.original.scan_status_map ?? {};
- const keys = Object.keys(data);
- const statuses = Object.keys(data).map((current, index) => {
+ const statuses = Object.keys(data).map((current) => {
return (
- <>
-
-
- {data[current]}
-
-
- {index < keys.length - 1 ? (
-
- ) : null}
-
- >
+
+
+ {data[current]}
+
+
+
);
});
- return {statuses}
;
+ return {statuses}
;
} else {
const value = info.getValue();
return ;
@@ -1367,7 +1362,11 @@ const AccountTable = ({
...columnWidth.active,
header: () => 'Active',
cell: (info) => {
- return info.getValue() ? 'Yes' : 'No';
+ return (
+
+ {info.getValue() ? 'Yes' : 'No'}
+
+ );
},
}),
];
@@ -1380,7 +1379,9 @@ const AccountTable = ({
...columnWidth.account_name,
header: () => 'Name',
cell: (info) => {
- return ;
+ return (
+
+ );
},
}),
);
@@ -1391,14 +1392,30 @@ const AccountTable = ({
...columnWidth.refresh_status,
header: () => 'Refresh status',
cell: (info) => {
- if (isRefreshAccountFailed(info.getValue())) {
+ if (nodeType?.endsWith?.('_org')) {
+ const data = info.row.original.refresh_status_map ?? {};
+ const statuses = Object.keys(data).map((current) => {
+ return (
+
+
+ {data[current]}
+
+
+
+ );
+ });
+ return {statuses}
;
+ } else {
return (
-
);
}
- return ;
},
}),
);
diff --git a/deepfence_frontend/apps/dashboard/src/features/postures/utils/index.ts b/deepfence_frontend/apps/dashboard/src/features/postures/utils/index.ts
index 0e9f05f071..fe67bab5bc 100644
--- a/deepfence_frontend/apps/dashboard/src/features/postures/utils/index.ts
+++ b/deepfence_frontend/apps/dashboard/src/features/postures/utils/index.ts
@@ -1,8 +1,8 @@
import {
ModelCloudComplianceStatusEnum,
- ModelCloudNodeAccountInfoRefreshStatusEnum,
ModelCloudNodeAccountsListReqCloudProviderEnum,
ModelComplianceStatusEnum,
+ ModelScanInfoStatusEnum,
} from '@/api/generated';
export const isCloudNonOrgNode = (nodeType?: string) => {
@@ -122,7 +122,7 @@ export function getDeleteConfirmationDisplayName(
}
export const isRefreshAccountFailed = (status: string): boolean => {
- if (status?.length && ModelCloudNodeAccountInfoRefreshStatusEnum.Error === status) {
+ if (status?.length && ModelScanInfoStatusEnum.Error === status) {
return true;
}
return false;
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 f25d090274..08e394d202 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
@@ -1,6 +1,8 @@
import { useSuspenseQuery } from '@suspensive/react-query';
+import { startCase } from 'lodash-es';
import { Suspense, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
+import { cn } from 'tailwind-preset';
import {
Badge,
Button,
@@ -546,7 +548,7 @@ function Filters() {
}}
getDisplayValue={() => FILTER_SEARCHPARAMS['agentRunning']}
>
- {['On', 'Off']
+ {['Yes', 'No']
.filter((item) => {
if (!agentRunningSearchText.length) return true;
return item.toLowerCase().includes(agentRunningSearchText.toLowerCase());
@@ -639,7 +641,7 @@ function useSearchHostsWithPagination() {
order: getOrderFromSearchParams(searchParams),
agentRunning: searchParams
.getAll('agentRunning')
- .map((value) => (value === 'On' ? true : false)),
+ .map((value) => (value === 'Yes' ? true : false)),
cloudAccounts: searchParams.getAll('cloudAccounts'),
clusterIds: searchParams.getAll('clusters'),
hosts: searchParams.getAll('hosts'),
@@ -723,8 +725,8 @@ const DataTable = ({
},
header: () => 'Name',
minSize: 300,
- size: 400,
- maxSize: 600,
+ size: 320,
+ maxSize: 360,
}),
columnHelper.accessor('vulnerability_scan_status', {
cell: (info) => {
@@ -767,20 +769,27 @@ const DataTable = ({
if (!info.getValue()) {
return ;
}
- return ;
+ return ;
},
- header: () => OS,
+ header: () => 'OS',
minSize: 50,
size: 60,
maxSize: 120,
}),
columnHelper.accessor('agent_running', {
cell: (info) => {
- return ;
+ return (
+
+ );
},
header: () => ,
- minSize: 50,
- size: 60,
+ minSize: 80,
+ size: 100,
maxSize: 120,
}),
columnHelper.accessor('version', {
@@ -827,6 +836,9 @@ const DataTable = ({
);
}
}
+ if (!info.getValue()) {
+ return ;
+ }
return ;
},
header: () => ,
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 d78cabb589..a207676d3c 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
@@ -1,7 +1,7 @@
import { useSuspenseQuery } from '@suspensive/react-query';
-import { upperFirst } from 'lodash-es';
import { Suspense, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
+import { cn } from 'tailwind-preset';
import {
Badge,
Button,
@@ -109,7 +109,7 @@ function Filters() {
}}
getDisplayValue={() => FILTER_SEARCHPARAMS['agentRunning']}
>
- {['On', 'Off']
+ {['Yes', 'No']
.filter((item) => {
if (!agentRunningSearchText.length) return true;
return item.toLowerCase().includes(agentRunningSearchText.toLowerCase());
@@ -347,7 +347,7 @@ function useSearchClustersWithPagination() {
clusterIds: searchParams.getAll('clusters'),
agentRunning: searchParams
.getAll('agentRunning')
- .map((value) => (value === 'On' ? true : false)),
+ .map((value) => (value === 'Yes' ? true : false)),
}),
keepPreviousData: true,
});
@@ -458,7 +458,14 @@ const DataTable = ({
}),
columnHelper.accessor('agent_running', {
cell: (info) => {
- return ;
+ return (
+
+ );
},
header: () => Agent running,
minSize: 60,
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 8700651481..91a5723dff 100644
--- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/MostExploitableVulnerabilities.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/MostExploitableVulnerabilities.tsx
@@ -50,7 +50,7 @@ import { ScanTypeEnum, VulnerabilitySeverityType } from '@/types/common';
import { get403Message } from '@/utils/403';
import { apiWrapper, retryUntilResponseHasValue } from '@/utils/api';
import { download } from '@/utils/download';
-import { SeverityEnumList } from '@/utils/enum';
+import { getSeverityPrettyName, SeverityEnumList, SeverityValueType } from '@/utils/enum';
const DEFAULT_PAGE_SIZE = 10;
@@ -79,6 +79,24 @@ const FILTER_SEARCHPARAMS: Record = {
clusters: 'Clusters',
};
+const getPrettyNameForAppliedFilters = ({
+ key,
+ value,
+}: {
+ key: string;
+ value: string;
+}) => {
+ switch (key) {
+ case 'severity':
+ return getSeverityPrettyName(value as SeverityValueType);
+ case 'liveConnection':
+ return upperFirst(value);
+
+ default:
+ return value;
+ }
+};
+
enum ActionEnumType {
DOWNLOAD = 'download',
}
@@ -399,7 +417,10 @@ const Filters = () => {
);
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 93e16b81f9..e4be88d31a 100644
--- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/UniqueVulnerabilities.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/UniqueVulnerabilities.tsx
@@ -35,7 +35,7 @@ import { FilterWrapper } from '@/features/common/FilterWrapper';
import { queries } from '@/queries';
import { useTheme } from '@/theme/ThemeContext';
import { ScanTypeEnum, VulnerabilitySeverityType } from '@/types/common';
-import { SeverityEnumList } from '@/utils/enum';
+import { getSeverityPrettyName, SeverityEnumList, SeverityValueType } from '@/utils/enum';
import { getOrderFromSearchParams, useSortingState } from '@/utils/table';
const DEFAULT_PAGE_SIZE = 10;
@@ -65,6 +65,24 @@ const FILTER_SEARCHPARAMS: Record = {
clusters: 'Clusters',
};
+const getPrettyNameForAppliedFilters = ({
+ key,
+ value,
+}: {
+ key: string;
+ value: string;
+}) => {
+ switch (key) {
+ case 'severity':
+ return getSeverityPrettyName(value as SeverityValueType);
+ case 'liveConnection':
+ return upperFirst(value);
+
+ default:
+ return value;
+ }
+};
+
const getAppliedFiltersCount = (searchParams: URLSearchParams) => {
return Object.keys(FILTER_SEARCHPARAMS).reduce((prev, curr) => {
return prev + searchParams.getAll(curr).length;
@@ -287,7 +305,10 @@ const Filters = () => {
);
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 1234bfb5ea..e2ecd02e2a 100644
--- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx
@@ -98,7 +98,13 @@ import {
import { get403Message, getResponseErrors } from '@/utils/403';
import { apiWrapper } from '@/utils/api';
import { formatMilliseconds } from '@/utils/date';
-import { SeverityEnum, SeverityEnumList } from '@/utils/enum';
+import {
+ getMaskedUnmaskedPrettyName,
+ getSeverityPrettyName,
+ SeverityEnum,
+ SeverityEnumList,
+ SeverityValueType,
+} from '@/utils/enum';
import { abbreviateNumber } from '@/utils/number';
import {
isScanComplete,
@@ -1216,6 +1222,30 @@ const FILTER_SEARCHPARAMS: Record = {
language: 'CVE Type',
exploitable: 'Exploitable',
};
+
+const getPrettyNameForAppliedFilters = ({
+ key,
+ value,
+}: {
+ key: string;
+ value: string;
+}) => {
+ switch (key) {
+ case 'severity':
+ return getSeverityPrettyName(value as SeverityValueType);
+ case 'visibility':
+ return getMaskedUnmaskedPrettyName(value);
+ case 'exploitable':
+ return 'Most Exploitable';
+ case 'attackVector':
+ return capitalize(value);
+ case 'language':
+ return capitalize(value);
+ default:
+ return value;
+ }
+};
+
const getAppliedFiltersCount = (searchParams: URLSearchParams) => {
return Object.keys(FILTER_SEARCHPARAMS).reduce((prev, curr) => {
return prev + searchParams.getAll(curr).length;
@@ -1306,7 +1336,7 @@ const Filters = () => {
}).map((item) => {
return (
- {capitalize(item)}
+ {getSeverityPrettyName(item)}
);
})}
@@ -1383,7 +1413,7 @@ const Filters = () => {
return prev;
});
}}
- text={`${FILTER_SEARCHPARAMS[key]}: ${value}`}
+ text={`${FILTER_SEARCHPARAMS[key]}: ${getPrettyNameForAppliedFilters({ key, value })}`}
/>
);
})}
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 958bbed8eb..b9fc62be2d 100644
--- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScans.tsx
+++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScans.tsx
@@ -1,6 +1,6 @@
import { useSuspenseQuery } from '@suspensive/react-query';
import { useIsFetching } from '@tanstack/react-query';
-import { capitalize } from 'lodash-es';
+import { capitalize, startCase } from 'lodash-es';
import React, { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import {
ActionFunctionArgs,
@@ -788,6 +788,21 @@ const FILTER_SEARCHPARAMS: Record = {
registryAccounts: 'Registry',
};
+const getPrettyNameForAppliedFilters = ({
+ key,
+ value,
+}: {
+ key: string;
+ value: string;
+}) => {
+ switch (key) {
+ case 'nodeType':
+ return startCase(value);
+ default:
+ return value;
+ }
+};
+
const getAppliedFiltersCount = (searchParams: URLSearchParams) => {
return Object.keys(FILTER_SEARCHPARAMS).reduce((prev, curr) => {
return prev + searchParams.getAll(curr).length;
@@ -1036,7 +1051,10 @@ const Filters = () => {
);
diff --git a/deepfence_frontend/apps/dashboard/src/utils/enum.ts b/deepfence_frontend/apps/dashboard/src/utils/enum.ts
index b61d6e1a5a..c0830dedc0 100644
--- a/deepfence_frontend/apps/dashboard/src/utils/enum.ts
+++ b/deepfence_frontend/apps/dashboard/src/utils/enum.ts
@@ -97,3 +97,17 @@ export function getSeverityPrettyName(severity: SeverityValueType) {
throw new Error(`Unhandled case: ${_exhaustiveCheck}`);
}
}
+
+export function getMaskedUnmaskedPrettyName(maskedUnmasked: string) {
+ switch (maskedUnmasked) {
+ case 'masked':
+ return 'Masked';
+ case 'unmasked':
+ return 'Unmasked';
+
+ default:
+ throw new Error(
+ `Unhandled case: No matching key found for getMaskedUnmaskedPrettyName`,
+ );
+ }
+}
diff --git a/deepfence_server/constants/api-messages/success.go b/deepfence_server/constants/api-messages/success.go
index 96c38cb354..993f9db40d 100644
--- a/deepfence_server/constants/api-messages/success.go
+++ b/deepfence_server/constants/api-messages/success.go
@@ -14,4 +14,5 @@ const (
ErrIntegrationTypeCannotBeUpdated = "integration type cannot be updated"
ErrIntegrationTypeEmpty = "integration type cannot be empty"
ErrNotificationTypeEmpty = "notification type cannot be empty"
+ ErrSendSummaryNotSupported = "notification type does not support sending summary links"
)
diff --git a/deepfence_server/controls/agent.go b/deepfence_server/controls/agent.go
index e7e76706f9..deaa03d41d 100644
--- a/deepfence_server/controls/agent.go
+++ b/deepfence_server/controls/agent.go
@@ -69,7 +69,7 @@ func GetAgentActions(ctx context.Context, agentID model.AgentID, consoleURL stri
actions = append(actions, upgradeActions...)
}
- scanActions, scanErr := ExtractStartingAgentScans(ctx, nodeID, workNumToExtract)
+ scanActions, scanErr := ExtractStartingAgentScans(ctx, nodeID, agentType, workNumToExtract)
workNumToExtract -= len(scanActions)
if scanErr == nil {
actions = append(actions, scanActions...)
@@ -312,7 +312,7 @@ func hasPendingAgentScans(ctx context.Context, client neo4j.DriverWithContext, n
return len(records) != 0, err
}
-func ExtractStartingAgentScans(ctx context.Context, nodeID string, maxWork int) ([]controls.Action, error) {
+func ExtractStartingAgentScans(ctx context.Context, nodeID string, agentType string, maxWork int) ([]controls.Action, error) {
ctx, span := telemetry.NewSpan(ctx, "control", "extract-starting-agent-scans")
defer span.End()
@@ -340,14 +340,28 @@ func ExtractStartingAgentScans(ctx context.Context, nodeID string, maxWork int)
}
defer tx.Close(ctx)
- r, err := tx.Run(ctx, `MATCH (s) -[:SCHEDULED]-> (n:Node{node_id:$id})
+ var r neo4j.ResultWithContext
+ if agentType == controls.CLOUD_AGENT {
+ r, err = tx.Run(ctx, `MATCH (c:CloudNode) <- [:SCANNED] - (s) -[:SCHEDULED]-> (n:Node{node_id:$id})
WHERE s.status = '`+utils.ScanStatusStarting+`'
+ AND c.refresh_status = 'COMPLETE'
+ AND c.active = true
AND s.retries < 3
WITH s ORDER BY s.is_priority DESC, s.updated_at ASC LIMIT $max_work
SET s.status = '`+utils.ScanStatusInProgress+`', s.updated_at = TIMESTAMP()
WITH s
RETURN s.trigger_action`,
- map[string]interface{}{"id": nodeID, "max_work": maxWork})
+ map[string]interface{}{"id": nodeID, "max_work": maxWork})
+ } else {
+ r, err = tx.Run(ctx, `MATCH (s) -[:SCHEDULED]-> (n:Node{node_id:$id})
+ WHERE s.status = '`+utils.ScanStatusStarting+`'
+ AND s.retries < 3
+ WITH s ORDER BY s.is_priority DESC, s.updated_at ASC LIMIT $max_work
+ SET s.status = '`+utils.ScanStatusInProgress+`', s.updated_at = TIMESTAMP()
+ WITH s
+ RETURN s.trigger_action`,
+ map[string]interface{}{"id": nodeID, "max_work": maxWork})
+ }
if err != nil {
return res, err
@@ -629,8 +643,7 @@ func ExtractPendingAgentUpgrade(ctx context.Context, nodeID string, maxWork int,
}
-func ExtractRefreshResourceAction(ctx context.Context, nodeID string,
- maxWork int) ([]controls.Action, error) {
+func ExtractRefreshResourceAction(ctx context.Context, nodeID string, maxWork int) ([]controls.Action, error) {
ctx, span := telemetry.NewSpan(ctx, "control", "extract-pending-refresh-resources")
defer span.End()
@@ -659,15 +672,15 @@ func ExtractRefreshResourceAction(ctx context.Context, nodeID string,
defer tx.Close(ctx)
r, err := tx.Run(ctx, `
- MATCH(n:Node{node_id:$id}) -[:HOSTS]-> (c:CloudNode)
- MATCH(r:CloudNodeRefresh{node_id:c.node_id})
- WHERE r.refresh=true
- WITH HEAD(collect(r)) AS rnode
- WHERE rnode IS NOT NULL
- WITH rnode, rnode.node_id AS node_id
- DETACH DELETE rnode
- RETURN node_id`,
- map[string]interface{}{"id": nodeID})
+ MATCH(n:Node{node_id:$id}) -[:HOSTS]-> (r:CloudNode)
+ WHERE r.refresh_status = '`+utils.ScanStatusStarting+`'
+ AND r.cloud_provider in ['`+model.PostureProviderAWS+`','`+model.PostureProviderGCP+`','`+model.PostureProviderAzure+`']
+ AND COALESCE(r.cloud_compliance_scan_status, '') <> '`+utils.ScanStatusInProgress+`'
+ WITH r LIMIT $max_work
+ SET r.refresh_status = '`+utils.ScanStatusInProgress+`', r.refresh_message = ''
+ WITH r
+ RETURN r.node_id, r.node_name AS account_id`,
+ map[string]interface{}{"id": nodeID, "max_work": maxWork})
if err != nil {
return res, err
@@ -683,13 +696,14 @@ func ExtractRefreshResourceAction(ctx context.Context, nodeID string,
for _, record := range records {
var action controls.Action
- if record.Values[0] == nil {
+ if record.Values[0] == nil || record.Values[1] == nil {
log.Error().Msgf("Invalid CloudNode ID, skipping")
continue
}
req := controls.RefreshResourcesRequest{}
req.NodeId = record.Values[0].(string)
+ req.AccountID = record.Values[1].(string)
req.NodeType = controls.CloudAccount
reqBytes, err := json.Marshal(req)
diff --git a/deepfence_server/go.mod b/deepfence_server/go.mod
index 4015b45fa2..9c8a42ec1b 100644
--- a/deepfence_server/go.mod
+++ b/deepfence_server/go.mod
@@ -134,7 +134,7 @@ require (
github.com/segmentio/asm v1.2.0 // indirect
github.com/sendgrid/sendgrid-go v3.14.0+incompatible
github.com/shopspring/decimal v1.2.0 // indirect
- github.com/spf13/cast v1.6.0 // indirect
+ github.com/spf13/cast v1.6.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/swaggest/jsonschema-go v0.3.64 // indirect
github.com/swaggest/refl v1.3.0 // indirect
diff --git a/deepfence_server/handler/cloud_node.go b/deepfence_server/handler/cloud_node.go
index 6e4e5585fd..a86d39bebf 100644
--- a/deepfence_server/handler/cloud_node.go
+++ b/deepfence_server/handler/cloud_node.go
@@ -8,6 +8,7 @@ import (
"net/http"
"github.com/deepfence/ThreatMapper/deepfence_server/model"
+ "github.com/deepfence/ThreatMapper/deepfence_server/reporters"
reporters_scan "github.com/deepfence/ThreatMapper/deepfence_server/reporters/scan"
ctl "github.com/deepfence/ThreatMapper/deepfence_utils/controls"
"github.com/deepfence/ThreatMapper/deepfence_utils/directory"
@@ -19,6 +20,9 @@ import (
var (
cloudAccountNodeType = ctl.ResourceTypeToString(ctl.CloudAccount)
+ refreshAccountFilter = reporters.FieldsFilters{
+ NotContainsFilter: reporters.ContainsFilter{FieldsValues: map[string][]interface{}{"refresh_status": {utils.ScanStatusStarting}}},
+ }
)
func (h *Handler) RegisterCloudNodeAccountHandler(w http.ResponseWriter, r *http.Request) {
@@ -59,19 +63,20 @@ func (h *Handler) RegisterCloudNodeAccountHandler(w http.ResponseWriter, r *http
"version": req.Version,
"node_type": req.CloudProvider,
}
- err = model.UpsertCloudComplianceNode(ctx, orgAccountNode, "", req.HostNodeID)
+ err = model.UpsertCloudAccount(ctx, orgAccountNode, req.IsOrganizationDeployment, req.HostNodeID)
if err != nil {
h.complianceError(w, err.Error())
return
}
- for _, monitoredAccount := range monitoredAccounts {
+ monitoredNodes := make([]map[string]interface{}, len(monitoredAccounts))
+ for i, monitoredAccount := range monitoredAccounts {
err = h.Validator.Struct(monitoredAccount)
if err != nil {
log.Error().Msg(err.Error())
h.respondError(&ValidatorError{err: err}, w)
return
}
- monitoredNode := map[string]interface{}{
+ monitoredNodes[i] = map[string]interface{}{
"node_id": monitoredAccount.NodeID,
"cloud_provider": req.CloudProvider,
"node_name": monitoredAccount.AccountID,
@@ -80,12 +85,12 @@ func (h *Handler) RegisterCloudNodeAccountHandler(w http.ResponseWriter, r *http
"version": req.Version,
"node_type": req.CloudProvider,
}
- err = model.UpsertCloudComplianceNode(ctx, monitoredNode, orgNodeID, req.HostNodeID)
- if err != nil {
- log.Error().Msgf("Error while upserting node: %+v", err)
- h.complianceError(w, err.Error())
- return
- }
+ }
+ err = model.UpsertChildCloudAccounts(ctx, monitoredNodes, orgNodeID, req.HostNodeID)
+ if err != nil {
+ log.Error().Msgf("Error while upserting node: %+v", err)
+ h.complianceError(w, err.Error())
+ return
}
} else {
log.Debug().Msgf("Single account monitoring for node: %s", nodeID)
@@ -98,7 +103,7 @@ func (h *Handler) RegisterCloudNodeAccountHandler(w http.ResponseWriter, r *http
"node_type": req.CloudProvider,
}
log.Debug().Msgf("Node for upsert: %+v", node)
- err = model.UpsertCloudComplianceNode(ctx, node, "", req.HostNodeID)
+ err = model.UpsertCloudAccount(ctx, node, req.IsOrganizationDeployment, req.HostNodeID)
if err != nil {
log.Error().Msgf("Error while upserting node: %+v", err)
h.complianceError(w, err.Error())
@@ -124,13 +129,19 @@ func (h *Handler) RefreshCloudAccountHandler(w http.ResponseWriter, r *http.Requ
nodeIdentifiers[i] = model.NodeIdentifier{NodeID: id, NodeType: cloudAccountNodeType}
}
- cloudNodeIds, err := reporters_scan.GetCloudAccountIDs(r.Context(), nodeIdentifiers)
+ cloudNodeIds, err := reporters_scan.GetCloudAccountIDs(r.Context(), nodeIdentifiers, &refreshAccountFilter)
if err != nil {
log.Error().Msgf(err.Error())
h.respondError(&BadDecoding{err}, w)
return
}
+ if len(cloudNodeIds) == 0 {
+ // Refresh already in progress for all requested cloud accounts
+ w.WriteHeader(http.StatusNoContent)
+ return
+ }
+
resolvedRequest := model.CloudAccountRefreshReq{NodeIDs: make([]string, len(cloudNodeIds))}
for i, id := range cloudNodeIds {
resolvedRequest.NodeIDs[i] = id.NodeID
diff --git a/deepfence_server/handler/integration.go b/deepfence_server/handler/integration.go
index 1368d83cea..d68ca1dd72 100644
--- a/deepfence_server/handler/integration.go
+++ b/deepfence_server/handler/integration.go
@@ -44,6 +44,18 @@ func (h *Handler) AddIntegration(w http.ResponseWriter, r *http.Request) {
return
}
+ if req.SendSummary {
+ if integration.SupportsSummaryLink(req.IntegrationType) {
+ req.Config["send_summary"] = True
+ } else {
+ err = httpext.JSON(w, http.StatusBadRequest, model.ErrorResponse{Message: api_messages.ErrSendSummaryNotSupported})
+ if err != nil {
+ log.Error().Msg(err.Error())
+ }
+ return
+ }
+ }
+
req.Config["filter_hash"], err = GetFilterHash(req.Filters)
if err != nil {
log.Error().Msgf("%v", err)
@@ -286,6 +298,18 @@ func (h *Handler) UpdateIntegration(w http.ResponseWriter, r *http.Request) {
return
}
+ if req.SendSummary {
+ if integration.SupportsSummaryLink(req.IntegrationType) {
+ req.Config["send_summary"] = True
+ } else {
+ err = httpext.JSON(w, http.StatusBadRequest, model.ErrorResponse{Message: api_messages.ErrSendSummaryNotSupported})
+ if err != nil {
+ log.Error().Msg(err.Error())
+ }
+ return
+ }
+ }
+
// check if integration is valid
/*err = i.SendNotification("validating integration")
if err != nil {
diff --git a/deepfence_server/handler/scan_reports.go b/deepfence_server/handler/scan_reports.go
index d2b1a66a7f..e8e17683c4 100644
--- a/deepfence_server/handler/scan_reports.go
+++ b/deepfence_server/handler/scan_reports.go
@@ -418,7 +418,7 @@ func (h *Handler) StartComplianceScanHandler(w http.ResponseWriter, r *http.Requ
regular, k8s, _, _ := extractBulksNodes(reqs.NodeIDs)
- cloudNodeIds, err := reportersScan.GetCloudAccountIDs(ctx, regular)
+ cloudNodeIds, err := reportersScan.GetCloudAccountIDs(ctx, regular, nil)
if err != nil {
h.respondError(err, w)
return
diff --git a/deepfence_server/ingesters/scan_status.go b/deepfence_server/ingesters/scan_status.go
index 068b8fb63b..d462bf6049 100644
--- a/deepfence_server/ingesters/scan_status.go
+++ b/deepfence_server/ingesters/scan_status.go
@@ -325,16 +325,16 @@ func AddNewCloudComplianceScan(
}
}
}
- nt := controls.KubernetesCluster
- if nodeType == controls.ResourceTypeToString(controls.Host) {
- nt = controls.Host
- }
var action []byte
var hostNodeID, hostNeo4jNodeType string
if nodeType == controls.ResourceTypeToString(controls.KubernetesCluster) || nodeType == controls.ResourceTypeToString(controls.Host) {
hostNodeID = nodeID
hostNeo4jNodeType = neo4jNodeType
+ nt := controls.KubernetesCluster
+ if nodeType == controls.ResourceTypeToString(controls.Host) {
+ nt = controls.Host
+ }
internalReq, _ := json.Marshal(controls.StartComplianceScanRequest{
NodeID: nodeID,
NodeType: nt,
@@ -374,7 +374,7 @@ func AddNewCloudComplianceScan(
internalReq, _ := json.Marshal(controls.StartCloudComplianceScanRequest{
NodeID: nodeID,
- NodeType: nt,
+ NodeType: controls.CloudAccount,
BinArgs: map[string]string{"scan_id": scanID, "benchmark_types": strings.Join(benchmarkTypes, ",")},
ScanDetails: controls.CloudComplianceScanDetails{
ScanId: scanID,
diff --git a/deepfence_server/model/cloud_node.go b/deepfence_server/model/cloud_node.go
index 8a74aff137..bf1f1c57dd 100644
--- a/deepfence_server/model/cloud_node.go
+++ b/deepfence_server/model/cloud_node.go
@@ -84,7 +84,8 @@ type CloudNodeAccountInfo struct {
LastScanID string `json:"last_scan_id"`
LastScanStatus string `json:"last_scan_status"`
RefreshMessage string `json:"refresh_message"`
- RefreshStatus string `json:"refresh_status" enum:"STARTING,IN_PROGRESS,ERROR,COMPLETE"`
+ RefreshStatus string `json:"refresh_status"`
+ RefreshStatusMap map[string]int64 `json:"refresh_status_map"`
ScanStatusMap map[string]int64 `json:"scan_status_map"`
Version string `json:"version"`
HostNodeID string `json:"host_node_id"`
@@ -205,8 +206,7 @@ type PostureProvider struct {
ResourceCount int64 `json:"resource_count"`
}
-func UpsertCloudComplianceNode(ctx context.Context, nodeDetails map[string]interface{},
- parentNodeID string, hostNodeID string) error {
+func UpsertCloudAccount(ctx context.Context, nodeDetails map[string]interface{}, isOrganizationDeployment bool, hostNodeID string) error {
ctx, span := telemetry.NewSpan(ctx, "model", "upsert-cloud-compliance-node")
defer span.End()
@@ -225,39 +225,65 @@ func UpsertCloudComplianceNode(ctx context.Context, nodeDetails map[string]inter
}
defer tx.Close(ctx)
- if parentNodeID == "" {
- if _, err := tx.Run(ctx, `
- MERGE (r:Node{node_id:$host_node_id, node_type: "cloud_agent"})
- WITH $param as row, r
- MERGE (n:CloudNode{node_id:row.node_id})
- ON CREATE SET n.refresh_status = 'STARTING', n.refresh_message = ''
- MERGE (r) -[:HOSTS]-> (n)
- SET n+= row, n.active = true, n.updated_at = TIMESTAMP(), n.version = row.version,
- r.node_name=$host_node_id, r.active = true, r.agent_running=true, r.updated_at = TIMESTAMP()`,
- map[string]interface{}{
- "param": nodeDetails,
- "host_node_id": hostNodeID,
- }); err != nil {
- return err
- }
- } else {
- if _, err := tx.Run(ctx, `
+ var setRefreshStatusQuery string
+ // Organization account node does not have refresh status. It is rather an aggregation of all child account statuses.
+ if !isOrganizationDeployment {
+ setRefreshStatusQuery = `ON CREATE SET n.refresh_status = '` + utils.ScanStatusStarting + `', n.refresh_message = ''`
+ }
+
+ if _, err = tx.Run(ctx, `
+ MERGE (r:Node{node_id:$host_node_id, node_type: "cloud_agent"})
+ WITH $param as row, r
+ MERGE (n:CloudNode{node_id:row.node_id})
+ `+setRefreshStatusQuery+`
+ MERGE (r) -[:HOSTS]-> (n)
+ SET n+= row, n.active = true, n.updated_at = TIMESTAMP(), n.version = row.version,
+ r.node_name=$host_node_id, r.active = true, r.agent_running=true, r.updated_at = TIMESTAMP()`,
+ map[string]interface{}{
+ "param": nodeDetails,
+ "host_node_id": hostNodeID,
+ }); err != nil {
+ return err
+ }
+
+ return tx.Commit(ctx)
+}
+
+func UpsertChildCloudAccounts(ctx context.Context, nodeDetails []map[string]interface{}, parentNodeID string, hostNodeID string) error {
+ ctx, span := telemetry.NewSpan(ctx, "model", "upsert-cloud-compliance-node")
+ defer span.End()
+
+ driver, err := directory.Neo4jClient(ctx)
+ if err != nil {
+ return err
+ }
+
+ session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite})
+ defer session.Close(ctx)
+
+ tx, err := session.BeginTransaction(ctx, neo4j.WithTxTimeout(30*time.Second))
+ if err != nil {
+ return err
+ }
+ defer tx.Close(ctx)
+
+ if _, err = tx.Run(ctx, `
MERGE (r:Node{node_id:$host_node_id, node_type: "cloud_agent"})
MERGE (m:CloudNode{node_id: $parent_node_id})
- WITH $param as row, r, m
+ WITH r, m
+ UNWIND $param as row
MERGE (n:CloudNode{node_id:row.node_id})
- ON CREATE SET n.refresh_status = 'STARTING', n.refresh_message = ''
+ ON CREATE SET n.refresh_status = '`+utils.ScanStatusStarting+`', n.refresh_message = ''
MERGE (m) -[:IS_CHILD]-> (n)
MERGE (r) -[:HOSTS]-> (n)
SET n+= row, n.active = true, n.updated_at = TIMESTAMP(), n.version = row.version,
r.active = true, r.agent_running=true, r.updated_at = TIMESTAMP()`,
- map[string]interface{}{
- "param": nodeDetails,
- "parent_node_id": parentNodeID,
- "host_node_id": hostNodeID,
- }); err != nil {
- return err
- }
+ map[string]interface{}{
+ "param": nodeDetails,
+ "parent_node_id": parentNodeID,
+ "host_node_id": hostNodeID,
+ }); err != nil {
+ return err
}
return tx.Commit(ctx)
@@ -532,8 +558,8 @@ func (c *CloudAccountRefreshReq) SetCloudAccountRefresh(ctx context.Context) err
if _, err = tx.Run(ctx, `
UNWIND $batch as cloudNode
- MERGE (n:CloudNodeRefresh{node_id: cloudNode})
- SET n.refresh = true, n.updated_at = TIMESTAMP()`,
+ MATCH (m:CloudNode{node_id: cloudNode})
+ SET m.refresh_status = '`+utils.ScanStatusStarting+`', m.refresh_message = ''`,
map[string]interface{}{
"batch": c.NodeIDs,
}); err != nil {
@@ -542,50 +568,6 @@ func (c *CloudAccountRefreshReq) SetCloudAccountRefresh(ctx context.Context) err
return tx.Commit(ctx)
}
-func (c *CloudAccountRefreshReq) GetCloudAccountRefresh(ctx context.Context) ([]string, error) {
-
- ctx, span := telemetry.NewSpan(ctx, "model", "get-cloud-account-refresh")
- defer span.End()
-
- var updatedNodeIDs []string
- driver, err := directory.Neo4jClient(ctx)
- if err != nil {
- return updatedNodeIDs, err
- }
-
- session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite})
- defer session.Close(ctx)
-
- tx, err := session.BeginTransaction(ctx, neo4j.WithTxTimeout(30*time.Second))
- if err != nil {
- return updatedNodeIDs, err
- }
- defer tx.Close(ctx)
-
- res, err := tx.Run(ctx, `
- UNWIND $batch as cloudNode
- MATCH (n:CloudNodeRefresh{node_id: cloudNode})
- WHERE n.refresh=true
- WITH n, n.node_id as deletedNodeID
- DELETE n
- RETURN deletedNodeID`,
- map[string]interface{}{
- "batch": c.NodeIDs,
- })
- if err != nil {
- return updatedNodeIDs, err
- }
- recs, err := res.Collect(ctx)
- if err != nil {
- return updatedNodeIDs, err
- }
-
- for _, rec := range recs {
- updatedNodeIDs = append(updatedNodeIDs, rec.Values[0].(string))
- }
- return updatedNodeIDs, tx.Commit(ctx)
-}
-
type CloudAccountDeleteReq struct {
NodeIDs []string `json:"node_ids" validate:"required,gt=0" required:"true"`
}
diff --git a/deepfence_server/model/integration.go b/deepfence_server/model/integration.go
index 5a8491b3c7..5ceffaf1f4 100644
--- a/deepfence_server/model/integration.go
+++ b/deepfence_server/model/integration.go
@@ -27,6 +27,7 @@ type IntegrationAddReq struct {
IntegrationType string `json:"integration_type" required:"true"`
NotificationType string `json:"notification_type" required:"true"`
Filters IntegrationFilters `json:"filters"`
+ SendSummary bool `json:"send_summary"`
}
type IntegrationFilters struct {
@@ -157,6 +158,7 @@ type IntegrationUpdateReq struct {
NotificationType string `json:"notification_type"`
Filters IntegrationFilters `json:"filters"`
IntegrationID string `path:"integration_id" validate:"required" required:"true"`
+ SendSummary bool `json:"send_summary"`
}
func (i *IntegrationUpdateReq) UpdateIntegration(ctx context.Context, pgClient *postgresqlDb.Queries, integration postgresqlDb.Integration) error {
diff --git a/deepfence_server/pkg/integration/aws-security-hub/awssecurityhub.go b/deepfence_server/pkg/integration/aws-security-hub/awssecurityhub.go
index 22faf43040..0180209f31 100644
--- a/deepfence_server/pkg/integration/aws-security-hub/awssecurityhub.go
+++ b/deepfence_server/pkg/integration/aws-security-hub/awssecurityhub.go
@@ -433,3 +433,7 @@ func (a AwsSecurityHub) mapPayloadToFindings(msg []map[string]interface{}, resou
func (a AwsSecurityHub) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (a AwsSecurityHub) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/elasticsearch/elasticsearch.go b/deepfence_server/pkg/integration/elasticsearch/elasticsearch.go
index f93920bcef..b0b5f549f5 100644
--- a/deepfence_server/pkg/integration/elasticsearch/elasticsearch.go
+++ b/deepfence_server/pkg/integration/elasticsearch/elasticsearch.go
@@ -100,3 +100,7 @@ func (e ElasticSearch) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (e ElasticSearch) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/email/email.go b/deepfence_server/pkg/integration/email/email.go
index e7d29e46b6..32aeaaee7f 100644
--- a/deepfence_server/pkg/integration/email/email.go
+++ b/deepfence_server/pkg/integration/email/email.go
@@ -6,8 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
- "sort"
- "strconv"
"strings"
"github.com/deepfence/ThreatMapper/deepfence_server/model"
@@ -35,69 +33,49 @@ func New(ctx context.Context, b []byte) (*Email, error) {
return &h, nil
}
-func (e Email) FormatMessage(message []map[string]interface{}) string {
- var entiremsg strings.Builder
- entiremsg.WriteString("*")
- entiremsg.WriteString(e.Resource)
- entiremsg.WriteString("*\n\n")
-
- // Prepare the sorted keys so that the output has the records in same order
- var keys []string
- if len(message) > 0 {
- keys = make([]string, 0, len(message[0]))
- for key := range message[0] {
- keys = append(keys, key)
- }
+func (e Email) FormatMessage(message []map[string]interface{},
+ extras map[string]interface{}) (string, map[string][]byte) {
- sort.Strings(keys)
- }
+ var msg strings.Builder
+ var attachments = map[string][]byte{}
- for k, v := range message {
- entiremsg.WriteString("#")
- entiremsg.WriteString(strconv.Itoa(k + 1))
- entiremsg.WriteString("\n")
- for _, key := range keys {
- if val, ok := v[key]; ok {
- fmtVal := ""
- if val != nil {
- fmtVal = fmt.Sprintf("%v", val)
+ for k, v := range extras {
+ if v != "" {
+ if k == "severity_counts" {
+ s := ""
+ for i, j := range v.(map[string]int32) {
+ s = fmt.Sprintf(" %s: %d\r\n", i, j)
}
- entiremsg.WriteString(key)
- entiremsg.WriteString(": ")
- entiremsg.WriteString(fmtVal)
- entiremsg.WriteString("\n")
- delete(v, key)
+ msg.WriteString(fmt.Sprintf("%s:\r\n%s", k, s))
+ } else {
+ msg.WriteString(fmt.Sprintf("%s: %v\r\n", k, v))
}
}
+ }
- // This is to handle if we have unprocessed data in the map
- // Possilbe if all the records are not uniform
- for key, val := range v {
- fmtVal := ""
- if val != nil {
- fmtVal = fmt.Sprintf("%v", val)
- }
- entiremsg.WriteString(key)
- entiremsg.WriteString(": ")
- entiremsg.WriteString(fmtVal)
- entiremsg.WriteString("\n")
- }
- entiremsg.WriteString("\n")
+ r, err := json.Marshal(message)
+ if err != nil {
+ log.Error().Err(err).Msg("failed to marshal results")
}
- return entiremsg.String()
+
+ attachments["scan-results.json"] = r
+
+ return msg.String(), attachments
}
-func (e Email) SendNotification(ctx context.Context, message []map[string]interface{}, extras map[string]interface{}) error {
+func (e Email) SendNotification(ctx context.Context,
+ message []map[string]interface{}, extras map[string]interface{}) error {
_, span := telemetry.NewSpan(ctx, "integrations", "email-send-notification")
defer span.End()
- m := e.FormatMessage(message)
+ m, a := e.FormatMessage(message, extras)
emailSender, err := sendemail.NewEmailSender(ctx)
if err != nil {
return err
}
- return emailSender.Send([]string{e.Config.EmailID}, "Deepfence Subscription", m, "", nil)
+ return emailSender.Send([]string{e.Config.EmailID},
+ fmt.Sprintf("Deepfence %s Subscription", e.Resource), m, "", a)
}
func (e Email) IsEmailConfigured(ctx context.Context) bool {
@@ -127,3 +105,7 @@ func (e Email) IsEmailConfigured(ctx context.Context) bool {
func (e Email) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (e Email) SendSummaryLink() bool {
+ return e.Config.SendSummary
+}
diff --git a/deepfence_server/pkg/integration/email/types.go b/deepfence_server/pkg/integration/email/types.go
index cb3e08a951..dad186bcd2 100644
--- a/deepfence_server/pkg/integration/email/types.go
+++ b/deepfence_server/pkg/integration/email/types.go
@@ -15,7 +15,8 @@ type Email struct {
}
type Config struct {
- EmailID string `json:"email_id" validate:"required,email" required:"true"`
+ EmailID string `json:"email_id" validate:"required,email" required:"true"`
+ SendSummary bool `json:"send_summary"`
}
func (e Email) ValidateConfig(validate *validator.Validate) error {
diff --git a/deepfence_server/pkg/integration/google-chronicle/googlechronicle.go b/deepfence_server/pkg/integration/google-chronicle/googlechronicle.go
index dba2421d5e..f7838fa60b 100644
--- a/deepfence_server/pkg/integration/google-chronicle/googlechronicle.go
+++ b/deepfence_server/pkg/integration/google-chronicle/googlechronicle.go
@@ -65,3 +65,7 @@ func (g GoogleChronicle) SendNotification(ctx context.Context, message []map[str
func (g GoogleChronicle) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (g GoogleChronicle) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/http-endpoint/http-endpoint.go b/deepfence_server/pkg/integration/http-endpoint/http-endpoint.go
index 672a62d28f..5e068dcfd8 100644
--- a/deepfence_server/pkg/integration/http-endpoint/http-endpoint.go
+++ b/deepfence_server/pkg/integration/http-endpoint/http-endpoint.go
@@ -103,3 +103,7 @@ func (h HTTPEndpoint) IsValidCredential(ctx context.Context) (bool, error) {
// Check the response status code.
return resp.StatusCode == http.StatusOK, nil
}
+
+func (h HTTPEndpoint) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/integration.go b/deepfence_server/pkg/integration/integration.go
index e451d9e867..a26e6eb581 100644
--- a/deepfence_server/pkg/integration/integration.go
+++ b/deepfence_server/pkg/integration/integration.go
@@ -58,17 +58,31 @@ func GetIntegration(ctx context.Context, integrationType string, b []byte) (Inte
func IsMessagingFormat(integrationType string) bool {
retVal := false
switch integrationType {
- case constants.Slack, constants.Teams, constants.PagerDuty,
- constants.Email, constants.Jira:
+ case constants.Slack,
+ constants.Teams,
+ constants.PagerDuty,
+ constants.Email,
+ constants.Jira:
retVal = true
}
return retVal
}
+func SupportsSummaryLink(integrationType string) bool {
+ switch integrationType {
+ case constants.Slack,
+ constants.Teams:
+ return true
+ }
+ return false
+}
+
// Integration is the interface for all integrations
type Integration interface {
// extras are additional fields that are not part of the message
+ // if SendSummaryLink is true then message array is expected to be scan summay
SendNotification(ctx context.Context, message []map[string]interface{}, extras map[string]interface{}) error
ValidateConfig(*validator.Validate) error
IsValidCredential(ctx context.Context) (bool, error)
+ SendSummaryLink() bool
}
diff --git a/deepfence_server/pkg/integration/jira/jira.go b/deepfence_server/pkg/integration/jira/jira.go
index ecf545bf82..1f4cb4c6f1 100644
--- a/deepfence_server/pkg/integration/jira/jira.go
+++ b/deepfence_server/pkg/integration/jira/jira.go
@@ -53,7 +53,15 @@ func (j Jira) SendNotification(ctx context.Context, message []map[string]interfa
extraStr := []string{}
for k, v := range extras {
if v != "" {
- extraStr = append(extraStr, fmt.Sprintf("%s: %v", k, v))
+ if k == "severity_counts" {
+ s := ""
+ for i, j := range v.(map[string]int32) {
+ s = fmt.Sprintf(" %s: %d\n", i, j)
+ }
+ extraStr = append(extraStr, fmt.Sprintf("%s:\n%s", k, s))
+ } else {
+ extraStr = append(extraStr, fmt.Sprintf("%s: %v\n", k, v))
+ }
}
}
@@ -168,3 +176,7 @@ func (j Jira) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (j Jira) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/pagerduty/pagerduty.go b/deepfence_server/pkg/integration/pagerduty/pagerduty.go
index 7b6a65e8c3..3a4c81a5e1 100644
--- a/deepfence_server/pkg/integration/pagerduty/pagerduty.go
+++ b/deepfence_server/pkg/integration/pagerduty/pagerduty.go
@@ -158,3 +158,7 @@ func IsValidCreds(p PagerDuty) (bool, error) {
func (p PagerDuty) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (p PagerDuty) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/s3/s3.go b/deepfence_server/pkg/integration/s3/s3.go
index 7d4ec69f07..d0d8d02a2c 100644
--- a/deepfence_server/pkg/integration/s3/s3.go
+++ b/deepfence_server/pkg/integration/s3/s3.go
@@ -142,3 +142,7 @@ func (s S3) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (s S3) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/slack/slack.go b/deepfence_server/pkg/integration/slack/slack.go
index 879ccc49ea..c74f26e932 100644
--- a/deepfence_server/pkg/integration/slack/slack.go
+++ b/deepfence_server/pkg/integration/slack/slack.go
@@ -6,11 +6,13 @@ import (
"encoding/json"
"fmt"
"net/http"
+ "time"
intgerr "github.com/deepfence/ThreatMapper/deepfence_server/pkg/integration/errors"
"github.com/deepfence/ThreatMapper/deepfence_utils/log"
"github.com/deepfence/ThreatMapper/deepfence_utils/telemetry"
"github.com/deepfence/ThreatMapper/deepfence_utils/utils"
+ "github.com/spf13/cast"
)
const BatchSize = 5
@@ -120,6 +122,100 @@ func (s Slack) FormatMessage(message []map[string]interface{}, index int) []map[
return blocks
}
+func (s Slack) FormatSummaryMessage(message []map[string]interface{}) []map[string]interface{} {
+
+ blocks := []map[string]interface{}{}
+
+ for _, m := range message {
+
+ // header
+ blocks = append(
+ blocks,
+ map[string]interface{}{
+ "type": "header",
+ "text": map[string]interface{}{
+ "type": "plain_text",
+ "text": fmt.Sprintf("Deepfence %s Scan", s.Resource),
+ "emoji": true,
+ },
+ },
+ )
+
+ // info section
+ startedAt := time.UnixMilli(cast.ToInt64(m["created_at"])).Format(time.RFC1123)
+ completedAt := time.UnixMilli(cast.ToInt64(m["updated_at"])).Format(time.RFC1123)
+ blocks = append(
+ blocks,
+ map[string]interface{}{
+ "type": "section",
+ "fields": []map[string]interface{}{
+ {
+ "type": "mrkdwn",
+ "text": fmt.Sprintf("*Node Type:*\n%v", m["node_type"]),
+ },
+ {
+ "type": "mrkdwn",
+ "text": fmt.Sprintf("*Node Name:*\n%v", m["node_name"]),
+ },
+ {
+ "type": "mrkdwn",
+ "text": fmt.Sprintf("*Started On:*\n %s", startedAt),
+ },
+ {
+ "type": "mrkdwn",
+ "text": fmt.Sprintf("*Completed On:*\n %s", completedAt),
+ },
+ },
+ },
+ )
+
+ // Severity section
+ var severity = ""
+ for k, v := range m["severity_counts"].(map[string]int32) {
+ severity += fmt.Sprintf(">_%s:_ %d\n", k, v)
+ }
+
+ blocks = append(
+ blocks,
+ map[string]interface{}{
+ "type": "section",
+ "fields": []map[string]interface{}{
+ {
+ "type": "mrkdwn",
+ "text": fmt.Sprintf("*%s Severity Count:*\n%s", s.Resource, severity),
+ },
+ },
+ },
+ )
+
+ // link
+ blocks = append(
+ blocks,
+ map[string]interface{}{
+ "type": "section",
+ "fields": []map[string]interface{}{
+ {
+ "type": "mrkdwn",
+ "text": fmt.Sprintf("<%v|*Click here*> to view scan results on Console",
+ m["scan_result_link"]),
+ },
+ },
+ },
+ )
+
+ // add a divider
+ blocks = append(
+ blocks,
+ map[string]interface{}{
+ "type": "divider",
+ },
+ )
+
+ }
+
+ return blocks
+}
+
func (s Slack) SendNotification(ctx context.Context, message []map[string]interface{}, extras map[string]interface{}) error {
_, span := telemetry.NewSpan(ctx, "integrations", "slack-send-notification")
@@ -137,7 +233,14 @@ func (s Slack) SendNotification(ctx context.Context, message []map[string]interf
batchMsg := message[startIdx:endIdx]
- m := s.FormatMessage(batchMsg, startIdx+1)
+ var m []map[string]interface{}
+
+ if s.SendSummaryLink() {
+ m = s.FormatSummaryMessage(batchMsg)
+ } else {
+ m = s.FormatMessage(batchMsg, startIdx+1)
+ }
+
payload := map[string]interface{}{
"blocks": m,
}
@@ -181,7 +284,8 @@ func (s Slack) SendNotification(ctx context.Context, message []map[string]interf
}
}
resp.Body.Close()
- return fmt.Errorf("failed to send notification batch %d, status code: %d , error: %s", i+1, resp.StatusCode, errorMsg)
+ return fmt.Errorf("failed to send notification batch %d, status code: %d , error: %s",
+ i+1, resp.StatusCode, errorMsg)
}
resp.Body.Close()
}
@@ -237,3 +341,7 @@ func (s Slack) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (s Slack) SendSummaryLink() bool {
+ return s.Config.SendSummary
+}
diff --git a/deepfence_server/pkg/integration/slack/types.go b/deepfence_server/pkg/integration/slack/types.go
index 2a003f24a6..de3fb1f2dc 100644
--- a/deepfence_server/pkg/integration/slack/types.go
+++ b/deepfence_server/pkg/integration/slack/types.go
@@ -19,8 +19,9 @@ func (s Slack) ValidateConfig(validate *validator.Validate) error {
}
type Config struct {
- WebhookURL string `json:"webhook_url" validate:"required,url" required:"true"`
- Channel string `json:"channel" validate:"required,min=1" required:"true"`
+ WebhookURL string `json:"webhook_url" validate:"required,url" required:"true"`
+ Channel string `json:"channel" validate:"required,min=1" required:"true"`
+ SendSummary bool `json:"send_summary"`
}
type Payload struct {
diff --git a/deepfence_server/pkg/integration/splunk/splunk.go b/deepfence_server/pkg/integration/splunk/splunk.go
index af8a8de083..3fbfe00987 100644
--- a/deepfence_server/pkg/integration/splunk/splunk.go
+++ b/deepfence_server/pkg/integration/splunk/splunk.go
@@ -120,3 +120,7 @@ func (s Splunk) Sender(in chan []byte, wg *sync.WaitGroup) {
func (s Splunk) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (s Splunk) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/sumologic/sumologic.go b/deepfence_server/pkg/integration/sumologic/sumologic.go
index 918aef605a..7cf53aeafb 100644
--- a/deepfence_server/pkg/integration/sumologic/sumologic.go
+++ b/deepfence_server/pkg/integration/sumologic/sumologic.go
@@ -76,3 +76,7 @@ func (s SumoLogic) SendNotification(ctx context.Context, data []map[string]inter
func (s SumoLogic) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (s SumoLogic) SendSummaryLink() bool {
+ return false
+}
diff --git a/deepfence_server/pkg/integration/teams/teams.go b/deepfence_server/pkg/integration/teams/teams.go
index 09a8643356..c38072279d 100644
--- a/deepfence_server/pkg/integration/teams/teams.go
+++ b/deepfence_server/pkg/integration/teams/teams.go
@@ -9,10 +9,12 @@ import (
"strconv"
"strings"
"sync"
+ "time"
"github.com/deepfence/ThreatMapper/deepfence_utils/log"
"github.com/deepfence/ThreatMapper/deepfence_utils/telemetry"
"github.com/deepfence/ThreatMapper/deepfence_utils/utils"
+ "github.com/spf13/cast"
)
const BatchSize = 5
@@ -31,20 +33,21 @@ func (t Teams) FormatMessage(message map[string]interface{}, position int, entir
if position == 1 {
entiremsg.WriteString("**")
entiremsg.WriteString(t.Resource)
- entiremsg.WriteString("**
")
+ entiremsg.WriteString("**\n\n")
}
entiremsg.WriteString("**#")
entiremsg.WriteString(strconv.Itoa(position))
- entiremsg.WriteString("**
")
+ entiremsg.WriteString("**\n")
for key, val := range message {
- entiremsg.WriteString(fmt.Sprintf("**%s**:%s
", key, val))
+ entiremsg.WriteString(fmt.Sprintf("**%s**:%s\n", key, val))
}
- entiremsg.WriteString("
")
+ entiremsg.WriteString("\n")
return entiremsg.String()
}
-func (t Teams) SendNotification(ctx context.Context, message []map[string]interface{}, extras map[string]interface{}) error {
+func (t Teams) SendNotification(ctx context.Context,
+ message []map[string]interface{}, extras map[string]interface{}) error {
_, span := telemetry.NewSpan(ctx, "integrations", "teams-send-notification")
defer span.End()
@@ -58,7 +61,7 @@ func (t Teams) SendNotification(ctx context.Context, message []map[string]interf
numRoutines = 10
}
- senderChan := make(chan *Payload, 500)
+ senderChan := make(chan any, 500)
var wg sync.WaitGroup
for i := 0; i < numRoutines; i++ {
wg.Add(1)
@@ -80,13 +83,16 @@ func (t Teams) SendNotification(ctx context.Context, message []map[string]interf
if endIndex > len(message) {
endIndex = len(message)
}
- t.enqueueNotification(message[startIndex:endIndex], senderChan)
+ if !t.SendSummaryLink() {
+ t.enqueueNotification(message[startIndex:endIndex], senderChan)
+ } else {
+ t.enqueueSummaryNotification(message[startIndex:endIndex], senderChan)
+ }
}
return nil
}
-func (t Teams) enqueueNotification(payloads []map[string]interface{},
- senderChan chan *Payload) {
+func (t Teams) enqueueNotification(payloads []map[string]interface{}, senderChan chan any) {
var message strings.Builder
var b strings.Builder
@@ -103,7 +109,85 @@ func (t Teams) enqueueNotification(payloads []map[string]interface{},
senderChan <- &payload
}
-func (t Teams) Sender(in chan *Payload, wg *sync.WaitGroup) {
+func (t Teams) enqueueSummaryNotification(payloads []map[string]interface{}, senderChan chan any) {
+
+ for _, m := range payloads {
+
+ ac := NewAdaptiveCards()
+
+ body := []map[string]interface{}{}
+
+ // header
+ body = append(
+ body,
+ map[string]interface{}{
+ "type": "TextBlock",
+ "size": "medium",
+ "weight": "bolder",
+ "style": "heading",
+ "text": fmt.Sprintf("Deepfence %s Scan", t.Resource),
+ },
+ )
+
+ // info
+ startedAt := time.UnixMilli(cast.ToInt64(m["created_at"])).Format(time.RFC1123)
+ completedAt := time.UnixMilli(cast.ToInt64(m["updated_at"])).Format(time.RFC1123)
+ body = append(
+ body,
+ map[string]interface{}{
+ "type": "TextBlock",
+ "text": fmt.Sprintf("**Node Type:** %s", m["node_type"]),
+ "warp": "true",
+ },
+ map[string]interface{}{
+ "type": "TextBlock",
+ "text": fmt.Sprintf("**Node Name:** %s", m["node_name"]),
+ "warp": "true",
+ },
+ map[string]interface{}{
+ "type": "TextBlock",
+ "text": fmt.Sprintf("**Started On:** %s", startedAt),
+ "warp": "true",
+ },
+ map[string]interface{}{
+ "type": "TextBlock",
+ "text": fmt.Sprintf("**Completed On:** %s", completedAt),
+ "warp": "true",
+ },
+ )
+
+ // Severity section
+ var severity = ""
+ for k, v := range m["severity_counts"].(map[string]int32) {
+ severity += fmt.Sprintf("> _%s:_ %d\n", k, v)
+ }
+
+ body = append(
+ body,
+ map[string]interface{}{
+ "type": "TextBlock",
+ "text": fmt.Sprintf("**%s Severity Count:**\n%s", t.Resource, severity),
+ "warp": "true",
+ },
+ )
+
+ body = append(
+ body,
+ map[string]interface{}{
+ "type": "TextBlock",
+ "text": fmt.Sprintf("[Click here](%s) to view scan results on Console", m["scan_result_link"]),
+ },
+ )
+
+ ac.AddAttachment(NewAttachment(NewContent(body, nil)))
+
+ // send summary card
+ senderChan <- &ac
+ }
+
+}
+
+func (t Teams) Sender(in chan any, wg *sync.WaitGroup) {
defer wg.Done()
for {
@@ -173,3 +257,7 @@ func (t Teams) IsValidCredential(ctx context.Context) (bool, error) {
return true, nil
}
+
+func (t Teams) SendSummaryLink() bool {
+ return t.Config.SendSummary
+}
diff --git a/deepfence_server/pkg/integration/teams/types.go b/deepfence_server/pkg/integration/teams/types.go
index 6506b585dc..bb60139162 100644
--- a/deepfence_server/pkg/integration/teams/types.go
+++ b/deepfence_server/pkg/integration/teams/types.go
@@ -18,7 +18,8 @@ type Teams struct {
}
type Config struct {
- WebhookURL string `json:"webhook_url" validate:"required,url" required:"true"`
+ WebhookURL string `json:"webhook_url" validate:"required,url" required:"true"`
+ SendSummary bool `json:"send_summary"`
}
// Payloads
@@ -48,3 +49,51 @@ type fact struct {
Key string `json:"key"`
Value string `json:"value"`
}
+
+type AdaptiveCards struct {
+ Type string `json:"type"`
+ Attachments []Attachment `json:"attachments"`
+}
+
+type Attachment struct {
+ ContentType string `json:"contentType"`
+ ContentURL any `json:"contentUrl"`
+ Content Content `json:"content"`
+}
+
+type Content struct {
+ Schema string `json:"$schema"`
+ Type string `json:"type"`
+ Version string `json:"version"`
+ Body []map[string]interface{} `json:"body"`
+ Actions []map[string]interface{} `json:"action"`
+}
+
+func NewAdaptiveCards() *AdaptiveCards {
+ return &AdaptiveCards{
+ Type: "message",
+ Attachments: []Attachment{},
+ }
+}
+
+func (ac *AdaptiveCards) AddAttachment(attach Attachment) *AdaptiveCards {
+ ac.Attachments = append(ac.Attachments, attach)
+ return ac
+}
+
+func NewAttachment(content Content) Attachment {
+ return Attachment{
+ ContentType: "application/vnd.microsoft.card.adaptive",
+ ContentURL: nil,
+ Content: content,
+ }
+}
+func NewContent(body, actions []map[string]interface{}) Content {
+ return Content{
+ Schema: "http://adaptivecards.io/schemas/adaptive-card.json",
+ Type: "AdaptiveCard",
+ Version: "1.0",
+ Body: body,
+ Actions: actions,
+ }
+}
diff --git a/deepfence_server/reporters/scan/scan_reporters.go b/deepfence_server/reporters/scan/scan_reporters.go
index c2a759b303..d923d28174 100644
--- a/deepfence_server/reporters/scan/scan_reporters.go
+++ b/deepfence_server/reporters/scan/scan_reporters.go
@@ -398,7 +398,7 @@ func GetPodContainerIDs(ctx context.Context, podIds []model.NodeIdentifier) ([]m
return res, nil
}
-func GetCloudAccountIDs(ctx context.Context, cloudProviderIds []model.NodeIdentifier) ([]model.NodeIdentifier, error) {
+func GetCloudAccountIDs(ctx context.Context, cloudProviderIds []model.NodeIdentifier, filters *reporters.FieldsFilters) ([]model.NodeIdentifier, error) {
ctx, span := telemetry.NewSpan(ctx, "scan-reports", "get-cloud-account-ids")
defer span.End()
@@ -418,9 +418,15 @@ func GetCloudAccountIDs(ctx context.Context, cloudProviderIds []model.NodeIdenti
}
defer tx.Close(ctx)
+ filterClauses := mo.None[reporters.FieldsFilters]()
+ if filters != nil {
+ filterClauses = mo.Some(*filters)
+ }
+
nres, err := tx.Run(ctx, `
MATCH (n:CloudNode)
WHERE n.node_id IN $node_ids
+ `+reporters.ParseFieldFilters2CypherWhereConditions(`n`, filterClauses, false)+`
RETURN n.node_id, n.cloud_provider`,
map[string]interface{}{"node_ids": NodeIdentifierToIDList(cloudProviderIds)})
if err != nil {
@@ -432,6 +438,7 @@ func GetCloudAccountIDs(ctx context.Context, cloudProviderIds []model.NodeIdenti
return res, err
}
orgNodeIds := []string{}
+ childNodeIDs := []string{}
for _, rec := range recs {
cloudProvider := rec.Values[1].(string)
if cloudProvider == model.PostureProviderAWSOrg || cloudProvider == model.PostureProviderGCPOrg || cloudProvider == model.PostureProviderAzureOrg {
@@ -442,13 +449,16 @@ func GetCloudAccountIDs(ctx context.Context, cloudProviderIds []model.NodeIdenti
NodeID: rec.Values[0].(string),
NodeType: controls.ResourceTypeToString(controls.CloudAccount),
})
+ childNodeIDs = append(childNodeIDs, rec.Values[0].(string))
}
if len(orgNodeIds) > 0 {
nres, err = tx.Run(ctx, `
MATCH (n:CloudNode) -[:IS_CHILD] -> (m)
- WHERE n.node_id IN $node_ids
+ WHERE n.node_id IN $node_ids
+ AND NOT m.node_id IN $child_node_ids
+ `+reporters.ParseFieldFilters2CypherWhereConditions(`m`, filterClauses, false)+`
RETURN m.node_id`,
- map[string]interface{}{"node_ids": orgNodeIds})
+ map[string]interface{}{"node_ids": orgNodeIds, "child_node_ids": childNodeIDs})
if err != nil {
return res, err
}
diff --git a/deepfence_server/reporters/search/search.go b/deepfence_server/reporters/search/search.go
index e51e3d0b03..db68ef0e56 100644
--- a/deepfence_server/reporters/search/search.go
+++ b/deepfence_server/reporters/search/search.go
@@ -444,6 +444,11 @@ func searchCloudNode(ctx context.Context, filter SearchFilter, fw model.FetchWin
if err != nil {
log.Error().Msgf("Error in populating status of org %v", err)
}
+
+ node.RefreshStatusMap, err = getRefreshStatusMap(ctx, node.NodeID)
+ if err != nil {
+ log.Error().Msgf("Error in populating refresh status of org %v", err)
+ }
}
res = append(res, node)
}
@@ -493,6 +498,42 @@ func getScanStatusMap(ctx context.Context, id string, cloudProvider string) (map
return res, nil
}
+func getRefreshStatusMap(ctx context.Context, nodeID string) (map[string]int64, error) {
+
+ ctx, span := telemetry.NewSpan(ctx, "search", "get-scan-status-map")
+ defer span.End()
+
+ res := map[string]int64{}
+ driver, err := directory.Neo4jClient(ctx)
+ if err != nil {
+ return res, err
+ }
+
+ session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
+ defer session.Close(ctx)
+
+ tx, err := session.BeginTransaction(ctx, neo4j.WithTxTimeout(30*time.Second))
+ if err != nil {
+ return res, err
+ }
+ defer tx.Close(ctx)
+ query := `MATCH (n:CloudNode{node_id:"` + nodeID + `"}) -[:IS_CHILD] -> (m:CloudNode)
+ RETURN m.refresh_status, count(m.refresh_status) `
+ r, err := tx.Run(ctx, query, map[string]interface{}{})
+ if err != nil {
+ return res, err
+ }
+
+ recs, err := r.Collect(ctx)
+ if err != nil {
+ return res, err
+ }
+ for _, rec := range recs {
+ res[rec.Values[0].(string)] = rec.Values[1].(int64)
+ }
+ return res, nil
+}
+
type FakeCypher struct {
nodeType string
extended string
diff --git a/deepfence_utils/controls/agent.go b/deepfence_utils/controls/agent.go
index 49858bb795..7d74eabe7a 100644
--- a/deepfence_utils/controls/agent.go
+++ b/deepfence_utils/controls/agent.go
@@ -27,6 +27,7 @@ const (
StartCloudComplianceScan
StopCloudComplianceScan
CloudScannerJobCount
+ CloudScannerResourceRefreshCount
)
type ScanResource int
@@ -160,8 +161,9 @@ type CloudComplianceScanBenchmark struct {
}
type RefreshResourcesRequest struct {
- NodeId string `json:"node_id" required:"true"`
- NodeType ScanResource `json:"node_type" required:"true"`
+ NodeId string `json:"node_id" required:"true"`
+ AccountID string `json:"account_id" required:"true"`
+ NodeType ScanResource `json:"node_type" required:"true"`
}
type StopSecretScanRequest StartSecretScanRequest
diff --git a/deepfence_utils/integrations/metrics.go b/deepfence_utils/integrations/metrics.go
index 3120cfe220..f33f87f9c5 100644
--- a/deepfence_utils/integrations/metrics.go
+++ b/deepfence_utils/integrations/metrics.go
@@ -6,8 +6,9 @@ import (
)
type Metrics struct {
- Ok int64 `json:"ok,omitempty"`
- Error int64 `json:"error,omitempty"`
+ Ok int64 `json:"ok,omitempty"`
+ Error int64 `json:"error,omitempty"`
+ IsSummary bool `json:"is_summary,omitempty"`
}
func (m *Metrics) Update(n Metrics) *Metrics {
@@ -17,6 +18,7 @@ func (m *Metrics) Update(n Metrics) *Metrics {
if n.Error > 0 {
m.Error += n.Error
}
+ m.IsSummary = n.IsSummary
return m
}
diff --git a/deepfence_utils/utils/ingesters/cloud_resource.go b/deepfence_utils/utils/ingesters/cloud_resource.go
index a76792c436..f083fff2b5 100644
--- a/deepfence_utils/utils/ingesters/cloud_resource.go
+++ b/deepfence_utils/utils/ingesters/cloud_resource.go
@@ -192,6 +192,7 @@ type CloudResourceRefreshStatus struct {
CloudNodeID string `json:"cloud_node_id"`
RefreshMessage string `json:"refresh_message"`
RefreshStatus string `json:"refresh_status"`
+ UpdatedAt int64 `json:"updated_at"`
}
func (c *CloudResourceRefreshStatus) ToMap() map[string]interface{} {
@@ -199,5 +200,6 @@ func (c *CloudResourceRefreshStatus) ToMap() map[string]interface{} {
"cloud_node_id": c.CloudNodeID,
"refresh_message": c.RefreshMessage,
"refresh_status": c.RefreshStatus,
+ "updated_at": c.UpdatedAt,
}
}
diff --git a/deepfence_worker/cronjobs/neo4j.go b/deepfence_worker/cronjobs/neo4j.go
index 1479f7470c..ce8479950a 100644
--- a/deepfence_worker/cronjobs/neo4j.go
+++ b/deepfence_worker/cronjobs/neo4j.go
@@ -23,6 +23,7 @@ const (
dbUpgradeTimeout = time.Minute * 30
defaultDBScannedResourceCleanUpTimeout = time.Hour * 24 * 30
dbCloudResourceCleanupTimeout = time.Hour * 24
+ dbCloudResourceRefreshTimeout = time.Hour * 12
ecsTaskRunningStatus = "RUNNING"
ecsTaskPublicEnabledConfig = "ENABLED"
dbDeletionTimeThreshold = time.Hour
@@ -401,6 +402,20 @@ func CleanUpDB(ctx context.Context, task *asynq.Task) error {
return err
}
+ if _, err = session.Run(ctx, `
+ MATCH (n:CloudNode)
+ WHERE n.refresh_updated_at < TIMESTAMP()-$time_ms
+ AND n.active = true
+ AND NOT n.refresh_status IN ['`+utils.ScanStatusStarting+`','`+utils.ScanStatusInProgress+`']
+ WITH n LIMIT 10000
+ SET n.refresh_status = '`+utils.ScanStatusStarting+`', n.refresh_updated_at = TIMESTAMP()`,
+ map[string]interface{}{
+ "time_ms": dbCloudResourceRefreshTimeout.Milliseconds(),
+ }, txConfig); err != nil {
+ log.Error().Msgf("Error in Clean up DB task: %v", err)
+ return err
+ }
+
if _, err = session.Run(ctx, `
MATCH (n:CloudResource) <-[:HOSTS]- (cr:CloudRegion)
WHERE n.updated_at < TIMESTAMP()-$time_ms
diff --git a/deepfence_worker/cronjobs/notification.go b/deepfence_worker/cronjobs/notification.go
index 0204323d28..a3c2377de9 100644
--- a/deepfence_worker/cronjobs/notification.go
+++ b/deepfence_worker/cronjobs/notification.go
@@ -8,6 +8,7 @@ import (
"fmt"
"os"
"strconv"
+ "strings"
"time"
reporters_search "github.com/deepfence/ThreatMapper/deepfence_server/reporters/search"
@@ -20,15 +21,12 @@ import (
"github.com/deepfence/ThreatMapper/deepfence_utils/integrations"
"github.com/deepfence/ThreatMapper/deepfence_utils/log"
postgresql_db "github.com/deepfence/ThreatMapper/deepfence_utils/postgresql/postgresql-db"
+ "github.com/deepfence/ThreatMapper/deepfence_utils/setting"
"github.com/deepfence/ThreatMapper/deepfence_utils/telemetry"
"github.com/deepfence/ThreatMapper/deepfence_utils/utils"
"github.com/hibiken/asynq"
)
-const NOTIFICATION_INTERVAL = 60000 //in milliseconds
-
-var ErrUnsupportedScan = errors.New("unsupported scan type in integration")
-
var fieldsMap = map[string]map[string]string{
utils.ScanTypeDetectedNode[utils.NEO4JVulnerabilityScan]: {
"cve_severity": "Severity",
@@ -95,12 +93,13 @@ var fieldsMap = map[string]map[string]string{
},
}
-const DefaultNotificationErrorBackoff = 15 * time.Minute
+var NotificationErrorBackoff time.Duration
-var (
- NotificationErrorBackoff time.Duration
- // notificationLock sync.Mutex
-)
+var ErrUnsupportedScan = errors.New("unsupported scan type in integration")
+
+const DefaultNotificationInterval = 60000 // in milliseconds
+
+const DefaultNotificationErrorBackoff = 15 * time.Minute
func init() {
backoffTimeStr := os.Getenv("DEEPFENCE_NOTIFICATION_ERROR_BACKOFF_MINUTES")
@@ -150,7 +149,7 @@ func TriggerSendNotifications(ctx context.Context, task *asynq.Task) error {
}
// check if any integrations are configured
- if len(integrations) <= 0 {
+ if len(integrations) == 0 {
log.Warn().Msg("No integrations configured to notify")
return nil
}
@@ -215,8 +214,8 @@ func SendNotifications(ctx context.Context, task *asynq.Task) error {
err = processForResource(ctx, intg)
- update_row := err != nil || (intg.ErrorMsg.Valid && err == nil)
- if update_row {
+ updateStatus := err != nil || (intg.ErrorMsg.Valid && err == nil)
+ if updateStatus {
var params postgresql_db.UpdateIntegrationStatusParams
if err != nil {
log.Error().Msgf("Error on integration %v", err)
@@ -268,7 +267,7 @@ func getLastEventUpdatedAt(eTime sql.NullTime, defaultTime time.Time) string {
if eTime.Valid {
return strconv.FormatInt(eTime.Time.UnixMilli(), 10)
}
- return strconv.FormatInt(defaultTime.UnixMilli()-NOTIFICATION_INTERVAL, 10)
+ return strconv.FormatInt(defaultTime.UnixMilli()-DefaultNotificationInterval, 10)
}
func processIntegration[T any](ctx context.Context, intg postgresql_db.Integration) error {
@@ -298,13 +297,15 @@ func processIntegration[T any](ctx context.Context, intg postgresql_db.Integrati
start := time.Now()
+ lastUpdatedAt := getLastEventUpdatedAt(intg.LastEventUpdatedAt, start)
+
filters.FieldsFilters = reporters.FieldsFilters{}
filters.FieldsFilters.CompareFilters = append(
filters.FieldsFilters.CompareFilters,
reporters.CompareFilter{
FieldName: "updated_at",
GreaterThan: true,
- FieldValue: getLastEventUpdatedAt(intg.LastEventUpdatedAt, start),
+ FieldValue: lastUpdatedAt,
},
)
filters.FieldsFilters.ContainsFilter = reporters.ContainsFilter{
@@ -329,7 +330,7 @@ func processIntegration[T any](ctx context.Context, intg postgresql_db.Integrati
scansList = append(scansList, scansInfo...)
}
}
- neo4j_query_nc := time.Since(profileStart)
+ neo4jQueryNc := time.Since(profileStart)
profileStart = time.Now()
if reqC != nil {
@@ -341,9 +342,9 @@ func processIntegration[T any](ctx context.Context, intg postgresql_db.Integrati
scansList = append(scansList, containerScanInfo...)
}
}
- neo4j_query_c := time.Since(profileStart)
+ neo4jQueryC := time.Since(profileStart)
- neo4j_query_default := time.Duration(0)
+ neo4jQueryDefault := time.Duration(0)
if reqNC == nil && reqC == nil {
profileStart = time.Now()
reqDefault := reporters_search.SearchScanReq{}
@@ -358,13 +359,12 @@ func processIntegration[T any](ctx context.Context, intg postgresql_db.Integrati
} else if len(defaultScanInfo) > 0 {
scansList = append(scansList, defaultScanInfo...)
}
- neo4j_query_default = time.Since(profileStart)
+ neo4jQueryDefault = time.Since(profileStart)
}
// nothing to notify
if len(scansList) == 0 {
- log.Info().Msgf("No %s scans after timestamp %d",
- intg.Resource, intg.LastEventUpdatedAt.Time.UnixMilli())
+ log.Info().Msgf("No %s scans after timestamp %s", intg.Resource, lastUpdatedAt)
return nil
}
@@ -385,9 +385,23 @@ func processIntegration[T any](ctx context.Context, intg postgresql_db.Integrati
// this var tracks the latest scan in the retrived scans
var latestScanUpdatedAt int64
+ // need sql client update status, event updated_at and metrics
+ pgClient, err := directory.PostgresClient(ctx)
+ if err != nil {
+ log.Error().Err(err).Msg("unable to get sql client")
+ return err
+ }
+
+ consoleURL, err := setting.GetManagementConsoleURL(ctx, pgClient)
+ if err != nil {
+ log.Error().Err(err).Msg("failed to get console url")
+ return err
+ }
+
+ // apply filters while fetching results
for _, scan := range scansList {
- //This map is required as we might have duplicates in the scansList
- //for the containers
+ // This map is required as we might have
+ // duplicates in the scansList for the containers
if _, ok := scanIDMap[scan.ScanID]; ok {
continue
}
@@ -406,71 +420,137 @@ func processIntegration[T any](ctx context.Context, intg postgresql_db.Integrati
return err
}
- totalQueryTime = totalQueryTime + time.Since(profileStart)
+ totalQueryTime += time.Since(profileStart)
+ // skip if no results
if len(results) == 0 {
- log.Info().Msgf("No Results filtered for scan id: %s with filters %+v", scan.ScanID, filters)
+ log.Info().Msgf("No Results filtered for scan id: %s with filters %+v",
+ scan.ScanID, filters)
continue
}
- var updatedResults []map[string]interface{}
- // inject node details to results
- if integration.IsMessagingFormat(intg.IntegrationType) {
- updatedResults = FormatForMessagingApps(results, intg.Resource)
- } else {
- updatedResults = []map[string]interface{}{}
- for _, r := range results {
- updatedResults = append(updatedResults, utils.ToMap[T](r))
+ // check if scan summary has to be sent
+ if integrationModel.SendSummaryLink() {
+
+ summary := utils.ToMap[any](scan)
+ summary["scan_result_link"] = scanResultLink(consoleURL, scan.NodeType, intg.Resource, scan.ScanID)
+
+ // send summary
+ err = integrationModel.SendNotification(
+ ctx,
+ []map[string]interface{}{summary},
+ map[string]interface{}{},
+ )
+ if err != nil {
+ updateIntegrationMetrics(ctx, pgClient, intg.ID,
+ integrations.Metrics{Ok: 0, Error: 1, IsSummary: true})
+ return err
+ } else {
+ updateIntegrationMetrics(ctx, pgClient, intg.ID,
+ integrations.Metrics{Ok: 1, Error: 0, IsSummary: true})
}
- }
- updatedResults = injectNodeDataMap(updatedResults, common, intg.IntegrationType, ctx)
+ } else {
- extras := utils.ToMap[any](common)
- extras["scan_type"] = intg.Resource
+ var updatedResults []map[string]interface{}
+ // inject node details to results
+ if integration.IsMessagingFormat(intg.IntegrationType) {
+ updatedResults = FormatForMessagingApps(results, intg.Resource)
+ } else {
+ updatedResults = []map[string]interface{}{}
+ for _, r := range results {
+ updatedResults = append(updatedResults, utils.ToMap[T](r))
+ }
+ }
- profileStart = time.Now()
+ updatedResults = injectNodeDataMap(updatedResults, common, intg.IntegrationType, ctx)
- err = integrationModel.SendNotification(ctx, updatedResults, extras)
- if err != nil {
- updateIntegrationMetrics(ctx, intg.ID,
- integrations.Metrics{Ok: 0, Error: int64(len(updatedResults))})
- return err
- } else {
- updateIntegrationMetrics(ctx, intg.ID,
- integrations.Metrics{Ok: int64(len(updatedResults)), Error: 0})
- }
+ extras := utils.ToMap[any](common)
+ extras["scan_type"] = intg.Resource
+ // applicable only for scans
+ extras["scan_result_link"] = scanResultLink(consoleURL, scan.NodeType, intg.Resource, scan.ScanID)
+ extras["severity_counts"] = scan.SeverityCounts
- totalSendTime = totalSendTime + time.Since(profileStart)
+ profileStart = time.Now()
- log.Info().Msgf("Notification sent %s scan %d messages using %s id %d, time taken: %s",
- intg.Resource, len(results), intg.IntegrationType,
- intg.ID, time.Since(profileStart))
- }
+ err = integrationModel.SendNotification(ctx, updatedResults, extras)
+ if err != nil {
+ updateIntegrationMetrics(ctx, pgClient, intg.ID,
+ integrations.Metrics{Ok: 0, Error: int64(len(updatedResults)), IsSummary: false})
+ return err
+ } else {
+ updateIntegrationMetrics(ctx, pgClient, intg.ID,
+ integrations.Metrics{Ok: int64(len(updatedResults)), Error: 0, IsSummary: false})
+ }
+ totalSendTime += time.Since(profileStart)
- if err := updateLastEventUpdatedAt(ctx, intg.ID, latestScanUpdatedAt); err != nil {
- log.Error().Err(err).Msg("failed to to update last_event_updated_at")
+ log.Info().Msgf("Notification sent %s scan %d messages using %s id %d, time taken: %s",
+ intg.Resource, len(results), intg.IntegrationType,
+ intg.ID, time.Since(profileStart))
+ }
}
log.Info().Msgf("%s Total Time taken for integration %s: %s", intg.Resource,
intg.IntegrationType, time.Since(start))
- log.Debug().Msgf("Time taken for neo4j_query_nc: %s", neo4j_query_nc)
- log.Debug().Msgf("Time taken for neo4j_query_c: %s", neo4j_query_c)
- log.Debug().Msgf("Time taken for neo4j_query_default: %s", neo4j_query_default)
+ log.Debug().Msgf("Time taken for neo4j_query_nc: %s", neo4jQueryNc)
+ log.Debug().Msgf("Time taken for neo4j_query_c: %s", neo4jQueryC)
+ log.Debug().Msgf("Time taken for neo4j_query_default: %s", neo4jQueryDefault)
log.Debug().Msgf("Time taken for neo4j_query_2: %s", totalQueryTime)
log.Debug().Msgf("Time taken for sending data : %s", totalSendTime)
+
+ // update the last event update_at
+ if err := updateLastEventUpdatedAt(ctx, pgClient, intg.ID, latestScanUpdatedAt); err != nil {
+ log.Error().Err(err).Msg("failed to to update last_event_updated_at")
+ }
+
return nil
}
-func updateLastEventUpdatedAt(ctx context.Context, intgId int32, updatedAt int64) error {
- pgClient, err := directory.PostgresClient(ctx)
- if err != nil {
- log.Error().Err(err).Msg("unable to get sql client")
- return err
+func scanResultLink(consoleURL string, nodeType string, scanType string, scanID string) string {
+
+ switch scanType {
+
+ case "Vulnerability":
+ return fmt.Sprintf("%s/vulnerability/scan-results/%s", consoleURL, scanID)
+
+ case "Secret":
+ return fmt.Sprintf("%s/secret/scan-results/%s", consoleURL, scanID)
+
+ case "Malware":
+ return fmt.Sprintf("%s/malware/scan-results/%s", consoleURL, scanID)
+
+ case "Compliance":
+ switch nodeType {
+ case "host":
+ return fmt.Sprintf("%s/posture/scan-results/linux/%s", consoleURL, scanID)
+ case "cluster":
+ return fmt.Sprintf("%s/posture/scan-results/kubernetes/%s", consoleURL, scanID)
+ default:
+ return fmt.Sprintf("%s/posture", consoleURL)
+ }
+
+ case "CloudCompliance":
+ switch {
+ case strings.Contains(scanID, "aws"):
+ return fmt.Sprintf("%s/posture/cloud/scan-results/aws/%s", consoleURL, scanID)
+ case strings.Contains(scanID, "azure"):
+ return fmt.Sprintf("%s/posture/cloud/scan-results/azure/%s", consoleURL, scanID)
+ case strings.Contains(scanID, "gcp"):
+ return fmt.Sprintf("%s/posture/cloud/scan-results/gcp/%s", consoleURL, scanID)
+ default:
+ return fmt.Sprintf("%s/posture", consoleURL)
+ }
+
+ default:
+ return consoleURL
}
+}
+
+func updateLastEventUpdatedAt(ctx context.Context, pgClient *postgresql_db.Queries,
+ intgID int32, updatedAt int64) error {
last := postgresql_db.UpdateIntegrationLastEventUpdatedAtParams{
- ID: intgId,
+ ID: intgID,
LastEventUpdatedAt: sql.NullTime{
Time: time.UnixMilli(updatedAt),
Valid: true,
@@ -485,12 +565,8 @@ func updateLastEventUpdatedAt(ctx context.Context, intgId int32, updatedAt int64
return nil
}
-func updateIntegrationMetrics(ctx context.Context, intgID int32, metrics integrations.Metrics) error {
- pgClient, err := directory.PostgresClient(ctx)
- if err != nil {
- log.Error().Err(err).Msg("unable to get sql client")
- return err
- }
+func updateIntegrationMetrics(ctx context.Context, pgClient *postgresql_db.Queries,
+ intgID int32, metrics integrations.Metrics) {
param := postgresql_db.SetIntegrationMetricsParams{ID: intgID}
@@ -504,10 +580,9 @@ func updateIntegrationMetrics(ctx context.Context, intgID int32, metrics integra
if err := pgClient.SetIntegrationMetrics(ctx, param); err != nil {
log.Error().Err(err).Msg("failed to update last event updated at")
- return err
+ return
}
- return nil
}
func FormatForMessagingApps[T any](results []T, resourceType string) []map[string]interface{} {
@@ -590,7 +665,6 @@ func injectNodeDataMap(results []map[string]interface{}, common model.ScanResult
flag := integration.IsMessagingFormat(integrationType)
for _, r := range results {
- //m := utils.ToMap[T](r)
r["node_id"] = common.NodeID
r["scan_id"] = common.ScanID
r["node_name"] = common.NodeName
diff --git a/deepfence_worker/cronscheduler/init_neo4j.go b/deepfence_worker/cronscheduler/init_neo4j.go
index 8ab4d3c44b..28f5fad942 100644
--- a/deepfence_worker/cronscheduler/init_neo4j.go
+++ b/deepfence_worker/cronscheduler/init_neo4j.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
+ "github.com/deepfence/ThreatMapper/deepfence_server/model"
"github.com/deepfence/ThreatMapper/deepfence_utils/directory"
"github.com/deepfence/ThreatMapper/deepfence_utils/log"
"github.com/deepfence/ThreatMapper/deepfence_utils/telemetry"
@@ -92,6 +93,8 @@ func initNeo4jDatabase(ctx context.Context) error {
//Set the base updated_at field on the ALIAS relationship
RunDisplayError(ctx, session, "WITH TIMESTAMP() as T MATCH(:ContainerImage) -[a:ALIAS]-> (:ImageTag) WHERE a.updated_at IS NULL SET a.updated_at=T")
+ RunDisplayError(ctx, session, "MATCH (n:CloudNode) WHERE COALESCE(n.refresh_status, '') = '' AND n.cloud_provider in ['"+model.PostureProviderAWS+"','"+model.PostureProviderGCP+"','"+model.PostureProviderAzure+"'] SET n.refresh_status='"+utils.ScanStatusStarting+"', n.refresh_message=''")
+
return nil
}
diff --git a/deepfence_worker/go.mod b/deepfence_worker/go.mod
index 847e55049a..bb4973975a 100644
--- a/deepfence_worker/go.mod
+++ b/deepfence_worker/go.mod
@@ -34,7 +34,7 @@ require (
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-20240626143546-e4ec9311fdf9
- github.com/deepfence/match-scanner v0.0.0-20240627065846-d2405fb72cfb
+ github.com/deepfence/match-scanner v0.0.0-20240701181002-ece6f13f296f
github.com/deepfence/package-scanner v0.0.0-00010101000000-000000000000
github.com/go-chi/chi/v5 v5.0.8
github.com/hibiken/asynq v0.24.1
diff --git a/deepfence_worker/go.sum b/deepfence_worker/go.sum
index e58fad65ab..d9094e36b5 100644
--- a/deepfence_worker/go.sum
+++ b/deepfence_worker/go.sum
@@ -212,8 +212,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
-github.com/deepfence/match-scanner v0.0.0-20240627065846-d2405fb72cfb h1:E3ffVItZVnhj1CD6UO/FPKyPz7Osinc4M770Jmm4JKc=
-github.com/deepfence/match-scanner v0.0.0-20240627065846-d2405fb72cfb/go.mod h1:eSZaZ9yVo4FoA3hJzTVWJL8HKvm3YPQzw3Nx/NKuq9A=
+github.com/deepfence/match-scanner v0.0.0-20240701181002-ece6f13f296f h1:TqJx9TO/YD/W6CgDb1f4IkZqRAM31i6O0I2OnrE6xCI=
+github.com/deepfence/match-scanner v0.0.0-20240701181002-ece6f13f296f/go.mod h1:eSZaZ9yVo4FoA3hJzTVWJL8HKvm3YPQzw3Nx/NKuq9A=
github.com/deepfence/vessel v0.12.3 h1:C34t+sV+JoFdfYhg+uyS+YOEDAFIYjBKHShD3emDISA=
github.com/deepfence/vessel v0.12.3/go.mod h1:bY97YUKMm0Oxasz/9o7Te60FjWCQWUYpgiWNC1E00xo=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
diff --git a/deepfence_worker/ingesters/cloud_resource.go b/deepfence_worker/ingesters/cloud_resource.go
index 035d58fbee..4b28313927 100644
--- a/deepfence_worker/ingesters/cloud_resource.go
+++ b/deepfence_worker/ingesters/cloud_resource.go
@@ -4,12 +4,12 @@ import (
"context"
"encoding/json"
"fmt"
+ "sort"
"time"
"github.com/deepfence/ThreatMapper/deepfence_utils/directory"
"github.com/deepfence/ThreatMapper/deepfence_utils/log"
"github.com/deepfence/ThreatMapper/deepfence_utils/telemetry"
- "github.com/deepfence/ThreatMapper/deepfence_utils/utils"
ingestersUtil "github.com/deepfence/ThreatMapper/deepfence_utils/utils/ingesters"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
@@ -266,7 +266,8 @@ func CommitFuncCloudResourceRefreshStatus(ctx context.Context, ns string, cs []i
UNWIND $batch as row
MATCH (n:CloudNode{node_id: row.cloud_node_id})
SET n.refresh_status = row.refresh_status,
- n.refresh_message = row.refresh_message`,
+ n.refresh_message = row.refresh_message,
+ n.refresh_updated_at = row.updated_at`,
map[string]interface{}{
"batch": ResourceRefreshStatusToMaps(cs),
},
@@ -276,42 +277,14 @@ func CommitFuncCloudResourceRefreshStatus(ctx context.Context, ns string, cs []i
}
func ResourceRefreshStatusToMaps(data []ingestersUtil.CloudResourceRefreshStatus) []map[string]interface{} {
- statusBuff := map[string]map[string]interface{}{}
- for _, i := range data {
- statusMap := i.ToMap()
+ statuses := make([]map[string]interface{}, len(data))
- cloudNodeId, ok := statusMap["cloud_node_id"].(string)
- if !ok {
- log.Error().Msgf("failed to convert cloud_node_id to string, data: %v", statusMap)
- continue
- }
-
- newStatus, ok := statusMap["refresh_status"].(string)
- if !ok {
- log.Error().Msgf("failed to convert refresh_status to string, data: %v", statusMap)
- continue
- }
+ sort.Slice(data, func(i, j int) bool {
+ return data[i].UpdatedAt < data[j].UpdatedAt
+ })
- old, found := statusBuff[cloudNodeId]
- if !found {
- statusBuff[cloudNodeId] = statusMap
- } else {
- oldStatus, ok := old["refresh_status"].(string)
- if !ok {
- log.Error().Msgf("failed to convert refresh_status to string, data: %v", old)
- continue
- }
- if newStatus != oldStatus {
- if newStatus == utils.ScanStatusSuccess ||
- newStatus == utils.ScanStatusFailed || newStatus == utils.ScanStatusCancelled {
- statusBuff[cloudNodeId] = statusMap
- }
- }
- }
- }
- statuses := []map[string]interface{}{}
- for _, v := range statusBuff {
- statuses = append(statuses, v)
+ for i, d := range data {
+ statuses[i] = d.ToMap()
}
return statuses
}
diff --git a/deepfence_worker/metrics.go b/deepfence_worker/metrics.go
index 578583f068..47309a86ce 100644
--- a/deepfence_worker/metrics.go
+++ b/deepfence_worker/metrics.go
@@ -203,7 +203,7 @@ func newIntegrationCollector() *IntegrationCollector {
integration: prometheus.NewDesc(
"integration_metrics",
"integration by type and metrics",
- []string{"namespace", "id", "type", "status"}, nil,
+ []string{"namespace", "id", "type", "status", "summary"}, nil,
),
}
}
@@ -237,9 +237,11 @@ func (collector *IntegrationCollector) Collect(ch chan<- prometheus.Metric) {
for _, intg := range integrations {
ch <- prometheus.MustNewConstMetric(collector.integration, prometheus.CounterValue,
- float64(intg.Metrics.Ok), ns, strconv.Itoa(int(intg.ID)), intg.IntegrationType, "ok")
+ float64(intg.Metrics.Ok), ns, strconv.Itoa(int(intg.ID)),
+ intg.IntegrationType, "ok", strconv.FormatBool(intg.Metrics.IsSummary))
ch <- prometheus.MustNewConstMetric(collector.integration, prometheus.CounterValue,
- float64(intg.Metrics.Error), ns, strconv.Itoa(int(intg.ID)), intg.IntegrationType, "error")
+ float64(intg.Metrics.Error), ns, strconv.Itoa(int(intg.ID)),
+ intg.IntegrationType, "error", strconv.FormatBool(intg.Metrics.IsSummary))
}
}
diff --git a/deepfence_worker/tasks/malwarescan/malwarescan.go b/deepfence_worker/tasks/malwarescan/malwarescan.go
index 0e8089bd25..c962298179 100644
--- a/deepfence_worker/tasks/malwarescan/malwarescan.go
+++ b/deepfence_worker/tasks/malwarescan/malwarescan.go
@@ -33,7 +33,7 @@ var (
failOnCompileWarning = false
malwareRulesDir = "/usr/local/malware"
malwareRulesPath = "/usr/local/malware/yara-rules"
- malwareConfigPath = "/malware-config"
+ malwareConfigPath = "/malware-config/config.yaml"
opts *malwareConfig.Options
yaraconfig config.Config
yr *yararules.YaraRules
@@ -77,7 +77,10 @@ func checkMalwareRulesUpdate(ctx context.Context) error {
if err := workerUtils.UpdateRules(ctx, path, malwareRulesDir); err != nil {
return err
}
- opts, yaraconfig, yr = initMalwareScanner()
+ opts, yaraconfig, yr, err = initMalwareScanner()
+ if err != nil {
+ return err
+ }
}
return nil
@@ -242,6 +245,7 @@ func (s MalwareScan) StartMalwareScan(ctx context.Context, task *asynq.Task) err
err = scanCtx.Checkpoint("After skopeo download")
if err != nil {
+ log.Error().Msg(err.Error())
return err
}
// set imageName in malwareScanner
@@ -251,8 +255,10 @@ func (s MalwareScan) StartMalwareScan(ctx context.Context, task *asynq.Task) err
err = malwareScanner.Scan(scanCtx, malwareScan.ImageScan, "", imageName, params.ScanID,
func(i output.IOCFound, s string) {
+ scanResult = append(scanResult, i)
})
if err != nil {
+ log.Error().Msg(err.Error())
return err
}
@@ -280,7 +286,7 @@ func (s MalwareScan) StartMalwareScan(ctx context.Context, task *asynq.Task) err
return nil
}
-func initMalwareScanner() (*malwareConfig.Options, config.Config, *yararules.YaraRules) {
+func initMalwareScanner() (*malwareConfig.Options, config.Config, *yararules.YaraRules, error) {
opts := malwareConfig.NewDefaultOptions()
opts.RulesPath = &malwareRulesPath
opts.ConfigPath = &malwareConfigPath
@@ -289,13 +295,15 @@ func initMalwareScanner() (*malwareConfig.Options, config.Config, *yararules.Yar
yaraconfig, err := config.ParseConfig(*opts.ConfigPath)
if err != nil {
log.Error().Msg(err.Error())
+ return nil, yaraconfig, nil, err
}
yr := yararules.New(*opts.RulesPath)
err = yr.Compile(malwareScanConstants.Filescan, *opts.FailOnCompileWarning)
if err != nil {
log.Error().Msg(err.Error())
+ return nil, yaraconfig, nil, err
}
- return opts, yaraconfig, yr
+ return opts, yaraconfig, yr, nil
}
diff --git a/docs/docs/cloudscanner/aws.md b/docs/docs/cloudscanner/aws.md
index 62bc46a7d7..d4f1e78d50 100644
--- a/docs/docs/cloudscanner/aws.md
+++ b/docs/docs/cloudscanner/aws.md
@@ -81,6 +81,8 @@ module "deepfence-cloud-scanner_example_single-account" {
mgmt-console-url = ""
mgmt-console-port = "443"
deepfence-key = ""
+ # AWS Account Name (Optional, for easy identification)
+ account_name = ""
image = "quay.io/deepfenceio/cloud_scanner_ce:THREATMAPPER_VERSION"
# Task CPU Units (Default: 4 vCPU)
cpu = "4096"
diff --git a/docs/docs/cloudscanner/gcp.md b/docs/docs/cloudscanner/gcp.md
index ff01921cfa..6761e50d12 100644
--- a/docs/docs/cloudscanner/gcp.md
+++ b/docs/docs/cloudscanner/gcp.md
@@ -21,6 +21,8 @@ module "cloud-scanner_example_single-project" {
mgmt-console-url = ""
mgmt-console-port = "443"
deepfence-key = ""
+ # GCP Project Name (Optional, for easy identification)
+ project_name = ""
image_name = "us-east1-docker.pkg.dev/deepfenceio/deepfence/cloud_scanner_ce:THREATMAPPER_VERSION"
# project_id example: dev1-123456
project_id = ""
diff --git a/docs/vulnerability_feeds/listing.json b/docs/vulnerability_feeds/listing.json
index 1212b5b831..170e6a7a79 100644
--- a/docs/vulnerability_feeds/listing.json
+++ b/docs/vulnerability_feeds/listing.json
@@ -2,54 +2,54 @@
"available": {
"3": [
{
- "built": "2024-06-28T00:14:41.212177109Z",
+ "built": "2024-07-02T00:13:36.994488193Z",
"version": 3,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-06-28_00-02-47/threatintel-vuln-v3-2024-06-28_00-02-47.tar.gz",
- "checksum": "6e007c26222d13214a2f9ed84a1b6168d7f220b58b3abdef1275097b10063134"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-07-02_00-02-33/threatintel-vuln-v3-2024-07-02_00-02-33.tar.gz",
+ "checksum": "ec15b20efc33447db14224822a95e349b84b7f6cc1fbc27abfbc3467afa108f9"
},
{
- "built": "2024-06-29T00:13:54.883824223Z",
+ "built": "2024-07-03T00:13:43.763310651Z",
"version": 3,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-06-29_00-02-25/threatintel-vuln-v3-2024-06-29_00-02-25.tar.gz",
- "checksum": "b1c02f19752fe2b9edbcdb9676a5a51caf856e891b4af4414a4227dc3821794f"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-07-03_00-02-40/threatintel-vuln-v3-2024-07-03_00-02-40.tar.gz",
+ "checksum": "f68bd1c7805c7c96d1556168c691d26a1c0bd13eac5fe00f7c385155311ca419"
},
{
- "built": "2024-06-30T00:13:55.172505189Z",
+ "built": "2024-07-04T00:14:41.155820756Z",
"version": 3,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-06-30_00-02-27/threatintel-vuln-v3-2024-06-30_00-02-27.tar.gz",
- "checksum": "2702efb332c2c1c45b634737e2422a7dc5c1e0a13402c7a7601e68a13228941b"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-07-04_00-02-19/threatintel-vuln-v3-2024-07-04_00-02-19.tar.gz",
+ "checksum": "8cca4816dd64be972f2f5721be61c5d755a02883b1c1cf31c6d58defac2d32d6"
},
{
- "built": "2024-07-01T00:13:40.377637385Z",
+ "built": "2024-07-05T00:13:51.524295992Z",
"version": 3,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-07-01_00-02-44/threatintel-vuln-v3-2024-07-01_00-02-44.tar.gz",
- "checksum": "bc7a35c4714fe713244a7109a1434dccae7f597278cf9c7336fa8e11b0561140"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v3-2024-07-05_00-02-39/threatintel-vuln-v3-2024-07-05_00-02-39.tar.gz",
+ "checksum": "d965859b48c6f9c395f724c3275a35ce65c5eac37d0d9a70aafe195ea240872f"
}
],
"5": [
{
- "built": "2024-06-29T14:36:08.794072895Z",
+ "built": "2024-07-03T14:26:24.407210575Z",
"version": 5,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-06-29_13-24-17/threatintel-vuln-v5-2024-06-29_13-24-17.tar.gz",
- "checksum": "c48957279cfbdd354b0e13b7c5843547b00cbc87618a7a468c2b4730b585f651"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-07-03_13-20-16/threatintel-vuln-v5-2024-07-03_13-20-16.tar.gz",
+ "checksum": "5449823099862fe3f8463bb02281b8991a07201e3cdfc277f6c6f43c5fd118b4"
},
{
- "built": "2024-06-30T03:00:59.325567564Z",
+ "built": "2024-07-04T02:57:44.20632311Z",
"version": 5,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-06-30_01-47-41/threatintel-vuln-v5-2024-06-30_01-47-41.tar.gz",
- "checksum": "25b7e7cd74e8ab3f31182fe894b8fd3d7d725d96504c33dff779c70ac8b4b7b5"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-07-04_01-47-42/threatintel-vuln-v5-2024-07-04_01-47-42.tar.gz",
+ "checksum": "f2d48e9c6f40d4fc1401e3c7ca68d566569286cc31ccf6e068c888d831afd20a"
},
{
- "built": "2024-06-30T14:31:16.209415361Z",
+ "built": "2024-07-04T14:32:02.795503284Z",
"version": 5,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-06-30_13-20-18/threatintel-vuln-v5-2024-06-30_13-20-18.tar.gz",
- "checksum": "6cb13fc86db44449a5f1fff895de4a1cac2a1095c1ff8e266969b68db3a5777e"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-07-04_13-23-21/threatintel-vuln-v5-2024-07-04_13-23-21.tar.gz",
+ "checksum": "60bc994283b75a45d27f2d3387e41b76224b3409491948a214fd825e7c0fdf7c"
},
{
- "built": "2024-07-01T02:48:57.308556995Z",
+ "built": "2024-07-05T02:51:35.567140143Z",
"version": 5,
- "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-07-01_01-45-26/threatintel-vuln-v5-2024-07-01_01-45-26.tar.gz",
- "checksum": "bacff416d5c58fb8cb1e365c5d803dbbd133db69ccc57566a70ba60bf2577eca"
+ "url": "https://threat-intel.deepfence.io/vulnerability-db/releases/download/threatintel-vuln-v5-2024-07-05_01-46-44/threatintel-vuln-v5-2024-07-05_01-46-44.tar.gz",
+ "checksum": "30797ed9f9e9c38c729c27ca2bd930e78a5af0259cc48a4d3aab909319d7f090"
}
]
}
diff --git a/golang_deepfence_sdk b/golang_deepfence_sdk
index 226fd1e1e6..e4ec9311fd 160000
--- a/golang_deepfence_sdk
+++ b/golang_deepfence_sdk
@@ -1 +1 @@
-Subproject commit 226fd1e1e6bb7b9f4e179d6f738bb7cd9c55dd72
+Subproject commit e4ec9311fdf96022b9a0fad59e63e530ba50587f