Skip to content

[Security Solution][Endpoint] Change Response Actions client for SentinelOne to support Sub-Action new UnsecuredActionsClient#179388

Merged
paul-tavares merged 23 commits intoelastic:mainfrom
paul-tavares:task/olm-8921-enable-retrieval-of-internal-response-actions-client
Apr 2, 2024
Merged

[Security Solution][Endpoint] Change Response Actions client for SentinelOne to support Sub-Action new UnsecuredActionsClient#179388
paul-tavares merged 23 commits intoelastic:mainfrom
paul-tavares:task/olm-8921-enable-retrieval-of-internal-response-actions-client

Conversation

@paul-tavares
Copy link
Copy Markdown
Contributor

@paul-tavares paul-tavares commented Mar 25, 2024

Summary

Changes are in support of Bi-Directional response actions and includes:

  • EndpointAppContextService.getInternalResponseActionsClient() now supports retrieving clients for non-endpoint EDR systems (ex. SentinelOne)
  • Refactored SentinelOne Response Actions Client and pulled logic around using the Connector into its own class. This new class allows it to normalize the use of connectors regardless of the Action plugin service being used (ActionClient or IUnsecuredActionsClient)
  • CompleteExternalActionsTaskRunner was adjust to ignore errors for agentType's that are not configured in the system (don't log useless errors)
  • The run_sentinelone_host.js script was enhanced to not create multiple VMs running SentinelOne agent on them if one is already running. A new CLI argument - forceNewS1Host - was also added to by pass this behaviour and allow for a VM to always be created

Checklist

@paul-tavares paul-tavares added release_note:skip Skip the PR/issue when compiling release notes Team:Defend Workflows “EDR Workflows” sub-team of Security Solution v8.14.0 labels Mar 25, 2024
@paul-tavares paul-tavares self-assigned this Mar 25, 2024
@paul-tavares
Copy link
Copy Markdown
Contributor Author

/ci

@paul-tavares paul-tavares marked this pull request as ready for review March 26, 2024 14:31
@paul-tavares paul-tavares requested review from a team as code owners March 26, 2024 14:31
@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/security-defend-workflows (Team:Defend Workflows)

@paul-tavares paul-tavares requested review from tomsonpl and removed request for szwarckonrad March 26, 2024 14:31
@paul-tavares paul-tavares requested a review from pzl March 27, 2024 18:18
Copy link
Copy Markdown
Contributor

@tomsonpl tomsonpl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through the code, it looks good! Thanks!

log.verbose(`Finding [${type}] VMs with name [${name}]`);

if (type === 'multipass') {
const list = JSON.parse((await execa.command(`multipass list --format json`)).stdout) as {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously there was --format=json, can these be used interchangeably? Or the previous one was not working correctly? :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same behaviour. Its a bash way of defining "long" parameter values

// if (abortSignal.aborted) {
// return;
// }
// Dev test entry below
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be deleted in my next PR. I only used the code below to test that we could call the external API during my test.

client: ActionsClient | IUnsecuredActionsClient
): client is IUnsecuredActionsClient {
// The methods below only exist in the normal `ActionsClient`
return !('create' in client) && !('delete' in client) && !('update' in client);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// `GET execute` property and wrap it a function that will automatically inject this data into
// `execute()` calls
if (taskId && taskType) {
connectorActionsClient = new Proxy(connectorActionsClient, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this metaprogramming here? I guess it can be useful in some cases, e.g. if we want to add logging to all methods of a class instead of modifying all of its methods one by one, but here we're just adding an additional layer of complexity to be able to modify one function, if I understood correctly.

could you consider passing the background task related parameters to the function processPendingActions() instead? or maybe adding those parameters to the ConstructorOptions of getResponseActionsClient(), if we want to store those params for the lifetime of the responseActionsClient object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not want to introduce Task specific details all the way down to the Action Response client - I feel that would just add unnecessary complexity. The data is only needed to silence a .warn()'ing that this new Connector's sub-actions client's .execute() method outputs when this data is not set. So its needed for all calls to .execute() from the Connector's sub-actions framework - thus I wanted to keep it in a place that would apply only to the use of this client (note: the sub-actions client that is used from our HTTP apis for response actions uses a different Connector client that does not have this need).

The use of the Connector sub-actions is an internal details to the implementation of the Response Actions Client's, thus I also did not want to expose connector specific details on our class methods and rather keep them as "pure" as possible as far as input arguments go. This will also makes it cleaner should we ever want to make internal changes to how we communicate with external systems by not having to alter our exposed interfaces and associated method signatures.

I also considered changing the constructor arguments to instead accept on input an instance of the new Class I created (NormalizedExternalConnectorClient), but because that class needs the agentType I felt it would be best for it to continue to be used only from within the ResponseActionsClient sub-classes where the agentType is already know.

Let me know what you think. I can still evaluate to see if there is a more clean and transparent way to do this without having to expose this data down/up the chain of the code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, if it's only for addressing a warning, and there is no change in the behaviour, I guess we can live with that.

Still, I have a feeling that when we finally call execute(), it receives taskId+taskType and requesterId: 'background_task' parameters, which seem related to me, but they come from 2 separate paths:

  • taskId+taskType come from proxying the connector client which happens only for non-endpoint agents in EndpointAppContextService,
  • 'background_task' comes from SentinelOneActionsClient when it calls execute()

So, a group of related parameters, that are set together accidentally. If my assumption is right, I'd suggest to simplify the dataflow by putting them in one place, either in SentinelOneActionsClient, or if it's really only the warning, in the Proxy.
If they are not related, feel free to skip my comment. :)

I tried to depict the situation in a flowchart-like irregular chart, where I marked the non-endpoint specific stuff with red.

flowchart TD

taskRunner(["CompleteExternalActionsTaskRunner.run()"])-->|taskId| getInternalResponseActionsClient(["getInternalResponseActionsClient()"])
getInternalResponseActionsClient --> getResponseActionsClient
getInternalResponseActionsClient--- |taskId| connectorsClient
connectorsClient --o |"taskId, requesterId: 'background_task'"| execute

getResponseActionsClient(["getResponseActionsClient()"]) --> SentinelOneActionsClient
SentinelOneActionsClient ---|"requesterId: 'background_task'"| NormalizedExternalConnector
NormalizedExternalConnector--o |"requesterId: 'background_task'"| connectorsClient

taskRunner --> processPendingActions(["processPendingActions()"])
processPendingActions -.-> execute(["`execute( *taskId, 'background_task'* )`"])

style SentinelOneActionsClient stroke:#d55,stroke-width:4px
style NormalizedExternalConnector stroke:#d55,stroke-width:4px
style taskRunner stroke:#d55,stroke-width:4px
Loading

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this: I'll make some changes to the NormalizedExternalConnector class and will add these parameters to it instead and will then use that class instance to initialize the Response Actions Client's.

Sound good to you?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But actually - I will do that in a follow up PR, if thats ok. Need to get this in so I can use it in my other PR and Tomasz also needs the changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, go for it! don't want to hold up the work, so follow up PR is perfect

yeah, using the NormalizedExternalConnector sounds reasonable, let's see 👍

@kibana-ci
Copy link
Copy Markdown

💚 Build Succeeded

Metrics [docs]

Unknown metric groups

ESLint disabled in files

id before after diff
securitySolution 76 77 +1

Total ESLint disabled count

id before after diff
securitySolution 566 567 +1

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

cc @paul-tavares

Copy link
Copy Markdown
Contributor

@gergoabraham gergoabraham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

@paul-tavares paul-tavares merged commit 66af262 into elastic:main Apr 2, 2024
@kibanamachine kibanamachine added the backport:skip This PR does not require backporting label Apr 2, 2024
@paul-tavares paul-tavares deleted the task/olm-8921-enable-retrieval-of-internal-response-actions-client branch April 2, 2024 12:27
paul-tavares added a commit that referenced this pull request Apr 4, 2024
…Clients (#179871)

## Summary

- Refactors Response Actions Client's to be initialized using
`NormalizedExternalConnectorClient` instead of the Action plugin
`ActionClient`
    - Remove `Proxy` code implemented in prior PR


_(Feedback from PR #179388)_


### Checklist

- [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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team:Defend Workflows “EDR Workflows” sub-team of Security Solution v8.14.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants