Skip to content

Commit

Permalink
[Security Solution][Detection Engine] Fixes date time errors when sou…
Browse files Browse the repository at this point in the history
…rce index does not have date time stamps (#79784)

## Summary

Right now even though it is not 100% ECS compliant a user can create a source index that has records which do not have the `@timestamp` but rather utilizes their own timestamp data and then within the detection engine they do an "override" to utilize that other timestamp.

To reproduce this simply add a lot of data records to an index and omit the `@timestamp` and then use the detection engine override to choose a different date timestamp. Before this fix you will see errors showing up during rule run even though it still does produce valid signals.

After this fix, you will not get errors showing up as we do not allow unusual things such as:

```ts
new Date(undefined).toISOString()
```

To occur which is what does the range throw. If you are on the estc server you can use the mapping I created called:

```ts
delme-alert-customer
```

With the override of `triggered`

### 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
  • Loading branch information
FrankHassanabad authored Oct 7, 2020
1 parent 8472bb7 commit 08fdcfc
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { getListArrayMock } from '../../../../common/detection_engine/schemas/ty
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates';

// @ts-expect-error
moment.suppressDeprecationWarnings = true;

import {
generateId,
parseInterval,
Expand All @@ -32,6 +35,7 @@ import {
createSearchAfterReturnType,
mergeReturns,
createTotalHitsFromSearchResult,
lastValidDate,
} from './utils';
import { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types';
import {
Expand All @@ -45,6 +49,7 @@ import {
sampleEmptyDocSearchResults,
sampleDocSearchResultsNoSortIdNoHits,
repeatedSearchResultsWithSortId,
sampleDocSearchResultsNoSortId,
} from './__mocks__/es_results';
import { ShardError } from '../../types';

Expand Down Expand Up @@ -967,6 +972,57 @@ describe('utils', () => {
const { success } = createSearchAfterReturnTypeFromResponse({ searchResult });
expect(success).toEqual(true);
});

test('It will not set an invalid date time stamp from a non-existent @timestamp when the index is not 100% ECS compliant', () => {
const searchResult = sampleDocSearchResultsNoSortId();
(searchResult.hits.hits[0]._source['@timestamp'] as unknown) = undefined;
const { lastLookBackDate } = createSearchAfterReturnTypeFromResponse({ searchResult });
expect(lastLookBackDate).toEqual(null);
});

test('It will not set an invalid date time stamp from a null @timestamp when the index is not 100% ECS compliant', () => {
const searchResult = sampleDocSearchResultsNoSortId();
(searchResult.hits.hits[0]._source['@timestamp'] as unknown) = null;
const { lastLookBackDate } = createSearchAfterReturnTypeFromResponse({ searchResult });
expect(lastLookBackDate).toEqual(null);
});

test('It will not set an invalid date time stamp from an invalid @timestamp string', () => {
const searchResult = sampleDocSearchResultsNoSortId();
(searchResult.hits.hits[0]._source['@timestamp'] as unknown) = 'invalid';
const { lastLookBackDate } = createSearchAfterReturnTypeFromResponse({ searchResult });
expect(lastLookBackDate).toEqual(null);
});
});

describe('lastValidDate', () => {
test('It returns undefined if the search result contains a null timestamp', () => {
const searchResult = sampleDocSearchResultsNoSortId();
(searchResult.hits.hits[0]._source['@timestamp'] as unknown) = null;
const date = lastValidDate(searchResult);
expect(date).toEqual(undefined);
});

test('It returns undefined if the search result contains a undefined timestamp', () => {
const searchResult = sampleDocSearchResultsNoSortId();
(searchResult.hits.hits[0]._source['@timestamp'] as unknown) = undefined;
const date = lastValidDate(searchResult);
expect(date).toEqual(undefined);
});

test('It returns undefined if the search result contains an invalid string value', () => {
const searchResult = sampleDocSearchResultsNoSortId();
(searchResult.hits.hits[0]._source['@timestamp'] as unknown) = 'invalid value';
const date = lastValidDate(searchResult);
expect(date).toEqual(undefined);
});

test('It returns correct date time stamp if the search result contains an invalid string value', () => {
const searchResult = sampleDocSearchResultsNoSortId();
(searchResult.hits.hits[0]._source['@timestamp'] as unknown) = 'invalid value';
const date = lastValidDate(searchResult);
expect(date).toEqual(undefined);
});
});

describe('createSearchAfterReturnType', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,17 +514,33 @@ export const createErrorsFromShard = ({ errors }: { errors: ShardError[] }): str
});
};

/**
* Given a SignalSearchResponse this will return a valid last date if it can find one, otherwise it
* will return undefined.
* @param result The result to try and parse out the timestamp.
*/
export const lastValidDate = (result: SignalSearchResponse): Date | undefined => {
if (result.hits.hits.length === 0) {
return undefined;
} else {
const lastTimestamp = result.hits.hits[result.hits.hits.length - 1]._source['@timestamp'];
const isValid = lastTimestamp != null && moment(lastTimestamp).isValid();
if (!isValid) {
return undefined;
} else {
return new Date(lastTimestamp);
}
}
};

export const createSearchAfterReturnTypeFromResponse = ({
searchResult,
}: {
searchResult: SignalSearchResponse;
}): SearchAfterAndBulkCreateReturnType => {
return createSearchAfterReturnType({
success: searchResult._shards.failed === 0,
lastLookBackDate:
searchResult.hits.hits.length > 0
? new Date(searchResult.hits.hits[searchResult.hits.hits.length - 1]?._source['@timestamp'])
: undefined,
lastLookBackDate: lastValidDate(searchResult),
});
};

Expand Down

0 comments on commit 08fdcfc

Please sign in to comment.