mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Analyzer] Make all analyzer apis have time range as optional (#142536)
This commit is contained in:
parent
f5f60b640b
commit
890bf7430c
15 changed files with 98 additions and 121 deletions
|
@ -58,10 +58,12 @@ export const validateEvents = {
|
|||
afterEvent: schema.maybe(schema.string()),
|
||||
}),
|
||||
body: schema.object({
|
||||
timeRange: schema.object({
|
||||
from: schema.string(),
|
||||
to: schema.string(),
|
||||
}),
|
||||
timeRange: schema.maybe(
|
||||
schema.object({
|
||||
from: schema.string(),
|
||||
to: schema.string(),
|
||||
})
|
||||
),
|
||||
indexPatterns: schema.arrayOf(schema.string()),
|
||||
filter: schema.maybe(schema.string()),
|
||||
entityType: schema.maybe(schema.string()),
|
||||
|
|
|
@ -28,12 +28,12 @@ describe('Analyze events view for alerts', () => {
|
|||
waitForAlertsToPopulate();
|
||||
});
|
||||
|
||||
it('should render analyzer when button is clicked', () => {
|
||||
it('should render when button is clicked', () => {
|
||||
openAnalyzerForFirstAlertInTimeline();
|
||||
cy.get(ANALYZER_NODE).first().should('be.visible');
|
||||
});
|
||||
|
||||
it(`should render an analyzer view and display
|
||||
it(`should display
|
||||
a toast indicating the date range of found events when a time range has 0 events in it`, () => {
|
||||
const dateContainingZeroEvents = 'Jul 27, 2022 @ 00:00:00.000';
|
||||
setStartDate(dateContainingZeroEvents);
|
||||
|
|
|
@ -17,6 +17,17 @@ import type {
|
|||
ResolverSchema,
|
||||
} from '../../../common/endpoint/types';
|
||||
|
||||
function getRangeFilter(timeRange: TimeRange | undefined) {
|
||||
return timeRange
|
||||
? {
|
||||
timeRange: {
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
},
|
||||
}
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The data access layer for resolver. All communication with the Kibana server is done through this object. This object is provided to Resolver. In tests, a mock data access layer can be used instead.
|
||||
*/
|
||||
|
@ -34,7 +45,7 @@ export function dataAccessLayerFactory(
|
|||
indexPatterns,
|
||||
}: {
|
||||
entityID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<ResolverRelatedEvents> {
|
||||
const response: ResolverPaginatedEvents = await context.services.http.post(
|
||||
|
@ -43,10 +54,7 @@ export function dataAccessLayerFactory(
|
|||
query: {},
|
||||
body: JSON.stringify({
|
||||
indexPatterns,
|
||||
timeRange: {
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
},
|
||||
...getRangeFilter(timeRange),
|
||||
filter: JSON.stringify({
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -76,16 +84,13 @@ export function dataAccessLayerFactory(
|
|||
entityID: string;
|
||||
category: string;
|
||||
after?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<ResolverPaginatedEvents> {
|
||||
const commonFields = {
|
||||
query: { afterEvent: after, limit: 25 },
|
||||
body: {
|
||||
timeRange: {
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
},
|
||||
...getRangeFilter(timeRange),
|
||||
indexPatterns,
|
||||
},
|
||||
};
|
||||
|
@ -127,30 +132,28 @@ export function dataAccessLayerFactory(
|
|||
limit,
|
||||
}: {
|
||||
ids: string[];
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
limit: number;
|
||||
}): Promise<SafeResolverEvent[]> {
|
||||
const query = {
|
||||
query: { limit },
|
||||
body: JSON.stringify({
|
||||
indexPatterns,
|
||||
...getRangeFilter(timeRange),
|
||||
filter: JSON.stringify({
|
||||
bool: {
|
||||
filter: [
|
||||
{ terms: { 'process.entity_id': ids } },
|
||||
{ term: { 'event.category': 'process' } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
};
|
||||
const response: ResolverPaginatedEvents = await context.services.http.post(
|
||||
'/api/endpoint/resolver/events',
|
||||
{
|
||||
query: { limit },
|
||||
body: JSON.stringify({
|
||||
timeRange: {
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
},
|
||||
indexPatterns,
|
||||
filter: JSON.stringify({
|
||||
bool: {
|
||||
filter: [
|
||||
{ terms: { 'process.entity_id': ids } },
|
||||
{ term: { 'event.category': 'process' } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}
|
||||
query
|
||||
);
|
||||
return response.events;
|
||||
},
|
||||
|
@ -172,7 +175,7 @@ export function dataAccessLayerFactory(
|
|||
eventTimestamp: string;
|
||||
eventID?: string | number;
|
||||
winlogRecordID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<SafeResolverEvent | null> {
|
||||
/** @description - eventID isn't provided by winlog. This can be removed once runtime fields are available */
|
||||
|
@ -200,10 +203,7 @@ export function dataAccessLayerFactory(
|
|||
query: { limit: 1 },
|
||||
body: JSON.stringify({
|
||||
indexPatterns,
|
||||
timeRange: {
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
},
|
||||
...getRangeFilter(timeRange),
|
||||
filter: JSON.stringify(filter),
|
||||
}),
|
||||
}
|
||||
|
@ -217,10 +217,7 @@ export function dataAccessLayerFactory(
|
|||
query: { limit: 1 },
|
||||
body: JSON.stringify({
|
||||
indexPatterns,
|
||||
timeRange: {
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
},
|
||||
...getRangeFilter(timeRange),
|
||||
entityType: 'alertDetail',
|
||||
eventID,
|
||||
}),
|
||||
|
@ -250,7 +247,7 @@ export function dataAccessLayerFactory(
|
|||
}: {
|
||||
dataId: string;
|
||||
schema: ResolverSchema;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indices: string[];
|
||||
ancestors: number;
|
||||
descendants: number;
|
||||
|
|
|
@ -63,7 +63,7 @@ export function generateTreeWithDAL(
|
|||
indexPatterns,
|
||||
}: {
|
||||
entityID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<ResolverRelatedEvents> {
|
||||
const node = allNodes.get(entityID);
|
||||
|
@ -88,7 +88,7 @@ export function generateTreeWithDAL(
|
|||
entityID: string;
|
||||
category: string;
|
||||
after?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
|
||||
const node = allNodes.get(entityID);
|
||||
|
@ -119,7 +119,7 @@ export function generateTreeWithDAL(
|
|||
eventCategory: string[];
|
||||
eventTimestamp: string;
|
||||
eventID?: string | number;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<SafeResolverEvent | null> {
|
||||
return null;
|
||||
|
@ -135,7 +135,7 @@ export function generateTreeWithDAL(
|
|||
limit,
|
||||
}: {
|
||||
ids: string[];
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
limit: number;
|
||||
}): Promise<SafeResolverEvent[]> {
|
||||
|
|
|
@ -59,7 +59,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
|
|||
indexPatterns,
|
||||
}: {
|
||||
entityID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<ResolverRelatedEvents> {
|
||||
return Promise.resolve({
|
||||
|
@ -83,7 +83,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
|
|||
entityID: string;
|
||||
category: string;
|
||||
after?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<{
|
||||
events: SafeResolverEvent[];
|
||||
|
@ -110,7 +110,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
|
|||
eventTimestamp: string;
|
||||
eventID?: string | number;
|
||||
winlogRecordID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<SafeResolverEvent | null> {
|
||||
return null;
|
||||
|
|
|
@ -64,7 +64,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
|
|||
indexPatterns,
|
||||
}: {
|
||||
entityID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<ResolverRelatedEvents> {
|
||||
return Promise.resolve({
|
||||
|
@ -90,7 +90,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
|
|||
entityID: string;
|
||||
category: string;
|
||||
after?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<{
|
||||
events: SafeResolverEvent[];
|
||||
|
@ -121,7 +121,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
|
|||
eventTimestamp: string;
|
||||
eventID?: string | number;
|
||||
winlogRecordID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<SafeResolverEvent | null> {
|
||||
return mockEndpointEvent({
|
||||
|
|
|
@ -67,7 +67,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
|
|||
indexPatterns,
|
||||
}: {
|
||||
entityID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<ResolverRelatedEvents> {
|
||||
/**
|
||||
|
@ -97,7 +97,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
|
|||
entityID: string;
|
||||
category: string;
|
||||
after?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
|
||||
const events =
|
||||
|
@ -129,7 +129,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
|
|||
eventTimestamp: string;
|
||||
eventID?: string | number;
|
||||
winlogRecordID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<SafeResolverEvent | null> {
|
||||
return relatedEvents.events.find((event) => eventModel.eventID(event) === eventID) ?? null;
|
||||
|
|
|
@ -58,7 +58,7 @@ export function oneNodeWithPaginatedEvents(): {
|
|||
indexPatterns,
|
||||
}: {
|
||||
entityID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<ResolverRelatedEvents> {
|
||||
/**
|
||||
|
@ -86,7 +86,7 @@ export function oneNodeWithPaginatedEvents(): {
|
|||
entityID: string;
|
||||
category: string;
|
||||
after?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
|
||||
let events: SafeResolverEvent[] = [];
|
||||
|
@ -121,7 +121,7 @@ export function oneNodeWithPaginatedEvents(): {
|
|||
eventTimestamp: string;
|
||||
eventID?: string | number;
|
||||
winlogRecordID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}): Promise<SafeResolverEvent | null> {
|
||||
return mockTree.events.find((event) => eventModel.eventID(event) === eventID) ?? null;
|
||||
|
|
|
@ -48,8 +48,9 @@ export function CurrentRelatedEventFetcher(
|
|||
api.dispatch({
|
||||
type: 'appRequestedCurrentRelatedEventData',
|
||||
});
|
||||
const timeRangeFilters = selectors.timeRangeFilters(state);
|
||||
|
||||
const detectedBounds = selectors.detectedBounds(state);
|
||||
const timeRangeFilters =
|
||||
detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
|
||||
let result: SafeResolverEvent | null = null;
|
||||
try {
|
||||
result = await dataAccessLayer.event({
|
||||
|
|
|
@ -60,7 +60,9 @@ export function NodeDataFetcher(
|
|||
|
||||
let results: SafeResolverEvent[] | undefined;
|
||||
try {
|
||||
const timeRangeFilters = selectors.timeRangeFilters(state);
|
||||
const detectedBounds = selectors.detectedBounds(state);
|
||||
const timeRangeFilters =
|
||||
detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
|
||||
results = await dataAccessLayer.nodeData({
|
||||
ids: Array.from(newIDsToRequest),
|
||||
timeRange: timeRangeFilters,
|
||||
|
|
|
@ -30,7 +30,9 @@ export function RelatedEventsFetcher(
|
|||
const indices = selectors.eventIndices(state);
|
||||
|
||||
const oldParams = last;
|
||||
const timeRangeFilters = selectors.timeRangeFilters(state);
|
||||
const detectedBounds = selectors.detectedBounds(state);
|
||||
const timeRangeFilters =
|
||||
detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
|
||||
// Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info.
|
||||
last = newParams;
|
||||
|
||||
|
|
|
@ -93,9 +93,9 @@ export function ResolverTreeFetcher(
|
|||
descendants: descendantsRequestAmount(),
|
||||
});
|
||||
if (unboundedTree.length > 0) {
|
||||
const timestamps = unboundedTree.map((event) =>
|
||||
firstNonNullValue(event.data['@timestamp'])
|
||||
);
|
||||
const timestamps = unboundedTree
|
||||
.map((event) => firstNonNullValue(event.data['@timestamp']))
|
||||
.sort();
|
||||
const oldestTimestamp = timestamps[0];
|
||||
const newestTimestamp = timestamps.slice(-1);
|
||||
api.dispatch({
|
||||
|
|
|
@ -692,7 +692,7 @@ export interface DataAccessLayer {
|
|||
indexPatterns,
|
||||
}: {
|
||||
entityID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}) => Promise<ResolverRelatedEvents>;
|
||||
|
||||
|
@ -710,7 +710,7 @@ export interface DataAccessLayer {
|
|||
entityID: string;
|
||||
category: string;
|
||||
after?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}) => Promise<ResolverPaginatedEvents>;
|
||||
|
||||
|
@ -725,7 +725,7 @@ export interface DataAccessLayer {
|
|||
limit,
|
||||
}: {
|
||||
ids: string[];
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
limit: number;
|
||||
}): Promise<SafeResolverEvent[]>;
|
||||
|
@ -747,7 +747,7 @@ export interface DataAccessLayer {
|
|||
eventTimestamp: string;
|
||||
eventID?: string | number;
|
||||
winlogRecordID: string;
|
||||
timeRange: TimeRange;
|
||||
timeRange?: TimeRange;
|
||||
indexPatterns: string[];
|
||||
}) => Promise<SafeResolverEvent | null>;
|
||||
|
||||
|
|
|
@ -11,31 +11,22 @@ import type { JsonObject, JsonValue } from '@kbn/utility-types';
|
|||
import { parseFilterQuery } from '../../../../utils/serialized_query';
|
||||
import type { SafeResolverEvent } from '../../../../../common/endpoint/types';
|
||||
import type { PaginationBuilder } from '../utils/pagination';
|
||||
|
||||
interface TimeRange {
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
import { BaseResolverQuery } from '../tree/queries/base';
|
||||
import type { ResolverQueryParams } from '../tree/queries/base';
|
||||
|
||||
/**
|
||||
* Builds a query for retrieving events.
|
||||
*/
|
||||
export class EventsQuery {
|
||||
private readonly pagination: PaginationBuilder;
|
||||
private readonly indexPatterns: string | string[];
|
||||
private readonly timeRange: TimeRange;
|
||||
export class EventsQuery extends BaseResolverQuery {
|
||||
readonly pagination: PaginationBuilder;
|
||||
constructor({
|
||||
pagination,
|
||||
indexPatterns,
|
||||
timeRange,
|
||||
}: {
|
||||
pagination: PaginationBuilder;
|
||||
indexPatterns: string | string[];
|
||||
timeRange: TimeRange;
|
||||
}) {
|
||||
isInternalRequest,
|
||||
pagination,
|
||||
}: ResolverQueryParams & { pagination: PaginationBuilder }) {
|
||||
super({ indexPatterns, timeRange, isInternalRequest });
|
||||
this.pagination = pagination;
|
||||
this.indexPatterns = indexPatterns;
|
||||
this.timeRange = timeRange;
|
||||
}
|
||||
|
||||
private query(filters: JsonObject[]): JsonObject {
|
||||
|
@ -44,15 +35,7 @@ export class EventsQuery {
|
|||
bool: {
|
||||
filter: [
|
||||
...filters,
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
{
|
||||
term: { 'event.kind': 'event' },
|
||||
},
|
||||
|
@ -71,15 +54,7 @@ export class EventsQuery {
|
|||
{
|
||||
term: { 'event.id': id },
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -97,15 +72,7 @@ export class EventsQuery {
|
|||
{
|
||||
term: { 'process.entity_id': id },
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: this.timeRange.from,
|
||||
lte: this.timeRange.to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
...this.getRangeFilter(),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -11,10 +11,10 @@ import type { TimeRange } from '../utils';
|
|||
import { resolverFields } from '../utils';
|
||||
|
||||
export interface ResolverQueryParams {
|
||||
readonly schema: ResolverSchema;
|
||||
readonly schema?: ResolverSchema;
|
||||
readonly indexPatterns: string | string[];
|
||||
readonly timeRange: TimeRange | undefined;
|
||||
readonly isInternalRequest: boolean;
|
||||
readonly isInternalRequest?: boolean;
|
||||
readonly resolverFields?: JsonValue[];
|
||||
getRangeFilter?: () => Array<{
|
||||
range: { '@timestamp': { gte: string; lte: string; format: string } };
|
||||
|
@ -25,12 +25,18 @@ export class BaseResolverQuery implements ResolverQueryParams {
|
|||
readonly schema: ResolverSchema;
|
||||
readonly indexPatterns: string | string[];
|
||||
readonly timeRange: TimeRange | undefined;
|
||||
readonly isInternalRequest: boolean;
|
||||
readonly isInternalRequest?: boolean;
|
||||
readonly resolverFields?: JsonValue[];
|
||||
|
||||
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) {
|
||||
this.resolverFields = resolverFields(schema);
|
||||
this.schema = schema;
|
||||
const schemaOrDefault = schema
|
||||
? schema
|
||||
: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
};
|
||||
this.resolverFields = resolverFields(schemaOrDefault);
|
||||
this.schema = schemaOrDefault;
|
||||
this.indexPatterns = indexPatterns;
|
||||
this.timeRange = timeRange;
|
||||
this.isInternalRequest = isInternalRequest;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue