Skip to content
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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
149 changes: 149 additions & 0 deletions external-import/metras/__metadata__/connector_config_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://www.filigran.io/connectors/w_config.schema.json",
"type": "object",
"properties": {
"OPENCTI_URL": {
"description": "The base URL of the OpenCTI instance.",
"format": "uri",
"maxLength": 2083,
"minLength": 1,
"type": "string"
},
"OPENCTI_TOKEN": {
"description": "The API token to connect to OpenCTI.",
"type": "string"
},
"CONNECTOR_NAME": {
"default": "Metras-Feed",
"examples": [
"Metras-Feed"
],
"type": "string"
},
"CONNECTOR_SCOPE": {
"description": "The scope of the connector, e.g. 'flashpoint'.",
"items": {
"type": "string"
},
"type": "array"
},
"CONNECTOR_LOG_LEVEL": {
"default": "error",
"description": "The minimum level of logs to display.",
"enum": [
"debug",
"info",
"warn",
"warning",
"error"
],
"type": "string"
},
"CONNECTOR_TYPE": {
"const": "EXTERNAL_IMPORT",
"default": "EXTERNAL_IMPORT",
"type": "string"
},
"CONNECTOR_DURATION_PERIOD": {
"default": "PT1H",
"examples": [
"PT1H"
],
"format": "duration",
"type": "string"
},
"METRAS_API_BASE_URL": {
"default": "https://api.metras.sa/api",
"description": "Base URL of the Metras API.",
"examples": [
"https://api.metras.sa/api"
],
"format": "uri",
"maxLength": 2083,
"minLength": 1,
"type": "string"
},
"METRAS_API_KEY": {
"description": "Metras API key (X-API-KEY header).",
"examples": [
"ChangeMe"
],
"format": "password",
"type": "string",
"writeOnly": true
},
"METRAS_VERIFY_SSL": {
"default": true,
"description": "Verify TLS certificates.",
"examples": [
true
],
"type": "boolean"
},
"METRAS_IMPORT_ALERTS": {
"default": true,
"description": "Import EDR alerts.",
"examples": [
true
],
"type": "boolean"
},
"METRAS_IMPORT_BINARIES": {
"default": true,
"description": "Import binaries as StixFile.",
"examples": [
true
],
"type": "boolean"
},
"METRAS_IMPORT_ENDPOINTS": {
"default": true,
"description": "Import endpoints as System identities.",
"examples": [
true
],
"type": "boolean"
},
"METRAS_BINARY_MALICIOUS_ONLY": {
"default": true,
"description": "Only import banned/unsigned binaries (reduces noise).",
"examples": [
true
],
"type": "boolean"
},
"METRAS_PAGE_SIZE": {
"default": 50,
"description": "Records per page.",
"examples": [
50
],
"maximum": 500,
"minimum": 1,
"type": "integer"
},
"METRAS_TLP_LEVEL": {
"default": "amber",
"description": "TLP marking applied to imported objects.",
"enum": [
"clear",
"white",
"green",
"amber",
"red"
],
"examples": [
"amber"
],
"type": "string"
}
},
"required": [
"OPENCTI_URL",
"OPENCTI_TOKEN",
"CONNECTOR_SCOPE",
"METRAS_API_KEY"
],
"additionalProperties": true
}
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": "https://github.com/OpenCTI-Platform/connectors/tree/master/external-import/metras",
"manager_supported": false,
"container_version": "1.0.0",
Comment thread
Powlinett marked this conversation as resolved.
"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'
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
image: opencti/connector-metras-feed:1.0.0
environment:
- OPENCTI_URL=http://opencti:8080
- OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
- CONNECTOR_ID=${CONNECTOR_METRAS_FEED_ID}
- CONNECTOR_TYPE=EXTERNAL_IMPORT
- 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