diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.test.ts index dff46266979cf..3c6f2fa2c61b7 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.test.ts @@ -25,13 +25,17 @@ describe('deduplicateAttackDiscoveries', () => { const uuid1 = 'test-uuid-1'; const uuid2 = 'test-uuid-2'; const [attack1, attack2] = mockAttackDiscoveries; + const ownerInfo = { + id: 'test-owner-1', + isSchedule: false, + }; const defaultProps = { attackDiscoveries: mockAttackDiscoveries, connectorId: 'test-connector-1', esClient: mockEsClient, indexPattern: '.test.alerts-*,.adhoc.alerts-*', logger: mockLogger, - ownerId: 'test-owner-1', + ownerInfo, replacements: undefined, spaceId: 'test-space', }; @@ -96,7 +100,7 @@ describe('deduplicateAttackDiscoveries', () => { expect(result).toEqual([attack1]); }); - it('should log when duplicates are found', async () => { + it('should log when duplicates are found for ad-hoc run', async () => { mockEsClient.search.mockResponse({ hits: { hits: [{ _source: { 'kibana.alert.instance.id': uuid1 } }], @@ -104,7 +108,32 @@ describe('deduplicateAttackDiscoveries', () => { } as unknown as estypes.SearchResponse); await deduplicateAttackDiscoveries(defaultProps); expect(mockLogger.info).toHaveBeenCalledWith( - 'Found 1 duplicate alert(s), skipping report for those.' + 'Ad-hoc Attack Discovery: Found 1 duplicate alert(s), skipping report for those.' + ); + expect((mockLogger.debug as jest.Mock).mock.calls[0][0]()).toBe( + `Ad-hoc Attack Discovery: Duplicated alerts:\n ${JSON.stringify([uuid1].sort(), null, 2)}` + ); + }); + + it('should log when duplicates are found for scheduled run', async () => { + mockEsClient.search.mockResponse({ + hits: { + hits: [{ _source: { 'kibana.alert.instance.id': uuid1 } }], + }, + } as unknown as estypes.SearchResponse); + await deduplicateAttackDiscoveries({ + ...defaultProps, + ownerInfo: { ...ownerInfo, isSchedule: true }, + }); + expect(mockLogger.info).toHaveBeenCalledWith( + 'Attack Discovery Schedule [test-owner-1]: Found 1 duplicate alert(s), skipping report for those.' + ); + expect((mockLogger.debug as jest.Mock).mock.calls[0][0]()).toBe( + `Attack Discovery Schedule [test-owner-1]: Duplicated alerts:\n ${JSON.stringify( + [uuid1].sort(), + null, + 2 + )}` ); }); }); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.ts index 79e5d4c35b73f..094770a88ff7e 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/deduplication/index.ts @@ -18,7 +18,10 @@ interface DeduplicateAttackDiscoveriesParams { esClient: ElasticsearchClient; indexPattern: string; logger: Logger; - ownerId: string; + ownerInfo: { + id: string; + isSchedule: boolean; + }; replacements: Replacements | undefined; spaceId: string; } @@ -29,7 +32,7 @@ export const deduplicateAttackDiscoveries = async ({ esClient, indexPattern, logger, - ownerId, + ownerInfo, replacements, spaceId, }: DeduplicateAttackDiscoveriesParams): Promise => { @@ -37,6 +40,8 @@ export const deduplicateAttackDiscoveries = async ({ return attackDiscoveries; } + const { id: ownerId, isSchedule } = ownerInfo; + // 1. Transform all attackDiscoveries to alert documents and collect alertUuids const alertDocs = attackDiscoveries.map((attack) => { const alertHash = generateAttackDiscoveryAlertHash({ @@ -70,8 +75,15 @@ export const deduplicateAttackDiscoveries = async ({ const numDuplicates = attackDiscoveries.length - newDiscoveries.length; if (numDuplicates > 0) { - logger.info(`Found ${numDuplicates} duplicate alert(s), skipping report for those.`); - logger.debug(() => `Duplicated alerts:\n ${JSON.stringify([...foundIds].sort(), null, 2)}`); + const logPrefix = isSchedule + ? `Attack Discovery Schedule [${ownerId}]` + : 'Ad-hoc Attack Discovery'; + logger.info( + `${logPrefix}: Found ${numDuplicates} duplicate alert(s), skipping report for those.` + ); + logger.debug( + () => `${logPrefix}: Duplicated alerts:\n ${JSON.stringify([...foundIds].sort(), null, 2)}` + ); } return newDiscoveries; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.test.ts index 248b1878847c0..7f4814c8b0b06 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.test.ts @@ -419,7 +419,10 @@ describe('attackDiscoveryScheduleExecutor', () => { esClient: services.scopedClusterClient.asCurrentUser, indexPattern: '.alerts-security.attack.discovery.alerts-test-space', logger: mockLogger, - ownerId: executorOptions.rule.id, + ownerInfo: { + id: executorOptions.rule.id, + isSchedule: true, + }, replacements: mockReplacements, spaceId, }); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.ts index a1ef473c7297c..e52c19b4b19c5 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/schedules/register_schedule/executor.ts @@ -127,7 +127,10 @@ export const attackDiscoveryScheduleExecutor = async ({ connectorId: params.apiConfig.connectorId, indexPattern, logger, - ownerId: rule.id, + ownerInfo: { + id: rule.id, + isSchedule: true, + }, replacements, spaceId, }); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/helpers/generate_and_update_discoveries.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/helpers/generate_and_update_discoveries.ts index bf2899b1080be..bc0664f86d2d2 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/helpers/generate_and_update_discoveries.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/helpers/generate_and_update_discoveries.ts @@ -111,7 +111,10 @@ export const generateAndUpdateAttackDiscoveries = async ({ connectorId: apiConfig.connectorId, indexPattern, logger, - ownerId: authenticatedUser.username ?? authenticatedUser.profile_uid, + ownerInfo: { + id: authenticatedUser.username ?? authenticatedUser.profile_uid, + isSchedule: false, + }, replacements: latestReplacements, spaceId: dataClient.spaceId, });