-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SecuritySolution][EntityAnalytics] Risk Scoring Preview API #155966
Merged
Merged
Changes from all commits
Commits
Show all changes
103 commits
Select commit
Hold shift + click to select a range
46ac1b4
Add basic route for requesting risk scores
rylnd 6f27d82
Fleshing out the logic of calculating risk scores
rylnd 62c660e
Add typings and simple version of top (riskiest) inputs
rylnd a089398
Camel case on the outgoing response
rylnd bd9f562
Add ability to return full risk inputs based on a request parameter
rylnd 26cafcd
Adds helper function for retrieval of input documents
rylnd 16a0d5e
Respect filters param in endpoint
rylnd d2d81ba
Request risk inputs via current user
rylnd cd1e5b1
Adds raw score and risk level to response
rylnd cf86293
Endpoint now respects the `range` parameters
rylnd 96481be
Limit the number of risk inputs processed to the first million
rylnd 9f0080b
Add scoring notes to the response
rylnd ebea202
Return an error response if our scoring query blows up
rylnd 4223ba2
Add note from marshall about potential performance improvement
rylnd b35f3e5
Filters passed via the API are a single object
rylnd 229e1af
Rename parameter to reflect (lack of) plurality
rylnd 1b49fc3
Retrieve the alerts index via our app client
rylnd 715d8c8
Retrieve the index pattern from the provided dataViewId
rylnd c05497b
Exclude _source from top-level risk score query
rylnd bbda87e
Respect identifier_type parameter in UI
rylnd 5393610
Add "debug" mode to risk score API
rylnd 4b9447a
Log the request/response to kibana in debug mode
rylnd 66c6afe
Better error handling
rylnd 0edf2af
Log at the info level
rylnd 197e749
Attempt to instrument our scoring calculation
rylnd 3884985
Return the max number of identities for the implementation
rylnd 76a2b9b
Tests for risk score
nkhristinin 765e0a3
Add more tests
nkhristinin 60d5e41
Decompose test helper function into component pieces
rylnd 45f2846
Remove obfuscating helper function
rylnd 527fb50
Only generate a uuid if we need it
rylnd 9b395f7
Rename helper method
rylnd a6b4826
Adds initial risk weight calculation
rylnd 9d132fb
Convert Risk Score aggregation to composite agg
rylnd e969fc2
Weights are specified as an array
rylnd e2cb1ad
Add order-dependent integration test
rylnd 4668975
Update usage of test helper
rylnd 54c0828
Update risk engine integration test descriptions
rylnd 08bfacb
Update score fields to include category scores
rylnd dd229d4
WIP: Applying category weights to our scoring aggregation
rylnd bb951b0
Sort risk inputs by their category-weighted scores
rylnd a1c8e91
Address persistence of signals by scoping our scoring
rylnd b192bb4
Revert "Address persistence of signals by scoping our scoring"
rylnd 31711c7
Delete documents instead of the index
rylnd 154ccc5
Finalize initial category weight test
rylnd 2965612
Add new client method to mock object
rylnd 3c47ed7
Add some tests for our risk score route
rylnd ceb5ab8
Add tests around calculateRiskScores function
rylnd 59b7c40
Update ticket to respect new alert limit
rylnd 5cb1564
Merge branch 'main' into risk_score_api
rylnd f9a6e84
Define a parameter for the size of our terms' aggregation
rylnd 80819ee
Allow pagination of the risk score endpoint
rylnd 432c04d
Add integration tests for pagination and filtering
rylnd 77414dd
Merge branch 'main' into risk_score_api
rylnd 974795c
Merge branch 'main' into risk_score_api
rylnd 350fe36
Add stricter request schema to remove need for type cast
rylnd ba949c8
Return top 10 riskiest inputs
rylnd c395c52
Add initial draft of OpenAPI spec for Risk scoring route
rylnd 956b866
Document risk score response schema
rylnd 194886a
Merge branch 'main' into risk_score_api
rylnd d74757b
Fix type error
rylnd 5836677
Merge branch 'main' into risk_score_api
rylnd ed9075c
Add descriptions to most of our endpoint specification
rylnd 33a6de2
Fix issue with $ref removing sibling keys
rylnd 91c238d
Merge branch 'main' into risk_score_api
rylnd 7172601
Merge branch 'main' into risk_score_api
rylnd 6aea611
Remove pending route tests
rylnd 7d8d15f
More accurate parameter name/value
rylnd 419f9e0
Refactor construction of our identity aggregations
rylnd fd00984
More generic parameter name, exposed as API param
rylnd 7b3e360
Remove unused type
rylnd a1457b1
Remove unused response schema
rylnd 20395b7
Merge branch 'main' into risk_score_api
rylnd ac55ca0
Remove temporary test helper in favor of permanent upstream one
rylnd 7574f54
Filter parameter is an object, not an array
rylnd 0b17b54
Move endpoint location and naming to reflect its 'preview' behavior
rylnd 9d8b193
Updates feature flag to be more general/accurate
rylnd 86d786d
Update API spec to reflect the 'preview' orientation
rylnd 097460e
Fix outdated import/constant
rylnd 99ab426
Merge branch 'main' into risk_score_api
rylnd c1b4048
Remove accidentally-committed notes
rylnd c367435
Merge branch 'main' into risk_score_api
rylnd fde5717
Merge branch 'main' into risk_score_api
rylnd a4b7760
Remove auxiliary helper function
rylnd 28cd88e
Adds a type representing probabilities/weights
rylnd 6a9321a
Export and use our new io-ts type for weight values
rylnd 69d21aa
Replace some special constants with enums
rylnd 15591b8
Test stricter weight param validation
rylnd 7aec3de
Rename request schema for accuracy
rylnd 7dd7266
More strict schema validations on weighting
rylnd 2448f1c
Merge branch 'main' into risk_score_api
rylnd 302a153
Adds a few more tests around allowed 'type's and 'value's
rylnd c53a7ed
Fix imports
rylnd 830edeb
Use our new schema in the API
rylnd 6dcce07
Replace our static type for risk weights with the io-ts type
rylnd 64e5c1d
Updates our identifierWeights schema
rylnd 0d6c252
Move IdentifierType definition to one from io-ts
rylnd fc98b91
Move after_keys to io-ts
rylnd f6671bb
Some reorganization for consistency
rylnd a31d7eb
Ensure that we're validating our date range
rylnd 370540b
Merge branch 'main' into risk_score_api
rylnd 95895f6
Return a 404 if specified dataview is not found
rylnd 8b3f52b
Merge branch 'main' into risk_score_api
rylnd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
.../kbn-securitysolution-io-ts-types/src/number_between_zero_and_one_inclusive/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import { pipe } from 'fp-ts/lib/pipeable'; | ||
import { left } from 'fp-ts/lib/Either'; | ||
import { NumberBetweenZeroAndOneInclusive } from '.'; | ||
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; | ||
|
||
describe('NumberBetweenZeroAndOneInclusive', () => { | ||
test('it should validate 1', () => { | ||
const payload = 1; | ||
const decoded = NumberBetweenZeroAndOneInclusive.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual(payload); | ||
}); | ||
|
||
test('it should validate a zero', () => { | ||
const payload = 0; | ||
const decoded = NumberBetweenZeroAndOneInclusive.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual(payload); | ||
}); | ||
|
||
test('it should validate a float between 0 and 1', () => { | ||
const payload = 0.58; | ||
const decoded = NumberBetweenZeroAndOneInclusive.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual(payload); | ||
}); | ||
|
||
test('it should NOT validate a negative number', () => { | ||
const payload = -1; | ||
const decoded = NumberBetweenZeroAndOneInclusive.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([ | ||
'Invalid value "-1" supplied to "NumberBetweenZeroAndOneInclusive"', | ||
]); | ||
expect(message.schema).toEqual({}); | ||
}); | ||
|
||
test('it should NOT validate NaN', () => { | ||
const payload = NaN; | ||
const decoded = NumberBetweenZeroAndOneInclusive.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([ | ||
'Invalid value "NaN" supplied to "NumberBetweenZeroAndOneInclusive"', | ||
]); | ||
expect(message.schema).toEqual({}); | ||
}); | ||
|
||
test('it should NOT validate Infinity', () => { | ||
const payload = Infinity; | ||
const decoded = NumberBetweenZeroAndOneInclusive.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([ | ||
'Invalid value "Infinity" supplied to "NumberBetweenZeroAndOneInclusive"', | ||
]); | ||
expect(message.schema).toEqual({}); | ||
}); | ||
|
||
test('it should NOT validate a string', () => { | ||
const payload = 'some string'; | ||
const decoded = NumberBetweenZeroAndOneInclusive.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([ | ||
'Invalid value "some string" supplied to "NumberBetweenZeroAndOneInclusive"', | ||
]); | ||
expect(message.schema).toEqual({}); | ||
}); | ||
}); |
28 changes: 28 additions & 0 deletions
28
packages/kbn-securitysolution-io-ts-types/src/number_between_zero_and_one_inclusive/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import * as t from 'io-ts'; | ||
import { Either } from 'fp-ts/lib/Either'; | ||
|
||
/** | ||
* Types a number between 0 and 1 inclusive. Useful for specifying a probability, weighting, etc. | ||
*/ | ||
export const NumberBetweenZeroAndOneInclusive = new t.Type<number, number, unknown>( | ||
'NumberBetweenZeroAndOneInclusive', | ||
t.number.is, | ||
(input, context): Either<t.Errors, number> => { | ||
return typeof input === 'number' && | ||
!Number.isNaN(input) && | ||
Number.isFinite(input) && | ||
input >= 0 && | ||
input <= 1 | ||
? t.success(input) | ||
: t.failure(input, context); | ||
}, | ||
t.identity | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
x-pack/plugins/security_solution/common/risk_engine/after_keys.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { pipe } from 'fp-ts/lib/pipeable'; | ||
import { left } from 'fp-ts/lib/Either'; | ||
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; | ||
|
||
import { afterKeysSchema } from './after_keys'; | ||
|
||
describe('after_keys schema', () => { | ||
it('allows an empty object', () => { | ||
const payload = {}; | ||
const decoded = afterKeysSchema.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual(payload); | ||
}); | ||
|
||
it('allows a valid host key', () => { | ||
const payload = { host: { 'host.name': 'hello' } }; | ||
const decoded = afterKeysSchema.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual(payload); | ||
}); | ||
|
||
it('allows a valid user key', () => { | ||
const payload = { user: { 'user.name': 'hello' } }; | ||
const decoded = afterKeysSchema.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual(payload); | ||
}); | ||
|
||
it('allows both valid host and user keys', () => { | ||
const payload = { user: { 'user.name': 'hello' }, host: { 'host.name': 'hello' } }; | ||
const decoded = afterKeysSchema.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual(payload); | ||
}); | ||
|
||
it('removes an unknown identifier key if used', () => { | ||
const payload = { bad: 'key' }; | ||
const decoded = afterKeysSchema.decode(payload); | ||
const message = pipe(decoded, foldLeftRight); | ||
|
||
expect(getPaths(left(message.errors))).toEqual([]); | ||
expect(message.schema).toEqual({}); | ||
}); | ||
}); |
21 changes: 21 additions & 0 deletions
21
x-pack/plugins/security_solution/common/risk_engine/after_keys.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import * as t from 'io-ts'; | ||
|
||
const afterKeySchema = t.record(t.string, t.string); | ||
export type AfterKeySchema = t.TypeOf<typeof afterKeySchema>; | ||
export type AfterKey = AfterKeySchema; | ||
|
||
export const afterKeysSchema = t.exact( | ||
t.partial({ | ||
host: afterKeySchema, | ||
user: afterKeySchema, | ||
}) | ||
); | ||
export type AfterKeysSchema = t.TypeOf<typeof afterKeysSchema>; | ||
export type AfterKeys = AfterKeysSchema; |
12 changes: 12 additions & 0 deletions
12
x-pack/plugins/security_solution/common/risk_engine/identifier_types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import * as t from 'io-ts'; | ||
|
||
export const identifierTypeSchema = t.keyof({ user: null, host: null }); | ||
export type IdentifierTypeSchema = t.TypeOf<typeof identifierTypeSchema>; | ||
export type IdentifierType = IdentifierTypeSchema; |
10 changes: 10 additions & 0 deletions
10
x-pack/plugins/security_solution/common/risk_engine/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
export * from './after_keys'; | ||
export * from './risk_weights'; | ||
export * from './identifier_types'; |
29 changes: 29 additions & 0 deletions
29
x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import * as t from 'io-ts'; | ||
import { DataViewId } from '../../detection_engine/rule_schema'; | ||
import { afterKeysSchema } from '../after_keys'; | ||
import { identifierTypeSchema } from '../identifier_types'; | ||
import { riskWeightsSchema } from '../risk_weights/schema'; | ||
|
||
export const riskScorePreviewRequestSchema = t.exact( | ||
t.partial({ | ||
after_keys: afterKeysSchema, | ||
data_view_id: DataViewId, | ||
debug: t.boolean, | ||
filter: t.unknown, | ||
page_size: t.number, | ||
identifier_type: identifierTypeSchema, | ||
range: t.type({ | ||
start: t.string, | ||
end: t.string, | ||
}), | ||
weights: riskWeightsSchema, | ||
}) | ||
); | ||
export type RiskScorePreviewRequestSchema = t.TypeOf<typeof riskScorePreviewRequestSchema>; |
9 changes: 9 additions & 0 deletions
9
x-pack/plugins/security_solution/common/risk_engine/risk_weights/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
export * from './types'; | ||
export type { RiskWeight, RiskWeights, GlobalRiskWeight, RiskCategoryRiskWeight } from './schema'; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure of a reasonable default here. This corresponds to the
size
of the composite agg response, so:But the number of unique identities in a system is going to be quite variable, and I'm bad at guesstimates. Help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional context: this was previously written as a
terms
agg, with a size of 65k terms. We decided against that route as it would limit the total number of identities that we could collect, but now we've got to decide on how to iterate through an undefined number of identities.