From 519d46209b6ccb09ff853b700e8064d6b6371c3e Mon Sep 17 00:00:00 2001 From: Kfir Peled <61654899+kfirpeled@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:58:38 +0000 Subject: [PATCH] [8.x] [Cloud Security] Fixed failing FTR (#199683) (#200530) # Backport This will backport the following commits from `main` to `8.x`: - [[Cloud Security] Fixed failing FTR (#199683)](https://github.com/elastic/kibana/pull/199683) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) --- .../graph_preview_container.test.tsx | 35 ++++++---- .../components/graph_preview_container.tsx | 67 +++++++++--------- .../right/hooks/use_graph_preview.test.tsx | 70 +++++++++++++++++-- .../right/hooks/use_graph_preview.ts | 17 +++-- .../routes/graph.ts | 20 +++++- .../es_archives/security_alerts/data.json | 36 +++++----- .../pages/alerts_flyout.ts | 4 +- 7 files changed, 169 insertions(+), 80 deletions(-) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.test.tsx index d44321a4926bd..ae907af316dc9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.test.tsx @@ -28,15 +28,6 @@ jest.mock('../hooks/use_fetch_graph_data', () => ({ })); const mockUseFetchGraphData = useFetchGraphData as jest.Mock; -const mockUseUiSetting = jest.fn().mockReturnValue([false]); -jest.mock('@kbn/kibana-react-plugin/public', () => { - const original = jest.requireActual('@kbn/kibana-react-plugin/public'); - return { - ...original, - useUiSetting$: () => mockUseUiSetting(), - }; -}); - const mockGraph = () =>
; jest.mock('@kbn/cloud-security-posture-graph', () => { @@ -64,7 +55,11 @@ describe('', () => { data: { nodes: [], edges: [] }, }); + const timestamp = new Date().toISOString(); + (useGraphPreview as jest.Mock).mockReturnValue({ + timestamp, + eventIds: [], isAuditLog: true, }); @@ -87,9 +82,23 @@ describe('', () => { expect( getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(GRAPH_PREVIEW_TEST_ID)) ).toBeInTheDocument(); + expect(mockUseFetchGraphData).toHaveBeenCalled(); + expect(mockUseFetchGraphData.mock.calls[0][0]).toEqual({ + req: { + query: { + eventIds: [], + start: `${timestamp}||-30m`, + end: `${timestamp}||+30m`, + }, + }, + options: { + enabled: true, + refetchOnWindowFocus: false, + }, + }); }); - it('should render error message and text in header', () => { + it('should not render when graph data is not available', () => { mockUseFetchGraphData.mockReturnValue({ isLoading: false, isError: false, @@ -100,10 +109,10 @@ describe('', () => { isAuditLog: false, }); - const { getByTestId } = renderGraphPreview(); + const { queryByTestId } = renderGraphPreview(); expect( - getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(GRAPH_PREVIEW_TEST_ID)) - ).toBeInTheDocument(); + queryByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(GRAPH_PREVIEW_TEST_ID)) + ).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx index af9e8dca1f24f..0b881b8f8d439 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/graph_preview_container.tsx @@ -14,57 +14,60 @@ import { useFetchGraphData } from '../hooks/use_fetch_graph_data'; import { useGraphPreview } from '../hooks/use_graph_preview'; import { ExpandablePanel } from '../../../shared/components/expandable_panel'; -const DEFAULT_FROM = 'now-60d/d'; -const DEFAULT_TO = 'now/d'; - /** * Graph preview under Overview, Visualizations. It shows a graph representation of entities. */ export const GraphPreviewContainer: React.FC = () => { const { dataAsNestedObject, getFieldsData } = useDocumentDetailsContext(); - const { eventIds } = useGraphPreview({ + const { + eventIds, + timestamp = new Date().toISOString(), + isAuditLog, + } = useGraphPreview({ getFieldsData, ecsData: dataAsNestedObject, }); // TODO: default start and end might not capture the original event - const graphFetchQuery = useFetchGraphData({ + const { isLoading, isError, data } = useFetchGraphData({ req: { query: { eventIds, - start: DEFAULT_FROM, - end: DEFAULT_TO, + start: `${timestamp}||-30m`, + end: `${timestamp}||+30m`, }, }, + options: { + enabled: isAuditLog, + refetchOnWindowFocus: false, + }, }); return ( - - ), - iconType: 'indexMapping', - }} - data-test-subj={GRAPH_PREVIEW_TEST_ID} - content={ - !graphFetchQuery.isLoading && !graphFetchQuery.isError - ? { - paddingSize: 'none', - } - : undefined - } - > - - + isAuditLog && ( + + ), + iconType: 'indexMapping', + }} + data-test-subj={GRAPH_PREVIEW_TEST_ID} + content={ + !isLoading && !isError + ? { + paddingSize: 'none', + } + : undefined + } + > + + + ) ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx index ff6118ec9b743..d12154a390abf 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx @@ -18,7 +18,7 @@ describe('useGraphPreview', () => { it(`should return false when missing actor`, () => { const getFieldsData: GetFieldsData = (field: string) => { if (field === 'kibana.alert.original_event.id') { - return field; + return 'eventId'; } return mockFieldData[field]; }; @@ -35,7 +35,12 @@ describe('useGraphPreview', () => { }, }); - expect(hookResult.result.current.isAuditLog).toEqual(false); + const { isAuditLog, timestamp, eventIds, actorIds, action } = hookResult.result.current; + expect(isAuditLog).toEqual(false); + expect(timestamp).toEqual(mockFieldData['@timestamp'][0]); + expect(eventIds).toEqual(['eventId']); + expect(actorIds).toEqual([]); + expect(action).toEqual(['action']); }); it(`should return false when missing event.action`, () => { @@ -57,7 +62,12 @@ describe('useGraphPreview', () => { }, }); - expect(hookResult.result.current.isAuditLog).toEqual(false); + const { isAuditLog, timestamp, eventIds, actorIds, action } = hookResult.result.current; + expect(isAuditLog).toEqual(false); + expect(timestamp).toEqual(mockFieldData['@timestamp'][0]); + expect(eventIds).toEqual(['eventId']); + expect(actorIds).toEqual(['actorId']); + expect(action).toEqual(undefined); }); it(`should return false when missing original_event.id`, () => { @@ -80,7 +90,45 @@ describe('useGraphPreview', () => { }, }); - expect(hookResult.result.current.isAuditLog).toEqual(false); + const { isAuditLog, timestamp, eventIds, actorIds, action } = hookResult.result.current; + expect(isAuditLog).toEqual(false); + expect(timestamp).toEqual(mockFieldData['@timestamp'][0]); + expect(eventIds).toEqual([]); + expect(actorIds).toEqual(['actorId']); + expect(action).toEqual(['action']); + }); + + it(`should return false when timestamp is missing`, () => { + const getFieldsData: GetFieldsData = (field: string) => { + if (field === '@timestamp') { + return; + } else if (field === 'kibana.alert.original_event.id') { + return 'eventId'; + } else if (field === 'actor.entity.id') { + return 'actorId'; + } + + return mockFieldData[field]; + }; + + hookResult = renderHook((props: UseGraphPreviewParams) => useGraphPreview(props), { + initialProps: { + getFieldsData, + ecsData: { + _id: 'id', + event: { + action: ['action'], + }, + }, + }, + }); + + const { isAuditLog, timestamp, eventIds, actorIds, action } = hookResult.result.current; + expect(isAuditLog).toEqual(false); + expect(timestamp).toEqual(null); + expect(eventIds).toEqual(['eventId']); + expect(actorIds).toEqual(['actorId']); + expect(action).toEqual(['action']); }); it(`should return true when alert is has graph preview`, () => { @@ -106,7 +154,12 @@ describe('useGraphPreview', () => { }, }); - expect(hookResult.result.current.isAuditLog).toEqual(true); + const { isAuditLog, timestamp, eventIds, actorIds, action } = hookResult.result.current; + expect(isAuditLog).toEqual(true); + expect(timestamp).toEqual(mockFieldData['@timestamp'][0]); + expect(eventIds).toEqual(['eventId']); + expect(actorIds).toEqual(['actorId']); + expect(action).toEqual(['action']); }); it(`should return true when alert is has graph preview with multiple values`, () => { @@ -132,6 +185,11 @@ describe('useGraphPreview', () => { }, }); - expect(hookResult.result.current.isAuditLog).toEqual(true); + const { isAuditLog, timestamp, eventIds, actorIds, action } = hookResult.result.current; + expect(isAuditLog).toEqual(true); + expect(timestamp).toEqual(mockFieldData['@timestamp'][0]); + expect(eventIds).toEqual(['id1', 'id2']); + expect(actorIds).toEqual(['actorId1', 'actorId2']); + expect(action).toEqual(['action1', 'action2']); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.ts index d833c0aa86dbc..bbaeb808c9e2a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.ts @@ -8,7 +8,7 @@ import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { get } from 'lodash/fp'; import type { GetFieldsData } from '../../shared/hooks/use_get_fields_data'; -import { getFieldArray } from '../../shared/utils'; +import { getField, getFieldArray } from '../../shared/utils'; export interface UseGraphPreviewParams { /** @@ -25,6 +25,11 @@ export interface UseGraphPreviewParams { * Interface for the result of the useGraphPreview hook */ export interface UseGraphPreviewResult { + /** + * The timestamp of the event + */ + timestamp: string | null; + /** * Array of event IDs associated with the alert */ @@ -38,7 +43,7 @@ export interface UseGraphPreviewResult { /** * Action associated with the event */ - action: string | undefined; + action?: string[]; /** * Boolean indicating if the event is an audit log (contains event ids, actor ids and action) @@ -53,13 +58,15 @@ export const useGraphPreview = ({ getFieldsData, ecsData, }: UseGraphPreviewParams): UseGraphPreviewResult => { + const timestamp = getField(getFieldsData('@timestamp')); const originalEventId = getFieldsData('kibana.alert.original_event.id'); const eventId = getFieldsData('event.id'); const eventIds = originalEventId ? getFieldArray(originalEventId) : getFieldArray(eventId); const actorIds = getFieldArray(getFieldsData('actor.entity.id')); - const action = get(['event', 'action'], ecsData); - const isAuditLog = actorIds.length > 0 && action?.length > 0 && eventIds.length > 0; + const action: string[] | undefined = get(['event', 'action'], ecsData); + const isAuditLog = + Boolean(timestamp) && actorIds.length > 0 && Boolean(action?.length) && eventIds.length > 0; - return { eventIds, actorIds, action, isAuditLog }; + return { timestamp, eventIds, actorIds, action, isAuditLog }; }; diff --git a/x-pack/test/cloud_security_posture_api/routes/graph.ts b/x-pack/test/cloud_security_posture_api/routes/graph.ts index 8043e6e22feb6..95625b24fa59a 100644 --- a/x-pack/test/cloud_security_posture_api/routes/graph.ts +++ b/x-pack/test/cloud_security_posture_api/routes/graph.ts @@ -399,7 +399,7 @@ export default function (providerContext: FtrProviderContext) { }); }); - it('Should filter unknown targets', async () => { + it('should filter unknown targets', async () => { const response = await postGraph(supertest, { query: { eventIds: [], @@ -424,7 +424,7 @@ export default function (providerContext: FtrProviderContext) { expect(response.body).not.to.have.property('messages'); }); - it('Should return unknown targets', async () => { + it('should return unknown targets', async () => { const response = await postGraph(supertest, { showUnknownTarget: true, query: { @@ -450,7 +450,7 @@ export default function (providerContext: FtrProviderContext) { expect(response.body).not.to.have.property('messages'); }); - it('Should limit number of nodes', async () => { + it('should limit number of nodes', async () => { const response = await postGraph(supertest, { nodesLimit: 1, query: { @@ -476,6 +476,20 @@ export default function (providerContext: FtrProviderContext) { expect(response.body).to.have.property('messages').length(1); expect(response.body.messages[0]).equal(ApiMessageCode.ReachedNodesLimit); }); + + it('should support date math', async () => { + const response = await postGraph(supertest, { + query: { + eventIds: ['kabcd1234efgh5678'], + start: '2024-09-01T12:30:00.000Z||-30m', + end: '2024-09-01T12:30:00.000Z||+30m', + }, + }).expect(result(200)); + + expect(response.body).to.have.property('nodes').length(3); + expect(response.body).to.have.property('edges').length(2); + expect(response.body).not.to.have.property('messages'); + }); }); }); } diff --git a/x-pack/test/cloud_security_posture_functional/es_archives/security_alerts/data.json b/x-pack/test/cloud_security_posture_functional/es_archives/security_alerts/data.json index 94ecc85bfd234..e28c92931b619 100644 --- a/x-pack/test/cloud_security_posture_functional/es_archives/security_alerts/data.json +++ b/x-pack/test/cloud_security_posture_functional/es_archives/security_alerts/data.json @@ -4,7 +4,7 @@ "id": "589e086d7ceec7d4b353340578bd607e96fbac7eab9e2926f110990be15122f1", "index": ".internal.alerts-security.alerts-default-000001", "source": { - "@timestamp": "2024-09-01T20:44:02.109Z", + "@timestamp": "2024-09-01T12:44:02.109Z", "actor": { "entity": { "id": "admin@example.com" @@ -34,7 +34,7 @@ ], "dataset": "gcp.audit", "id": "kabcd1234efgh5678", - "ingested": "2024-09-01T20:40:17Z", + "ingested": "2024-09-01T12:40:17Z", "module": "gcp", "outcome": "success", "provider": "activity", @@ -95,8 +95,8 @@ } ], "kibana.alert.depth": 1, - "kibana.alert.intended_timestamp": "2024-09-01T20:44:02.117Z", - "kibana.alert.last_detected": "2024-09-01T20:44:02.117Z", + "kibana.alert.intended_timestamp": "2024-09-01T12:44:02.117Z", + "kibana.alert.last_detected": "2024-09-01T12:44:02.117Z", "kibana.alert.original_event.action": "google.iam.admin.v1.CreateRole", "kibana.alert.original_event.agent_id_status": "missing", "kibana.alert.original_event.category": [ @@ -106,7 +106,7 @@ ], "kibana.alert.original_event.dataset": "gcp.audit", "kibana.alert.original_event.id": "kabcd1234efgh5678", - "kibana.alert.original_event.ingested": "2024-09-01T20:40:17Z", + "kibana.alert.original_event.ingested": "2024-09-01T12:40:17Z", "kibana.alert.original_event.kind": "event", "kibana.alert.original_event.module": "gcp", "kibana.alert.original_event.outcome": "success", @@ -126,13 +126,13 @@ ], "kibana.alert.rule.category": "Custom Query Rule", "kibana.alert.rule.consumer": "siem", - "kibana.alert.rule.created_at": "2024-09-01T20:38:49.650Z", + "kibana.alert.rule.created_at": "2024-09-01T12:38:49.650Z", "kibana.alert.rule.created_by": "elastic", "kibana.alert.rule.description": "Identifies an Identity and Access Management (IAM) custom role creation in Google Cloud Platform (GCP). Custom roles are user-defined, and allow for the bundling of one or more supported permissions to meet specific needs. Custom roles will not be updated automatically and could lead to privilege creep if not carefully scrutinized.", "kibana.alert.rule.enabled": true, "kibana.alert.rule.exceptions_list": [ ], - "kibana.alert.rule.execution.timestamp": "2024-09-01T20:44:02.117Z", + "kibana.alert.rule.execution.timestamp": "2024-09-01T12:44:02.117Z", "kibana.alert.rule.execution.uuid": "a440f349-1900-4087-b507-f2b98c6cfa79", "kibana.alert.rule.false_positives": [ "Custom role creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Role creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." @@ -300,12 +300,12 @@ "kibana.alert.rule.timestamp_override": "event.ingested", "kibana.alert.rule.to": "now", "kibana.alert.rule.type": "query", - "kibana.alert.rule.updated_at": "2024-09-01T20:39:00.099Z", + "kibana.alert.rule.updated_at": "2024-09-01T12:39:00.099Z", "kibana.alert.rule.updated_by": "elastic", "kibana.alert.rule.uuid": "c6f64115-5941-46ef-bfa3-61a4ecb4f3ba", "kibana.alert.rule.version": 104, "kibana.alert.severity": "medium", - "kibana.alert.start": "2024-09-01T20:44:02.117Z", + "kibana.alert.start": "2024-09-01T12:44:02.117Z", "kibana.alert.status": "active", "kibana.alert.uuid": "589e086d7ceec7d4b353340578bd607e96fbac7eab9e2926f110990be15122f1", "kibana.alert.workflow_assignee_ids": [ @@ -361,7 +361,7 @@ "id": "838ea37ab43ab7d2754d007fbe8191be53d7d637bea62f6189f8db1503c0e250", "index": ".internal.alerts-security.alerts-default-000001", "source": { - "@timestamp": "2024-09-01T20:39:03.646Z", + "@timestamp": "2024-09-01T12:39:03.646Z", "actor": { "entity": { "id": "admin@example.com" @@ -391,7 +391,7 @@ ], "dataset": "gcp.audit", "id": "kabcd1234efgh5678", - "ingested": "2024-09-01T20:38:13Z", + "ingested": "2024-09-01T12:38:13Z", "module": "gcp", "outcome": "success", "provider": "activity", @@ -452,8 +452,8 @@ } ], "kibana.alert.depth": 1, - "kibana.alert.intended_timestamp": "2024-09-01T20:39:03.657Z", - "kibana.alert.last_detected": "2024-09-01T20:39:03.657Z", + "kibana.alert.intended_timestamp": "2024-09-01T12:39:03.657Z", + "kibana.alert.last_detected": "2024-09-01T12:39:03.657Z", "kibana.alert.original_event.action": "google.iam.admin.v1.CreateRole", "kibana.alert.original_event.agent_id_status": "missing", "kibana.alert.original_event.category": [ @@ -463,7 +463,7 @@ ], "kibana.alert.original_event.dataset": "gcp.audit", "kibana.alert.original_event.id": "kabcd1234efgh5678", - "kibana.alert.original_event.ingested": "2024-09-01T20:38:13Z", + "kibana.alert.original_event.ingested": "2024-09-01T12:38:13Z", "kibana.alert.original_event.kind": "event", "kibana.alert.original_event.module": "gcp", "kibana.alert.original_event.outcome": "success", @@ -483,13 +483,13 @@ ], "kibana.alert.rule.category": "Custom Query Rule", "kibana.alert.rule.consumer": "siem", - "kibana.alert.rule.created_at": "2024-09-01T20:38:49.650Z", + "kibana.alert.rule.created_at": "2024-09-01T12:38:49.650Z", "kibana.alert.rule.created_by": "elastic", "kibana.alert.rule.description": "Identifies an Identity and Access Management (IAM) custom role creation in Google Cloud Platform (GCP). Custom roles are user-defined, and allow for the bundling of one or more supported permissions to meet specific needs. Custom roles will not be updated automatically and could lead to privilege creep if not carefully scrutinized.", "kibana.alert.rule.enabled": true, "kibana.alert.rule.exceptions_list": [ ], - "kibana.alert.rule.execution.timestamp": "2024-09-01T20:39:03.657Z", + "kibana.alert.rule.execution.timestamp": "2024-09-01T12:39:03.657Z", "kibana.alert.rule.execution.uuid": "939d34e1-1e74-480d-90ae-24079d9b40d3", "kibana.alert.rule.false_positives": [ "Custom role creations may be done by a system or network administrator. Verify whether the user email, resource name, and/or hostname should be making changes in your environment. Role creations by unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." @@ -657,12 +657,12 @@ "kibana.alert.rule.timestamp_override": "event.ingested", "kibana.alert.rule.to": "now", "kibana.alert.rule.type": "query", - "kibana.alert.rule.updated_at": "2024-09-01T20:39:00.099Z", + "kibana.alert.rule.updated_at": "2024-09-01T12:39:00.099Z", "kibana.alert.rule.updated_by": "elastic", "kibana.alert.rule.uuid": "c6f64115-5941-46ef-bfa3-61a4ecb4f3ba", "kibana.alert.rule.version": 104, "kibana.alert.severity": "medium", - "kibana.alert.start": "2024-09-01T20:39:03.657Z", + "kibana.alert.start": "2024-09-01T12:39:03.657Z", "kibana.alert.status": "active", "kibana.alert.uuid": "838ea37ab43ab7d2754d007fbe8191be53d7d637bea62f6189f8db1503c0e250", "kibana.alert.workflow_assignee_ids": [ diff --git a/x-pack/test/cloud_security_posture_functional/pages/alerts_flyout.ts b/x-pack/test/cloud_security_posture_functional/pages/alerts_flyout.ts index 619990d8e8281..63eafc4107bc1 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/alerts_flyout.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/alerts_flyout.ts @@ -17,9 +17,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'header', 'alerts']); const alertsPage = pageObjects.alerts; - // Failing: See https://github.com/elastic/kibana/issues/198632 - // Failing: See https://github.com/elastic/kibana/issues/198632 - describe.skip('Security Alerts Page - Graph visualization', function () { + describe('Security Alerts Page - Graph visualization', function () { this.tags(['cloud_security_posture_graph_viz']); before(async () => {