Skip to content
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

[sentinelone-indicators] Allows Indicators to be sent to S1 as IOCs #3269

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions stream/sentinelone-indicators/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.13-alpine
ENV CONNECTOR_TYPE=STREAM

COPY src /opt/connector-sentinel-one-indicator


RUN apk --no-cache add file libmagic \
libxml2 libxml2-dev libxslt libxslt-dev yaml-dev

RUN cd /opt/connector-sentinel-one-indicator && \
pip3 install --no-cache-dir -r requirements.txt

COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
128 changes: 128 additions & 0 deletions stream/sentinelone-indicators/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# OpenCTI SentinelOne Indicator Stream Connector

This connector allows Indicators from an OpenCTI Instance to be uploaded to a SentinelOne account.

<br>

This software is provided as a community-driven project and is not officially supported by SentinelOne. It is offered on an "as-is" basis, without warranties or guarantees, either express or implied. Users are encouraged to thoroughly test and validate the software before deploying it in their environments. While community contributions and feedback are welcome, SentinelOne does not provide formal technical support, maintenance, or updates for this project.

<br>


-This version of the connector simply uploads Indicators as they are created.

<br>

## Implementation
After appending the container to your compose file, you will need to create a new user in your OpenCTI instance and retrieve some information for your SentinelOne Account in order to interface with it.

If you don't want to use the default stream, you will also need to create your own.

<br>
<br>

### Retrieving Your Account ID

![Account ID In S1](doc/account_id.png)


- Click on the settings icon at the bottom of the left panel on the console.
- From the `Settings` menu, select `ACCOUNTS` and click on the account you seek to use in the below list.
- You will now be in the `Sentinels` menu. From here, select `ACCOUNT INFO` (you may need to scroll to the right on smaller monitors to see it).
- Underneath your account name you will see your account ID in the field `ACCOUNT ID`



<br>

### Generating an API Key

![Generating An API Token In S1](doc/api_generation.png)

- Click on your email address in the top right corner of the menu on the SentinelOne Console.
- Click the `Actions` dropdown button and hover over `API Token Operations`.
- Click `Regenerate API token` and proceed with the required Authentication.
- **Note:** you do not need to include the `'APIToken '`component of the string in any configs

<br>

### Determining Your SentinelOne URL
Your SentinelOne URL is simply the first component of the URL you use to access the console.

![S1 URL Example](doc/url_finding.png)

When configuring the connector, ensure that you include the ending '/'. For example, for the above image, you would input `https://usea1-purple.sentinelone.net/`

<br>

### Creating the Connector User
It is best practice to create a new user under the `Connectors` group and to use its token to interface with your instance.

![Generating A User In OpenCTI](doc/user_creation.png)


- Locate the gear (Settings) icon on the left menu and click `Security`.
- On the menu on the right click on the `Users` option.
- Click the blue `+` icon at the bottom of the list
- Enter `[C] S1 Indicator Connector`. **Note:** you can name this whatever you'd like, but you should include `[C]` at the start regardless.
- Enter the required information and ensure that under the `Groups` field `Connectors` is this selected option.


<br>


### Creating a dedicated Stream (optional)


- To create a dedicated stream for this connector head to `Data sharing` -> `Live streams` in the OpenCTI platform.

![Creating a Stream in OpenCTI](doc/stream_creation.png)

- Give the stream a name so that it can be identified
- Optional filters can be applied to determine what kind of data goes into the connector. It is recommended to not set any filters as to allow SentinelOne to consume all types it can.
- Copy the streams ID to be you used in your environment variables.


<br>

## Configuration

### **OpenCTI Parameters:**

| Parameter | config.yml | Docker environment variable | Example | Description |
|----------------------|------------|-----------------------------|----------------------------|----------------------------------------------|
| URL | `url` | `OPENCTI_URL` | `http://opencti:8080` | The URL of your OpenCTI instance within its internal network. |
| Token | `token` | `OPENCTI_TOKEN` | `11111111-2222-3333-4444-555555555555` | The token of the user specifically created for this Connector, under the `Connectors` group. |

---

<br>

### **Connector Parameters:**

| Parameter | config.yml | Docker environment variable | Example | Description |
|------------------|------------|-----------------------------|----------------------------------------|----------------------------------------------------------------------------------------|
| ID | `id` | `CONNECTOR_ID` | `11111111-2222-3333-4444-555555555555` | Unique `UUIDv4` identifier for the connector. |
| Name | `name` | `CONNECTOR_NAME` | `SentinelOne Indicator Export Stream` | The Connector's name as it will appear in OpenCTI. |
| Scope | `scope` | `CONNECTOR_SCOPE` | `all` | The scope of this connector. |
| Log Level | `log_level`| `CONNECTOR_LOG_LEVEL` | `info` | The level of logs/outputs presented. `info` is recommended. |
| Live Stream ID | `live_stream_id`| `CONNECTOR_LIVE_STREAM_ID` | `live` | The id of the stream to listen into, the default `live` is okay to use. |
| Stream Delete | `live_stream_listen_delete`| `CONNECTOR_LIVE_STREAM_LISTEN_DELETE` | `true` | Whether or not the connector will delete Indicator creation events after processing them (stops repetition) |
| Stream No Dependencies | `live_stream_no_dependencies`| `CONNECTOR_LIVE_STREAM_NO_DEPENDENCIES` | `true` | Determines whether the stream will require dependency on other entities, set to `true` |


---

<br>

### **SentinelOne Parameters**

| Parameter | config.yml | Docker environment variable | Example | Description |
|---------------------------|----------------------|---------------------------------|------------------------------------------|-----------------------------------------------------------------------------|
| SentinelOne URL | `url` | `SENTINELONE_URL` | `https://usea1-purple.sentinelone.net/` | The SentinelOne platform URL. **NOTE:** The URL should end with a `/`. |
| SentinelOne API Key | `api_key` | `SENTINELONE_API_KEY` | `eyJraWQiO...` | The API key for your SentinelOne account (JWT). **NOTE:** Should not include `APIToken`. |
| SentinelOne Account ID| `account_id` | `SENTINELONE_ACCOUNT_ID` | `1234567890123456789` | The ID of your SentinelOne Account. |
| Max API Call Attempts | `max_api_attempts` | `SENTINELONE_MAX_API_ATTEMPTS` | `5` | The maximum number of retry attempts when API requests to SentinelOne repeatedly fail. `5` is recommended. |
---

<br>
Binary file added stream/sentinelone-indicators/doc/account_id.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added stream/sentinelone-indicators/doc/url_finding.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions stream/sentinelone-indicators/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: '3'
services:
connector-template:
image: opencti/connector-sentinelone-indicators:1.0.0
environment:
- OPENCTI_URL=http://localhost
- OPENCTI_TOKEN=CHANGEME

- CONNECTOR_ID=CHANGEME
- CONNECTOR_NAME=CHANGEME
- CONNECTOR_SCOPE=CHANGEME
- CONNECTOR_LOG_LEVEL=error

- CONNECTOR_LIVE_STREAM_ID=live
- CONNECTOR_LIVE_STREAM_LISTEN_DELETE=true
- CONNECTOR_LIVE_STREAM_NO_DEPENDENCIES=true

- SENTINELONE_URL=changeMeEndingIn/
- SENTINELONE_API_KEY=changeMe
- SENTINELONE_ACCOUNT_ID=changeMe
- SENTINELONE_MAX_API_ATTEMPTS=5

restart: always
7 changes: 7 additions & 0 deletions stream/sentinelone-indicators/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Go to the directory in the container
cd /opt/connector-sentinel-one-indicator

# Launch the connector
python3 main.py
19 changes: 19 additions & 0 deletions stream/sentinelone-indicators/src/config.yml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
opencti:
url: 'http://localhost:PORT'
token: 'ChangeMe'

connector:
id: 'ChangeMe'
type: 'STREAM'
name: 'SentinelOne Indicator Stream Connnector'
scope: 'ChangeMe'
log_level: 'info'
live_stream_id: 'live' # ID of the live stream created in the OpenCTI UI
live_stream_listen_delete: true
live_stream_no_dependencies: true

sentinelOne:
url: 'change Me Ending In/'
api_key: 'ChangeMe'
account_id: 'ChangeMe'
max_api_attempts: 5
20 changes: 20 additions & 0 deletions stream/sentinelone-indicators/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import traceback

from stream_connector import IndicatorStreamConnector

if __name__ == "__main__":
"""
Entry point of the script

- traceback.print_exc(): This function prints the traceback of the exception to the standard error (stderr).
The traceback includes information about the point in the program where the exception occurred,
which is very useful for debugging purposes.
- exit(1): effective way to terminate a Python program when an error is encountered.
It signals to the operating system and any calling processes that the program did not complete successfully.
"""
try:
connector = IndicatorStreamConnector()
connector.run()
except Exception:
traceback.print_exc()
exit(1)
1 change: 1 addition & 0 deletions stream/sentinelone-indicators/src/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pycti==6.4.6
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .connector import IndicatorStreamConnector

__all__ = ["IndicatorStreamConnector"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import os
from pathlib import Path

import yaml
from pycti import get_config_variable


class ConfigConnector:
def __init__(self):
"""
Initialize the connector with necessary configurations
"""

# Load configuration file
self.load = self._load_config()
self._initialize_configurations()

@staticmethod
def _load_config() -> dict:
"""
Load the configuration from the YAML file
:return: Configuration dictionary
"""
config_file_path = Path(__file__).parents[1].joinpath("config.yml")
config = (
yaml.load(open(config_file_path), Loader=yaml.FullLoader)
if os.path.isfile(config_file_path)
else {}
)

return config

def _initialize_configurations(self) -> None:
"""
Connector configuration variables
:return: None
"""
# OpenCTI configurations

# Connector configurations
self.connector_name = get_config_variable(
"CONNECTOR_NAME", ["connector", "name"], self.load
)

# Connector extra parameters
self.s1_url = get_config_variable(
"SENTINELONE_URL", ["sentinelOne", "url"], self.load
)

self.s1_api_key = "APIToken " + (
get_config_variable(
"SENTINELONE_API_KEY", ["sentinelOne", "api_key"], self.load
)
)

self.s1_account_id = get_config_variable(
"SENTINELONE_ACCOUNT_ID", ["sentinelOne", "account_id"], self.load
)

self.max_api_attempts = int(
get_config_variable(
"SENTINELONE_MAX_API_ATTEMPTS",
["sentinelOne", "max_api_attempts"],
self.load,
)
)
Loading