[Security Solution][Endpoint] Adds logic for checking and completing Sentinelone isolate and release actions#179013
Conversation
…nelone-isolate-release-async-complete
…nelone-isolate-release-async-complete
|
/ci |
|
/ci |
…+ split off ES mock utils
…e-release-async-complete' into task/olm-8895-sentinelone-isolate-release-async-complete
|
/ci |
1 similar comment
|
/ci |
…nelone-isolate-release-async-complete
|
/ci |
…e-release-async-complete' into task/olm-8895-sentinelone-isolate-release-async-complete
…nelone-isolate-release-async-complete # Conflicts: # x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts # x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts # x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts
…nelone-isolate-release-async-complete
…nelone-isolate-release-async-complete # Conflicts: # x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts # x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts
|
Pinging @elastic/security-defend-workflows (Team:Defend Workflows) |
…e-release-async-complete' into task/olm-8895-sentinelone-isolate-release-async-complete
💚 Build Succeeded
Metrics [docs]Module Count
Page load bundle
Unknown metric groupsAPI count
ESLint disabled line counts
Total ESLint disabled count
History
To update your PR or re-run it, just comment with: |
tomsonpl
left a comment
There was a problem hiding this comment.
Looks great, thanks for doing this. It helped me understand how pendingActions work so I could prepare similar for CS 👍
Also TILT: collapse search, I haven't tried it before 👍
I left basically two suggestions for you to consider (nothing blocking from merging) :
- put activity codes in enum
- refactor from using
_sourcetofieldsin search
| export interface SentinelOneActionRequestCommonMeta { | ||
| /** The S1 agent id */ | ||
| agentId: string; | ||
| /** The S1 agent assigned UUID */ |
There was a problem hiding this comment.
I have trouble understanding what is the difference between agentId and agentUUID - could you explain in more details?
There was a problem hiding this comment.
I guess one is referencing Elastic Agent with the integration?
There was a problem hiding this comment.
Not sure its the "official" answer, but:
In SentinelOne, agents have both an id and a uuid. It seems to me that the uuid can be re-generated - I think I saw some action on the agent there to re-assign. new UUID. The id seems to be more in line with how we use "id" in elastic and this also what is used internally in SentinelOne to trigger several actions or run scripts.
Because we are using the UUID of the agent in kibana we need to (in this case) store the (what I am calling "internal") id of the agent in SentinelOne with the action so that we can use it later without having to call their API again.
| index: SENTINEL_ONE_ACTIVITY_INDEX, | ||
| query, | ||
| // There may be many documents for each host/agent, so we collapse it and only get back the | ||
| // first one that came in after the isolate request was sent |
| command === 'isolate' | ||
| ? [ | ||
| // { | ||
| // "id": 1001 |
There was a problem hiding this comment.
would you consider putting keeping these IDs in enum ?
There was a problem hiding this comment.
I'm not a big fan of enums as they have some drawbacks. I did think about putting all of these codes in a const object, but at this time I am not seeing the need to use them outside of this code here, so I felt it was unnecessary.
| sort: [{ 'sentinel_one.activity.updated_at': 'asc' }], | ||
| }, | ||
| }, | ||
| // we don't need the source. The document will be stored in `inner_hits.first_found` |
There was a problem hiding this comment.
👍 I've heard fields is preferred above _source when possible. Does collapse work the same way as fields meaning it returns just the agent.id field, or the whole document?
There was a problem hiding this comment.
Not sure. Collapse (in my understanding) takes the search results, groups them by a given field and then applies additional logic to it. Son in this case, it groups them by S1 agent ID, then than it sorts those in ascending order, and then it takes the first (most recent) record from that and it stores it in a property called first_found.
You can see what collapse does by running this in the dev console (just update the Agent ids below):
GET logs-sentinel_one.activity-default/_search
{
"query": {
"bool": {
"must": [
{
"terms": {
"sentinel_one.activity.type": [
1001,
2010
]
}
}
],
"should": [
{
"bool": {
"filter": [
{ "term": { "sentinel_one.activity.agent.id": "1913920934584665209" } },
{ "range": { "sentinel_one.activity.updated_at": { "gt": "2024-04-02T12:59:22.229Z" } } }
]
}
}
],
"minimum_should_match": 1
}
},
"_source": false,
"collapse": {
"field": "sentinel_one.activity.agent.id",
"inner_hits": {
"name": "first_found",
"size": 1,
"sort": [ { "sentinel_one.activity.updated_at": "asc" } ]
}
},
"size": 1000,
"sort": [
{
"sentinel_one.activity.updated_at": {
"order": "desc"
}
}
]
}
you can also reference the docs here: https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html
| : actionRequest.agent.id, | ||
| data: { command }, | ||
| error: | ||
| activityLogEntryType === 2010 && command === 'isolate' |
There was a problem hiding this comment.
again, I think 2010 as an enum would help in readability
| hit.inner_hits = { | ||
| first_found: { | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| hits: { hits: [this.toEsSearchHit(hit._source!, hit._index)] }, |
There was a problem hiding this comment.
Consider refactoring from using _source to fields.
There was a problem hiding this comment.
you will need to provide real working examples for me to understand how fields would help here
| // due to use of `collapse | ||
| _source: false, | ||
| sort: [{ 'sentinel_one.activity.updated_at': { order: 'asc' } }], | ||
| size: 1000, |
There was a problem hiding this comment.
I presume this should be 10,000
There was a problem hiding this comment.
its very unlikely that we would even hit 1000 documents in this context... but sure, I'll update to 10k in my next PR.
| inner_hits: { | ||
| name: 'first_found', | ||
| size: 1, | ||
| sort: [{ 'sentinel_one.activity.updated_at': 'asc' }], |
There was a problem hiding this comment.
I believe, the sort order for sentinel_one.activity.updated_at in the inner_hits as well as the outer query should be desc so that the most recent item is first in the array/list.
There was a problem hiding this comment.
no... it should be asc here based on the approach we need to take to determine if the action was complete. I can explain more if you like, but essentially: you want the first document that was received where the updated_at value is greater than when the response action was sent
|
Thanks @ashokaditya .
Yes, that is expected for now. My guess is that you are seeing |
Correct. Same for release. The isolated badge disappears while the action is pending. However, VM was inaccessible at the same instance when the status showed |

Summary
Adds checks for
isolateandreleaseresponse action for SentinelOne to see if they have completed. Changes include:isolateandreleaseare no longer marked "complete" once request is sent to SentinelOne. They are kept inpendingstatelogs-sentinel_one.activity-default) and check ifisolateandreleaseaction were completeserver/endpoint/mocks.tsfile to its own directory (server/endpoint/mocks/**)server/endpoint/mockspath.utils.mock.tsthat includes 1 new generic utilty -applyEsClientSearchMock()Sample output to kibana log (when running with `debug` mode)
Output showing completion of an Isolate action by checking SentinelOne's activity log:
How to Test
node x-pack/plugins/security_solution/scripts/endpoint/run_sentinelone_host.js. The credentials needed to setup the host can be retrieved from here.isolateorreleasethe host. The response actions should remain inpendinguntil the action is complete in SentinelOne and data (sentinelone activity logs) is ingested back to elasticsearch. NOTE: this could take a bit of time depending on the frequency setup in the integration policy for sentineloneChecklist