Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions external-import/metras/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
__metadata__
**/__pycache__
**/__docs__
**/.venv
**/venv
**/logs
**/config.yml
**/*.egg-info
**/*.gql
.plan
tests
22 changes: 22 additions & 0 deletions external-import/metras/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.12-alpine

ENV CONNECTOR_TYPE=EXTERNAL_IMPORT
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

COPY src /opt/opencti-connector-metras-feed

RUN apk update && apk upgrade --no-cache && \
apk --no-cache add libmagic libffi libxml2 libxslt && \
apk --no-cache add --virtual .build-deps git build-base libffi-dev libxml2-dev libxslt-dev && \
cd /opt/opencti-connector-metras-feed && \
pip3 install --no-cache-dir -r requirements.txt && \
apk del .build-deps

COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh && \
adduser -D -u 1000 connector && \
chown -R connector:connector /opt/opencti-connector-metras-feed
USER connector

ENTRYPOINT ["/entrypoint.sh"]
70 changes: 70 additions & 0 deletions external-import/metras/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# OpenCTI Metras Feed Connector (EXTERNAL_IMPORT)

Imports telemetry from the [Metras](https://dashboard.metras.sa/) security platform
(`api.metras.sa`) into OpenCTI on a schedule: EDR alerts (with MITRE ATT&CK mapping),
file binaries, and endpoint inventory.

## What it imports

| Metras source | OpenCTI output |
|---|---|
| EDR alerts (`/v1/edr/alerts`) | **Incident** (name, severity, score, labels) |
| └ `mitre_ids` | **Attack-Pattern** (keyed by `external_id`, merges with MITRE ATT&CK) + `uses` relationship |
| └ `url` | **Url** observable + `related-to` (external destination — a real IOC) |
| └ `endpoint_name` (+ `agent_ip`) | **Identity** (`identity_class: system`) + `related-to` |
| Binaries (`/v1/edr/binary/list`) | **StixFile** (MD5 / SHA-1 / SHA-256, name, size, score) + `related-to` System |
| Endpoints (`/v1/endpoints`) | **Identity** (`identity_class: system`); interface/tunnel IPs in the description |

> **Internal assets are System identities, not IOCs** — your fleet endpoints (and their internal
> IPs: `agent_ip`, interface/tunnel IPs) are modelled as `Identity(identity_class="system")` with the
> IPs in the description, **not** as IPv4-Addr observables. Only genuinely external artifacts (alert
> `url`, file hashes) become observables. (Pattern per OpenCTI maintainer review, PR #6164.)
>
> **Observables only** — no STIX Indicators are auto-created from binaries (analysts promote manually).
> Process name/GUID from alerts is folded into the Incident description (STIX 2.1 `Process` has no `name`).

## Incremental behavior
- **EDR alerts** have no time filter on the API → the connector filters client-side on
`last_occurrence_time` against stored state (`alerts_last_occurrence`).
- **Binaries** use the server-side `fromTime` window (state `binaries_last_seen`).
- **Endpoints** are re-listed each run (deduped by deterministic STIX IDs in OpenCTI).
- A category that errors does not advance its cursor (no data loss; safe retry next cycle).

## Requirements
- OpenCTI **7.260529.0** (pinned; `pycti==7.260529.0`).
- A Metras API key (`X-API-KEY`).

## Configuration

| Env var | Required | Default | Description |
|---|---|---|---|
| `OPENCTI_URL` | yes | — | OpenCTI base URL |
| `OPENCTI_TOKEN` | yes | — | OpenCTI API token |
| `CONNECTOR_ID` | yes | — | UUIDv4 for this connector |
| `CONNECTOR_NAME` | no | `Metras-Feed` | Connector name |
| `CONNECTOR_SCOPE` | no | `Metras` | Import scope |
| `CONNECTOR_LOG_LEVEL` | no | `info` | Log level |
| `CONNECTOR_DURATION_PERIOD` | yes | `PT1H` | ISO-8601 poll interval |
| `METRAS_API_BASE_URL` | no | `https://api.metras.sa/api` | Metras API base URL |
Comment on lines +45 to +48
| `METRAS_API_KEY` | yes | — | Metras API key |
| `METRAS_VERIFY_SSL` | no | `true` | Verify TLS certificates |
| `METRAS_IMPORT_ALERTS` | no | `true` | Import EDR alerts |
| `METRAS_IMPORT_BINARIES` | no | `true` | Import binaries |
| `METRAS_IMPORT_ENDPOINTS` | no | `true` | Import endpoints |
| `METRAS_BINARY_MALICIOUS_ONLY` | no | `true` | Only import banned/unsigned binaries |
| `METRAS_PAGE_SIZE` | no | `50` | Records per page |
| `METRAS_TLP_LEVEL` | no | `amber` | TLP marking (`clear`/`white`/`green`/`amber`/`red`) |

## Installation
```bash
cp config.yml.sample src/config.yml # or use env vars / docker-compose
docker compose up -d --build
```

## Troubleshooting
| Symptom | Cause / fix |
|---|---|
| `Metras API ping failed at startup` then exit | Bad `METRAS_API_KEY` or unreachable base URL |
| `Imported 0 incidents` after first run | Normal — alerts already imported; cursor advanced |
| `Cannot query field "s3"` | pycti/platform mismatch — keep `pycti==7.260529.0` |
| No Works in OpenCTI UI | Check `OPENCTI_TOKEN` permissions (Work API) |
1 change: 1 addition & 0 deletions external-import/metras/VERSION

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To remove - connector's version is declared in connector_manifest.json

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
19 changes: 19 additions & 0 deletions external-import/metras/__metadata__/connector_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"title": "Metras Feed Connector",
"slug": "metras-feed",
"description": "Imports Metras EDR alerts (with MITRE ATT&CK mapping), file binaries, and endpoint inventory into OpenCTI as STIX incidents, observables and system identities.",
"short_description": "External import connector for Metras EDR telemetry",
"logo": null,
"use_cases": ["Threat Intelligence"],
"verified": false,
"last_verified_date": null,
"playbook_supported": false,
"max_confidence_level": 50,
"support_version": ">=7.260529.0",
"subscription_link": null,
"source_code": null,
"manager_supported": false,
"container_version": "1.0.0",
Comment thread
Powlinett marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"container_version": "1.0.0",
"container_version": "rolling",

"container_image": "opencti/connector-metras-feed",
"container_type": "EXTERNAL_IMPORT"
}
22 changes: 22 additions & 0 deletions external-import/metras/config.yml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
opencti:
url: 'http://localhost:8080'
token: 'ChangeMe'

connector:
type: 'EXTERNAL_IMPORT'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To remove - connector's type must always be hardcoded, never configurable

id: 'ChangeMe' # UUIDv4
name: 'Metras-Feed'
scope: 'Metras'
log_level: 'info'
duration_period: 'PT1H' # ISO-8601: poll interval

metras:
api_base_url: 'https://api.metras.sa/api'
api_key: 'ChangeMe'
verify_ssl: true
import_alerts: true
import_binaries: true
import_endpoints: true
binary_malicious_only: true # only banned/unsigned binaries
page_size: 50
tlp_level: 'amber'
34 changes: 34 additions & 0 deletions external-import/metras/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
services:
connector-metras-feed:
build:
context: .
dockerfile: Dockerfile

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To remove - only pull the image from our Docker hub to stay consistent with the other connectors' docker-compose.Yml

image: opencti/connector-metras-feed:1.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
image: opencti/connector-metras-feed:1.0.0
image: opencti/connector-metras-feed:latest

environment:
- OPENCTI_URL=http://opencti:8080
- OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
- CONNECTOR_ID=${CONNECTOR_METRAS_FEED_ID}
- CONNECTOR_TYPE=EXTERNAL_IMPORT

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To remove - connector's type must always be hardcoded, never configurable

- CONNECTOR_NAME=Metras-Feed
- CONNECTOR_SCOPE=Metras
- CONNECTOR_LOG_LEVEL=info
- CONNECTOR_DURATION_PERIOD=PT1H
- METRAS_API_BASE_URL=https://api.metras.sa/api
- METRAS_API_KEY=${METRAS_API_KEY}
- METRAS_VERIFY_SSL=true
- METRAS_IMPORT_ALERTS=true
- METRAS_IMPORT_BINARIES=true
- METRAS_IMPORT_ENDPOINTS=true
- METRAS_BINARY_MALICIOUS_ONLY=true
- METRAS_PAGE_SIZE=50
- METRAS_TLP_LEVEL=amber
restart: always
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
mem_limit: 512m
pids_limit: 100
3 changes: 3 additions & 0 deletions external-import/metras/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
cd /opt/opencti-connector-metras-feed
exec python3 main.py
5 changes: 5 additions & 0 deletions external-import/metras/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[tool.coverage.run]
source = ["connector", "metras_client"]

[tool.coverage.report]
show_missing = true
4 changes: 4 additions & 0 deletions external-import/metras/src/connector/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from connector.connector import MetrasFeedConnector
from connector.settings import ConnectorSettings

__all__ = ["MetrasFeedConnector", "ConnectorSettings"]
Loading
Loading