[Security Solution][Analyzer] Make all analyzer apis have time range as optional (#142536)

This commit is contained in:
Kevin Qualters 2022-10-04 09:09:55 -04:00 committed by GitHub
parent f5f60b640b
commit 890bf7430c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 98 additions and 121 deletions

View file

@ -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()),

View file

@ -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);

View file

@ -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;

View file

@ -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[]> {

View file

@ -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;

View file

@ -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({

View file

@ -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;

View file

@ -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;

View file

@ -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({

View file

@ -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,

View file

@ -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;

View file

@ -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({

View file

@ -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>;

View file

@ -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(),
],
},
},

View file

@ -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;