diff --git a/docs/developer/quickstart.md b/docs/developer/quickstart.md index a31e1aa91b..a921a1200d 100644 --- a/docs/developer/quickstart.md +++ b/docs/developer/quickstart.md @@ -1,523 +1,80 @@ -# Quickstart +# Getting Started with the Sekoia.io API -Welcome to Sekoia.io's public REST API. Note that Sekoia.io GUI is built upon this API. +The Sekoia.io REST API lets you access platform data, trigger actions, and build integrations or automations on top of Sekoia.io. The GUI itself is built on this API, so anything you can do in the interface, you can do via the API. -### When to use the Sekoia.io API? +## Step 1 — Find your base URL -* **Data sharing**: Access Sekoia.io data in your application. -* **Integration**: Enable third-party applications to execute actions within Sekoia.io. -* **Automation**: Automate actions and tasks via your own scripts. +The base URL depends on the region where your Sekoia.io subscription is hosted. -Here are some characteristics of our REST API: + + + + + + + + + + + + + + +
RegionBase URL
FRA1 (default)https://api.sekoia.io
FRA2https://app.fra2.sekoia.io/api
MCO1https://app.mco1.sekoia.io/api
UAE1https://app.uae1.sekoia.io/api
USA1https://app.usa1.sekoia.io/api
-* **Client/Server architecture**: Operates through HTTP requests (GET, POST, PUT, DELETE...) -* **Stateless design**: No persistent connection maintained between consecutive requests. -* **Action identification**: Each specific action correlates with a unique URL endpoint. +If you are unsure of your region, look at the URL you use to access the Sekoia.io application. For example, if you log in via `https://app.mco1.sekoia.io/`, your API base URL is `https://app.mco1.sekoia.io/api`. -![API Overview](/assets/develop/api_overview.png){: style="max-width:100%"} +## Step 2 — Create an API key -## Authentication +All API requests are authenticated with an API key. To create one: -Access the complete Sekoia.io API through `https://api.sekoia.io`. +1. In Sekoia.io, go to **Settings > Workspace > API Keys**. +2. Click **+ API key**. +3. Give your key a name and a description. +4. Set an expiration date (30 days, 180 days, 365 days, or custom up to 1 year). +5. Select the permissions your key needs. +6. Click **Save** and copy the key immediately — it will only be shown once. -To authenticate, use a Bearer Token in all requests by including the header `Authorization: Bearer `. +!!! tip + Start with read-only permissions. You can always create a second key with write permissions when you need to perform write operations. -**Example using curl**: -```bash -curl -XGET -H "Authorization: Bearer YOUR_API_KEY" https://api.sekoia.io/v1/sic/conf/rules-catalog/rules -``` -Create your unique API key following the guidelines provided [here](/getting_started/manage_api_keys.md). -The permissions required for your key depend on what you want to achieve. For accessing information from Sekoia.io, read-only permissions are adequate; however, executing operations on Sekoia.io necessitates additional write permissions. - -Our documentation provides information on each endpoint and specifies the required permissions. - -### Regions -Sekoia operates in various European and Middle-Eastern regions. Each region is designed to meet specific legal and safety requirements. -Sekoia API's base URL depends on the hosting region of your subscription (FRA1, USA1, UAE1, ...). For instance, if you access the Sekoia application via `https://app.mco1.sekoia.io/`, you shall use our API via `https://app.mco1.sekoia.io/api`. - -## Organization - -Our API documentation is categorized based on the diverse features provided by the platform. - -#### Structure -Each action is described and contains the following information: - -* Action name -* Required permissions -* Mandatory and optional data parameters -* Endpoint URL -* Query and response examples - -The method of passing data through the API varies based on the endpoint. Data may be transmitted as `query parameters` or within an `application/json` body. Query parameters means that data is encoded within the URL. - -### Some use-cases - -#### URL parameters -URL parameters provide a direct method to convey data within the URL. Positioned after the question mark (?) and separated by an ampersand (&), these parameters are essential for transmitting information effectively. -For instance, in the following request, "match[name]=Suspicious%20Windows%20Defender%20Exclusion%20Command" and "limit=10" are the URL parameters. - -```bash -curl -XGET -H "Authorization: Bearer YOUR_API_KEY" https://api.sekoia.io/v1/sic/conf/rules-catalog/rules?match[name]=Suspicious%20Windows%20Defender%20Exclusion%20Command&limit=10 -``` - -URL parameters are typically used in simple requests, particularly search queries, where concise and uncomplicated data needs to be conveyed, commonly associated with `GET` requests. +!!! note + Only users with admin roles can create API keys. -#### Request Body with JSON Payload -Use the request body to transmit data in JSON format, known for its readability and compatibility for both humans and machines. Ensure to include the "Content-Type" header in your request, with the request body formatted as a JSON object. +## Step 3 — Make your first API call -This approach is ideal for more complex requests, such as creating or updating resources on the server, often executed with POST or PUT requests. The example below demonstrates the "Invite a user" action involving a POST request with an application/json body: +To verify that your key works, retrieve your user profile. This endpoint requires no special permissions beyond a valid key. ```bash -curl -X POST https://api.sekoia.io/v1/invitations \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"email": "john.doe@example.com","communities": {"88dcb0c6-4efe-4256-95f1-40d2c4fefefd": ["44aaa41f-24ee-41d3-a7c1-4677da8b9243"]}}' +export SEKOIA_API_KEY="your_api_key_here" - # ["44aaa41f-24ee-41d3-a7c1-4677da8b9243"] corresponds to the UUID of the role assigned to the user. - # This information is retrieved with the action "List the roles of a community" +curl -X GET https://api.sekoia.io/v1/me \ + -H "Authorization: Bearer $SEKOIA_API_KEY" ``` -!!! Note - In requests involving the creation or edition of server resources, it may be necessary to include parameters in the URL to specify the targeted object. The Sekoia.io API often requires passing the UUID of the specific object intended for modification through the URL. +A successful response looks like this: -#### Filtering - -Many API methods accept filtering and matching parameters. A client can request specific content from the Sekoia.io API by specifying a set of filters. - -##### Match - -The `match[]` field parameter can be used to filter documents given the value of a specific `field`. A filter parameter can be specified any number of times, where all filter fields are handed together. - -It should be noted that each `field` must not occur more than once. Multiple values of a match parameter are separated by a comma (U+002C COMMA, “,”) without any spaces. If multiple values are present, the match is treated as a logical OR. - -**Examples of match parameters** - -``` -# list alerts triggered on entity entity1 or entity2 -/alerts?match[entity_name]=entity1,entity2 - -# list alerts triggered on entity1 with rule named rule1 or rule2 -/alerts?match[entity_name]=entity1&match[rule_name]=rule1,rule2 +```json +{ + "uuid": "2a4b6c8d-...", + "email": "you@example.com", + "firstname": "Jane", + "lastname": "Doe" +} ``` -#### Date ranges - -The `date[field]` parameter can be used to filter documents given a date range on a specific `field`. The value of the parameter must be two dates separated by a comma (U+002C COMMA, ",") without any spaces. The first date is the start date and the second date is the end date. - -Example of date range parameter: `date[created_at]=2025-09-22T01:20:00.000Z,2025-09-22T23:59:59.999Z`. - -#### Pagination - -Many Sekoia.io API endpoints return large collections (alerts, assets, rules, events, intelligence objects, …). These endpoints are paginated. Depending on the product area, two main styles are used: - -* **Offset / page-based pagination** (most XDR and Operations Center endpoints) - * Use the `limit` query parameter to control how many items are returned in a single response. - * Use either a `page` parameter (starting at `page=1`) or an `offset` parameter (starting at `offset=0`) as documented in each endpoint. - * If you do not specify a `limit`, many endpoints default to **100 items per page**. - * Each endpoint may define its own **maximum** allowed `limit` (commonly 100 or 1000); requesting more will either be rejected or silently capped by the API. - -* **Cursor-based pagination** (Intelligence Center feeds) - * Endpoints such as `GET /v2/inthreat/collections/{feed_id}/objects` use a **cursor**. - * The request accepts a `limit` parameter. It returns STIX objects in an `items` field and a pagination cursor in `next_cursor`. - * To fetch the next page, pass the cursor back using the `cursor` query parameter: `cursor={next_cursor}`. - * By default, these endpoints return **100 objects** per request, and you can increase this up to **2000 objects** with `limit`. - * You can safely stop when `items` is empty or when fewer than `limit` items are returned. - -For both styles, the safest way to iterate over a complete collection is to **loop until the API returns fewer items than requested**, rather than assuming a fixed number of pages. +If you can see your email and UUID, your setup is working. -**Example – iterating 1,000 items across pages (offset/page-based)** - -The example below illustrates how to retrieve up to 1,000 items using `limit=100` and a `page` parameter, stopping early if fewer results are returned: - -```python -import requests - -API_KEY = "YOUR_API_KEY" -BASE_URL = "https://api.sekoia.io/v1" - -def list_items(): - headers = {"Authorization": f"Bearer {API_KEY}"} - endpoint = f"{BASE_URL}/sic/conf/rules-catalog/rules" - - all_items = [] - limit = 100 - max_items = 1000 - - page = 1 - while len(all_items) < max_items: - params = {"limit": limit, "page": page} - response = requests.get(endpoint, headers=headers, params=params) - response.raise_for_status() - - data = response.json() - items = data.get("items", data) - - all_items.extend(items) - - # Stop if the API returned less than requested (no more pages) - if len(items) < limit: - break - - page += 1 - - return all_items[:max_items] - -if __name__ == "__main__": - results = list_items() - print(f"Fetched {len(results)} items") -``` - -For cursor-based endpoints, the same pattern applies, but you replace the `page`/`offset` parameter with a `cursor` parameter and update it with the `next_cursor` value returned by the API on each iteration. - -## Code examples -Please find below two python code examples, one with a `GET` request with `query parameters` and another with a `POST` request and an `application/json` body. - -### Retrieving information with Query Parameters - -Let's delve into an example where we retrieve details about the rule titled "Suspicious Windows Defender Exclusion Command." To find this information, we can leverage the [List Rules](https://docs.sekoia.io/develop/rest_api/operation_center/configuration/#tag/rules-catalog/operation/get_rules_resource) endpoint. - -The documentation means that no parameters are mandatory for this request to execute successfully. However, to fulfill our specific requirement, we opt to use the `match[name]` parameter, designed to "Match rules by their name (separated by commas)." - -The following script has been crafted to fetch this information and display the results on the standard output (STDOUT). - - -```python -import json -import logging - -import requests - -API_KEY = "YOUR_API_KEY" -BASE_URL = "https://api.sekoia.io/v1" - -def get_request(): - - url = f"{BASE_URL}/sic/conf/rules-catalog/rules?match[name]=Suspicious Windows Defender Exclusion Command" - res = requests.get(url, headers={"Authorization": f"Bearer {API_KEY}"}) - - # In case of a success, the status code 200 is returned by the API - if res.status_code == 200: - data = res.json() - - # We print the result using the library JSON to get a pretty display - logging.info(f"This is the result of the request:") - logging.info(json.dumps(data, indent=4)) - # In case of authentication failed - elif res.status_code == 401: - logging.error(f"Cannot invite the user. Authentication failed: {res.status_code}") - logging.error(res.text) - # In case of insufficient permissions - elif res.status_code == 403: - logging.error(f"Insufficient permissions: {res.status_code}") - logging.error(res.text) - # In case of another status code - else: - logging.error(f"Error during the API request.") - logging.error(res.text) - -if __name__ == '__main__': - get_request() -``` +## Troubleshooting -### Create a SIGMA Rule with a POST Request and JSON Body +| Status code | Meaning | What to do | +| --- | --- | --- | +| `401 Unauthorized` | The API key is missing, expired, or invalid. | Check that the key is correctly copied and has not expired. | +| `403 Forbidden` | The key does not have the required permission for this endpoint. | Edit the key and add the missing permission, or create a new key with the right permissions. | +| `429 Too Many Requests` | You have exceeded the rate limit. | Wait before retrying. Add a delay between requests in your scripts. | -In this example, we will create a simple SIGMA rule named "My custom SIGMA rule", so it will be a `POST` request. According to [this documentation](https://docs.sekoia.io/xdr/develop/rest_api/configuration/#tag/rules-catalog/operation/post_rules_resource) in order to create a new rule we must provide the request with a minimum of mandatory fields. You will find in the following table what we will use in the Python script: +## Next steps - -In this scenario, we aim to create a SIGMA rule named "My custom SIGMA rule" through a **POST request**. Referring to the relevant documentation, some mandatory fields must be provided to successfully create a new rule. Below, you will find a summary of the key fields used in the Python script: - -| Fields | Value for the example | -| --- | --- | -| name | "My custom SIGMA rule" | -| type | "sigma" | -| description | "Detect spawn of Powershell" | -| payload | "detection:\\n  selection:\\n    process.command\_line\|contains: 'powershell'\\n  condition: selection" | -| severity | 40 | -| effort | 3 | -| alert\_type\_uuid | "599f4b1a-dd60-43fe-8ee9-07d3c5d00ded" | -| enabled | True | - -```python -import logging -import requests - -API_KEY = "YOUR_API_KEY" -BASE_URL = "https://api.sekoia.io/v1" - -def post_request(): - - url = f"{BASE_URL}/sic/conf/rules-catalog/rules" - body = { - "name": "My custom SIGMA rule", - "type": "sigma", - "description": "Detect spawn of Powershell", - "payload": "detection:\n selection:\n process.command_line|contains: 'powershell'\n condition: selection", - "severity": 40, - "effort": 3, - "alert_type_uuid": "599f4b1a-dd60-43fe-8ee9-07d3c5d00ded", - "enabled": True - } - - # The body is passed as a parameter of the post method. - res = requests.post(url, json=body, headers={"Authorization": f"Bearer {API_KEY}"}) - - # In case of a success, the status code 204 is returned by the API - if res.status_code == 200: - logging.info(f"The rule has been created on your community") - # In case of authentication failed - elif res.status_code == 401: - logging.error(f"Cannot create the rule. Authentication failed: {res.status_code}") - logging.error(res.text) - # In case of another status code - else: - logging.error(f"Error during the API request.") - logging.error(res.text) - -if __name__ == '__main__': - post_request() -``` - -### Advanced Example - Searching Events with Sekoia.io API -It is possible to search in your events by using the Sekoia.io API. Search for events can request some time to be processed and as a reminder, the API is stateless, that means no connection is maintained between two requests. - -To address the asynchronous nature of event searches, Sekoia.io introduces the concept of a search job, which offers a structured solution to manage this process effectively. - -We outline three essential steps involved in executing a search job: - -* [Create an event search job](https://docs.sekoia.io/xdr/develop/rest_api/configuration/#tag/events/operation/post_event_search_resource) - This step requires to provide the search query and the time window. The endpoint returns a `job UUID` which will be used in the next steps. -* [Get an event search job](https://docs.sekoia.io/xdr/develop/rest_api/configuration/#tag/events/operation/get_event_search_job_info_resource) - This step is used to get information about the status of a job by providing its UUID. Jobs can have five statuses: - * `0`: the job is not started - * `1`: the job is in progress - * `2`: the job is done (events can be retrieved) - * `3`: the job is canceled - * `4`: the job has failed -* [Get the events found by an event search job in descending order](https://docs.sekoia.io/xdr/develop/rest_api/configuration/#tag/events/operation/get_event_search_job_events_resource) - This steps is used to retrieved the events when the job is done (status `2`). It gives you the events in descending order (latest events first) and by default the limit is set to 100 events (up to 1000). An offset can be specified to get more of them. - -#### Python3 script -This Python script uses these 3 actions to perform a search and print the events to STDOUT. Only the latest 100 are printed. - -!!! Note - You need at least Python 3.10 to run this script. - -```python -#!/usr/bin/env python3 -""" -This script uses the Sekoia.io API to search events -and print the last 100 events to STDOUT -""" - -import argparse -import logging -import json -from time import sleep - -import requests - -BASE_URL = "https://api.sekoia.io/v1" - - -def create_search_job(apikey: str, dates: str, query: str) -> str: - """ - Function create a event search job based on a query and a time windows - """ - - # Parsing the dates - earliest_time = dates.split(",")[0] - latest_time = dates.split(",")[1] - body = { - "term": query, - "earliest_time": earliest_time, - "latest_time": latest_time, - "term_lang": "dork", - } - logging.debug(f"Search query: {query}") - logging.debug("Creating a new search job:") - response = requests.post( - f"{BASE_URL}/sic/conf/events/search/jobs", headers={"Authorization": f"Bearer {apikey}"}, json=body - ) - - # In case of a success, the status code 200 is returned by the API - if response.status_code == 200: - data = response.json() - logging.debug(json.dumps(data, indent=4)) - return data["uuid"] - - # In case of authentication failed - elif response.status_code == 401: - logging.error(f"Cannot create the search job. Authentication failed: {response.status_code}") - logging.error(response.text) - exit() - # In case of insufficient permissions - elif response.status_code == 403: - logging.error(f"Insufficient permissions: {response.status_code}") - logging.error(response.text) - exit() - # In case of another status code - else: - logging.error(f"Error during the API request.") - logging.error(response.text) - exit() - - -def get_search_job_status( - apikey: str, - job_uuid: str, -): - """ - Function that get the status of a search job - """ - - logging.debug(f"Checking the status of job {job_uuid}...") - response = requests.get( - f"{BASE_URL}/sic/conf/events/search/jobs/{job_uuid}", - headers={"Authorization": f"Bearer {apikey}"}, - ) - - # In case of a success, the job status is returned - if response.status_code == 200: - data = response.json() - logging.debug(json.dumps(data, indent=4)) - return data["status"] - - # In case of authentication failed - elif response.status_code == 401: - logging.error(f"Cannot get the search job status. Authentication failed: {response.status_code}") - logging.error(response.text) - exit() - # In case of insufficient permissions - elif response.status_code == 403: - logging.error(f"Insufficient permissions: {response.status_code}") - logging.error(response.text) - exit() - # In case of another status code - else: - logging.error(f"Error during the API request.") - logging.error(response.text) - exit() - - -def get_events_search_job( - apikey: str, - job_uuid: str, -) -> str: - """ - Function that get the events found by an event search job in descending order - """ - - response = requests.get( - f"{BASE_URL}/sic/conf/events/search/jobs/{job_uuid}/events", - headers={"Authorization": f"Bearer {apikey}"}, - ) - - # In case of a success, the status code 200 is returned by the API - if response.status_code == 200: - data = response.json() - return data - - # In case of authentication failed - elif response.status_code == 401: - logging.error(f"Cannot get the search job. Authentication failed: {response.status_code}") - logging.error(response.text) - exit() - # In case of insufficient permissions - elif response.status_code == 403: - logging.error(f"Insufficient permissions: {response.status_code}") - logging.error(response.text) - exit() - # In case of another status code - else: - logging.error(f"Error during the API request.") - exit() - - -def main( - apikey: str, - dates: str, - query: str, - verbose: bool, -): - """ - Main function - """ - - # If verbose mode is set, setting logging config to DEBUG - if verbose: - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.INFO) - - # Creation of the search job - search_job_uuid = create_search_job( - apikey=apikey, - dates=dates, - query=query, - ) - - # Check the status of the search job. - # 10 attempts are made with 1 second sleep between each try - attempts = 0 - while attempts < 10: - status = get_search_job_status(apikey=apikey, job_uuid=search_job_uuid) - - # If status is not started or is in progress we wait 1 second - if status == 0 or status == 1: - # Sleep 1 second before trying again. - sleep(1) - attempts += 1 - - # If the job is done, get the events - elif status == 2: - data = get_events_search_job(apikey=apikey, job_uuid=search_job_uuid) - - # Print the events to STDOUT (maximum 100 events) - logging.info(json.dumps(data, indent=4)) - break - - # If the job is cancel or failed - else: - logging.error("Job is failed or canceled.") - break - - -if __name__ == "__main__": - - parser = argparse.ArgumentParser() - parser.add_argument( - "APIKEY", - help="Sekoia.io API key with the SIC_READ_INTAKES permission", - ) - parser.add_argument( - "--dates", - help="The time window over which you want to search for your events. The format must be 'date_from,date_to'", - required=True, - ) - parser.add_argument("--query", help="Search filter in Dork format", required=True) - parser.add_argument("-v", action=argparse.BooleanOptionalAction, help="Verbose mode") - - args = parser.parse_args() - - main( - apikey=args.APIKEY, - dates=args.dates, - query=args.query, - verbose=args.v, - ) -``` - -#### How to use the script -Here is how to use the script: -```bash -search_events.py [-h] --dates DATES --query QUERY [-v] APIKEY -``` - -* `--dates` is used to specify to the time windows. The format used is `earliest_time,latest_time`. Dates can be in ISO8601 or relative format. -* `--query` is the query in Dork format (same language as the Sekoia.io event page) -* `-v` to get verbose mode -* `APIKEY` is the Sekoia.io key with the `SIC_READ_INTAKES` permission - -**Example**: - -To get events with source IP `1.2.3.4` in the last 30 days: -```bash -search_events.py [-h] --dates="-30d,now" --query='source.ip: \"1.2.3.4\"' YOUR_API_KEY -``` +- [Search for rules](tutorials/search_rules.md) — filter and retrieve detection rules from your catalog +- [Create a SIGMA rule](tutorials/create_sigma_rule.md) — create a detection rule programmatically +- [Search events](tutorials/search_events.md) — run an asynchronous event search and retrieve results diff --git a/docs/developer/tutorials/create_sigma_rule.md b/docs/developer/tutorials/create_sigma_rule.md new file mode 100644 index 0000000000..a4653132d5 --- /dev/null +++ b/docs/developer/tutorials/create_sigma_rule.md @@ -0,0 +1,92 @@ +# Create a SIGMA rule + +This tutorial shows how to create a detection rule programmatically using a `POST` request with a JSON body. + +## Prerequisites + +- A valid API key with the `SIC_WRITE_CONF` permission +- `curl` or Python 3 installed + +## Endpoint + +``` +POST /v1/sic/conf/rules-catalog/rules +``` + +## Required fields + +| Field | Description | Example value | +| --- | --- | --- | +| `name` | Name of the rule | `"My custom SIGMA rule"` | +| `type` | Rule type | `"sigma"` | +| `description` | Short description | `"Detect spawn of Powershell"` | +| `payload` | SIGMA detection logic (YAML as a string) | See below | +| `severity` | Severity score (0–100) | `40` | +| `effort` | Analyst effort level (1–5) | `3` | +| `alert_type_uuid` | UUID of the alert type | `"599f4b1a-dd60-43fe-8ee9-07d3c5d00ded"` | +| `enabled` | Whether the rule is active | `true` | + +## Create the rule + +=== "curl" + + ```bash + curl -X POST https://api.sekoia.io/v1/sic/conf/rules-catalog/rules \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My custom SIGMA rule", + "type": "sigma", + "description": "Detect spawn of Powershell", + "payload": "detection:\n selection:\n process.command_line|contains: powershell\n condition: selection", + "severity": 40, + "effort": 3, + "alert_type_uuid": "599f4b1a-dd60-43fe-8ee9-07d3c5d00ded", + "enabled": true + }' + ``` + +=== "Python" + + ```python + import requests + + API_KEY = "YOUR_API_KEY" + BASE_URL = "https://api.sekoia.io/v1" + + def create_sigma_rule(): + url = f"{BASE_URL}/sic/conf/rules-catalog/rules" + body = { + "name": "My custom SIGMA rule", + "type": "sigma", + "description": "Detect spawn of Powershell", + "payload": "detection:\n selection:\n process.command_line|contains: powershell\n condition: selection", + "severity": 40, + "effort": 3, + "alert_type_uuid": "599f4b1a-dd60-43fe-8ee9-07d3c5d00ded", + "enabled": True, + } + response = requests.post(url, json=body, headers={"Authorization": f"Bearer {API_KEY}"}) + response.raise_for_status() + print("Rule created:", response.json().get("uuid")) + + if __name__ == "__main__": + create_sigma_rule() + ``` + +## Response + +A successful request returns `200` with the created rule, including its generated `uuid`: + +```json +{ + "uuid": "a1b2c3d4-...", + "name": "My custom SIGMA rule", + "type": "sigma", + "enabled": true +} +``` + +## Next steps + +Use the returned `uuid` to update the rule (`PUT`) or retrieve it individually (`GET /v1/sic/conf/rules-catalog/rules/{uuid}`). To search for existing rules before creating a new one, see [Search for rules](search_rules.md). diff --git a/docs/developer/tutorials/index.md b/docs/developer/tutorials/index.md new file mode 100644 index 0000000000..fd6af0742d --- /dev/null +++ b/docs/developer/tutorials/index.md @@ -0,0 +1,25 @@ +# Tutorials + +Step-by-step examples to help you get the most out of the Sekoia.io API. + +
+ +- **Search for rules** + + Filter and retrieve detection rules from your catalog using query parameters. + + [:octicons-arrow-right-24: Read tutorial](search_rules.md) + +- **Create a SIGMA rule** + + Create a detection rule programmatically with a POST request and a JSON body. + + [:octicons-arrow-right-24: Read tutorial](create_sigma_rule.md) + +- **Search events** + + Run an asynchronous event search job and retrieve the results. + + [:octicons-arrow-right-24: Read tutorial](search_events.md) + +
diff --git a/docs/developer/tutorials/search_events.md b/docs/developer/tutorials/search_events.md new file mode 100644 index 0000000000..d775f1fe97 --- /dev/null +++ b/docs/developer/tutorials/search_events.md @@ -0,0 +1,122 @@ +# Search events + +Event searches can take time to process. The Sekoia.io API handles this with an asynchronous **search job**: you create a job, poll its status, and retrieve results once it is done. + +## Prerequisites + +- A valid API key with the `SIC_READ_INTAKES` permission +- Python 3.10 or later + +## How it works + +The workflow involves three steps: + +1. **Create a search job** — submit your query and time window, get back a job `uuid` +2. **Poll the job status** — check until the job is done +3. **Retrieve the events** — fetch results when status is `2` (done) + +Job statuses: + +| Status | Meaning | +| --- | --- | +| `0` | Not started | +| `1` | In progress | +| `2` | Done | +| `3` | Canceled | +| `4` | Failed | + +## Python script + +```python +#!/usr/bin/env python3 +import argparse +import json +import logging +from time import sleep + +import requests + +BASE_URL = "https://api.sekoia.io/v1" + + +def create_search_job(apikey: str, earliest: str, latest: str, query: str) -> str: + body = { + "term": query, + "earliest_time": earliest, + "latest_time": latest, + "term_lang": "dork", + } + response = requests.post( + f"{BASE_URL}/sic/conf/events/search/jobs", + headers={"Authorization": f"Bearer {apikey}"}, + json=body, + ) + response.raise_for_status() + return response.json()["uuid"] + + +def wait_for_job(apikey: str, job_uuid: str, retries: int = 10) -> int: + for _ in range(retries): + response = requests.get( + f"{BASE_URL}/sic/conf/events/search/jobs/{job_uuid}", + headers={"Authorization": f"Bearer {apikey}"}, + ) + response.raise_for_status() + status = response.json()["status"] + if status in (2, 3, 4): + return status + sleep(1) + return -1 + + +def get_events(apikey: str, job_uuid: str) -> list: + response = requests.get( + f"{BASE_URL}/sic/conf/events/search/jobs/{job_uuid}/events", + headers={"Authorization": f"Bearer {apikey}"}, + ) + response.raise_for_status() + return response.json() + + +def main(apikey: str, earliest: str, latest: str, query: str, verbose: bool): + logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO) + + logging.info("Creating search job...") + job_uuid = create_search_job(apikey, earliest, latest, query) + logging.info(f"Job created: {job_uuid}") + + status = wait_for_job(apikey, job_uuid) + if status != 2: + logging.error(f"Job ended with status {status}.") + return + + events = get_events(apikey, job_uuid) + print(json.dumps(events, indent=4)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Search Sekoia.io events") + parser.add_argument("APIKEY", help="Sekoia.io API key") + parser.add_argument("--earliest", required=True, help="Start of the time window (ISO8601 or relative, e.g. -30d)") + parser.add_argument("--latest", required=True, help="End of the time window (ISO8601 or relative, e.g. now)") + parser.add_argument("--query", required=True, help="Search query in Dork format") + parser.add_argument("-v", action="store_true", help="Verbose mode") + args = parser.parse_args() + + main(args.APIKEY, args.earliest, args.latest, args.query, args.v) +``` + +## Usage + +```bash +python search_events.py YOUR_API_KEY \ + --earliest="-30d" \ + --latest="now" \ + --query='source.ip: "1.2.3.4"' +``` + +## Notes + +- By default the API returns up to **100 events**. Pass `?limit=1000` on the results endpoint to retrieve more (maximum 1000). +- Dates can be in ISO8601 format (`2025-01-01T00:00:00Z`) or relative format (`-30d`, `now`). +- The script retries up to 10 times with a 1-second delay between attempts. For long-running searches, increase `retries`. diff --git a/docs/developer/tutorials/search_rules.md b/docs/developer/tutorials/search_rules.md new file mode 100644 index 0000000000..c4acadc848 --- /dev/null +++ b/docs/developer/tutorials/search_rules.md @@ -0,0 +1,84 @@ +# Search for rules + +This tutorial shows how to search for detection rules using the Sekoia.io API. It uses a `GET` request with query parameters to filter results by name. + +## Prerequisites + +- A valid API key with the `SIC_READ_CONF` permission +- `curl` or Python 3 installed + +## Endpoint + +``` +GET /v1/sic/conf/rules-catalog/rules +``` + +## Filter by name + +Use the `match[name]` parameter to find a rule by its name. + +=== "curl" + + ```bash + curl -X GET \ + "https://api.sekoia.io/v1/sic/conf/rules-catalog/rules?match[name]=Suspicious%20Windows%20Defender%20Exclusion%20Command" \ + -H "Authorization: Bearer YOUR_API_KEY" + ``` + +=== "Python" + + ```python + import json + import requests + + API_KEY = "YOUR_API_KEY" + BASE_URL = "https://api.sekoia.io/v1" + + def search_rules(name: str): + url = f"{BASE_URL}/sic/conf/rules-catalog/rules" + params = {"match[name]": name} + response = requests.get(url, headers={"Authorization": f"Bearer {API_KEY}"}, params=params) + response.raise_for_status() + return response.json() + + if __name__ == "__main__": + results = search_rules("Suspicious Windows Defender Exclusion Command") + print(json.dumps(results, indent=4)) + ``` + +## Filter by multiple values + +Separate values with a comma to match any of them (logical OR): + +``` +/v1/sic/conf/rules-catalog/rules?match[name]=rule1,rule2 +``` + +You can combine multiple `match` parameters (logical AND): + +``` +/v1/sic/conf/rules-catalog/rules?match[name]=rule1&match[severity]=high +``` + +## Response + +A successful request returns `200` with a list of matching rules: + +```json +{ + "items": [ + { + "uuid": "...", + "name": "Suspicious Windows Defender Exclusion Command", + "type": "sigma", + "severity": 80, + "enabled": true + } + ], + "total": 1 +} +``` + +## Next steps + +Once you have found a rule, you can update or delete it using its `uuid` with the corresponding `PUT` or `DELETE` endpoints. You can also [create a new rule](create_sigma_rule.md) from scratch. diff --git a/mkdocs.yml b/mkdocs.yml index 206a41d729..7ff35eb277 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -600,6 +600,11 @@ nav: - General Questions: - Bug VS Improvement Requests: integration/faq/general_questions/bug_and_improvement_requests.md - API Documentation: developer/api.md +- Tutorials: + - Overview: developer/tutorials/index.md + - Search for rules: developer/tutorials/search_rules.md + - Create a SIGMA rule: developer/tutorials/create_sigma_rule.md + - Search events: developer/tutorials/search_events.md plugins: - search: null - redirects: