[Security Solution][Detection Engine] Fixes date time errors when source 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
This commit is contained in:
Frank Hassanabad 2020-10-06 18:18:19 -06:00 committed by GitHub
parent 8472bb7d10
commit 08fdcfc621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 4 deletions

View file

@ -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,
@ -32,6 +35,7 @@ import {
createSearchAfterReturnType,
mergeReturns,
createTotalHitsFromSearchResult,
lastValidDate,
} from './utils';
import { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types';
import {
@ -45,6 +49,7 @@ import {
sampleEmptyDocSearchResults,
sampleDocSearchResultsNoSortIdNoHits,
repeatedSearchResultsWithSortId,
sampleDocSearchResultsNoSortId,
} from './__mocks__/es_results';
import { ShardError } from '../../types';
@ -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', () => {

View file

@ -514,6 +514,25 @@ 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,
}: {
@ -521,10 +540,7 @@ export const createSearchAfterReturnTypeFromResponse = ({
}): 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),
});
};