Skip to content

Commit

Permalink
[Cloud Security] Fixed failing FTR (elastic#199683)
Browse files Browse the repository at this point in the history
## Summary

fixes: elastic#198632

FTR failed due to the usage of relative start and end when querying for
the graph.

Together with @tinnytintin10 we decided it is safe to assume the time to
query would be 30 minutes before and after the alert.

**Some enhancements and fixes:**
- Disabled re-fetch /graph API when window focus returned

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
  • Loading branch information
kfirpeled authored and CAWilson94 committed Nov 18, 2024
1 parent ceeda4f commit 07fb226
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 79 deletions.
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

0 comments on commit 07fb226

Please sign in to comment.