Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cloud Security] Fixed failing FTR #199683

Merged
merged 9 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => <div data-test-subj={GRAPH_PREVIEW_TEST_ID} />;

jest.mock('@kbn/cloud-security-posture-graph', () => {
Expand Down Expand Up @@ -64,7 +55,11 @@ describe('<GraphPreviewContainer />', () => {
data: { nodes: [], edges: [] },
});

const timestamp = new Date().toISOString();

(useGraphPreview as jest.Mock).mockReturnValue({
timestamp,
eventIds: [],
isAuditLog: true,
});

Expand All @@ -87,9 +82,23 @@ describe('<GraphPreviewContainer />', () => {
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,
Expand All @@ -100,10 +109,10 @@ describe('<GraphPreviewContainer />', () => {
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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ExpandablePanel
header={{
title: (
<FormattedMessage
id="xpack.securitySolution.flyout.right.visualizations.graphPreview.graphPreviewTitle"
defaultMessage="Graph preview"
/>
),
iconType: 'indexMapping',
}}
data-test-subj={GRAPH_PREVIEW_TEST_ID}
content={
!graphFetchQuery.isLoading && !graphFetchQuery.isError
? {
paddingSize: 'none',
}
: undefined
}
>
<GraphPreview
isLoading={graphFetchQuery.isLoading}
isError={graphFetchQuery.isError}
data={graphFetchQuery.data}
/>
</ExpandablePanel>
isAuditLog && (
<ExpandablePanel
header={{
title: (
<FormattedMessage
id="xpack.securitySolution.flyout.right.visualizations.graphPreview.graphPreviewTitle"
defaultMessage="Graph preview"
/>
),
iconType: 'indexMapping',
}}
data-test-subj={GRAPH_PREVIEW_TEST_ID}
content={
!isLoading && !isError
? {
paddingSize: 'none',
}
: undefined
}
>
<GraphPreview isLoading={isLoading} isError={isError} data={data} />
</ExpandablePanel>
)
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};
Expand All @@ -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`, () => {
Expand All @@ -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`, () => {
Expand All @@ -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`, () => {
Expand All @@ -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`, () => {
Expand All @@ -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']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -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
*/
Expand All @@ -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)
Expand All @@ -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 };
};
20 changes: 17 additions & 3 deletions x-pack/test/cloud_security_posture_api/routes/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -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');
});
});
});
}
Loading