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 internal-enrichment/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 internal-enrichment/metras/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.12-alpine

ENV CONNECTOR_TYPE=INTERNAL_ENRICHMENT
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

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

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-enrichment && \
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-enrichment
USER connector

ENTRYPOINT ["/entrypoint.sh"]
58 changes: 58 additions & 0 deletions internal-enrichment/metras/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# OpenCTI Metras Enrichment Connector (INTERNAL_ENRICHMENT)

Answers *"have I seen this in my fleet?"* by querying the [Metras](https://dashboard.metras.sa/)
platform when you enrich an observable in OpenCTI. Adds a context **Note** (and
**System identity** links to matched fleet endpoints) summarising local EDR/endpoint presence.

## Supported observables (`CONNECTOR_SCOPE`)

| Observable | Metras lookups | Output |
|---|---|---|
| **IPv4-Addr** | `/v1/edr/alerts?agent_ip=`, `/v1/endpoints?interface_ip=` | Note (alert + endpoint hits) + `related-to` **System identity** per matched endpoint |
| **StixFile** | `/v1/edr/binary/list?query=sha256:` / `?query=sha1:`, `/v1/edr/binary/details?md5=` | Note (publisher, signer, signature/runnability status, first/last seen) + System identity |

> Matched fleet endpoints are emitted as `Identity(identity_class="system")` (internal assets,
> not IOCs), consistent with the Feed connector and OpenCTI maintainer guidance (PR #6164).

> **Why only IPv4 + StixFile?** Metras exposes no value-lookup for domains or URLs, and
> `/v4/threats/detail` requires incident identifiers (title+direction+type), not an observable
> value. Domain/URL enrichment is therefore not offered.
>
> Fleet presence is conveyed via **Notes + relationships**, not STIX Sightings (the `stix2`
> library forbids a Sighting referencing an observable).

## Behavior & safety
- **Custom TLP check** (not `helper.check_max_tlp`) — skips observables above `METRAS_MAX_TLP`.
- **Refangs** values before querying Metras.
- **Partial results**: each lookup is wrapped; if at least one succeeds, results are sent.
If *all* lookups fail, a `ValueError` is raised so the failure is visible in the OpenCTI UI.
- Playbook-compatible (`playbook_compatible=True`, `entity_in_scope()` guard).

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

## Configuration

| Env var | Required | Default | Description |
|---|---|---|---|
| `OPENCTI_URL` / `OPENCTI_TOKEN` / `CONNECTOR_ID` | yes | — | Standard connector settings |
| `CONNECTOR_NAME` | no | `Metras-Enrichment` | Connector name |
| `CONNECTOR_SCOPE` | no | `IPv4-Addr,StixFile` | Observable types to enrich |
| `CONNECTOR_AUTO` | no | `false` | Auto-enrich on observable creation |
| `CONNECTOR_LOG_LEVEL` | no | `info` | Log level |
| `METRAS_API_BASE_URL` | no | `https://api.metras.sa/api` | Metras API base URL |
| `METRAS_API_KEY` | yes | — | Metras API key |
| `METRAS_VERIFY_SSL` | no | `true` | Verify TLS certificates |
| `METRAS_MAX_TLP` | no | `amber+strict` | Max TLP to enrich (`clear`/`white`/`green`/`amber`/`amber+strict`/`red`) |

## Usage
Right-click an IPv4 or file-hash observable → **Enrich** → *Metras-Enrichment*, or set
`CONNECTOR_AUTO=true` to enrich automatically. Triggering via API uses
`stixCoreObjectEdit.askEnrichment(connectorId)` on OpenCTI 7.260529.0+.

## Troubleshooting
| Symptom | Cause / fix |
|---|---|
| "No Metras fleet data found" | The observable isn't present in your Metras fleet (expected for unknown IOCs) |
| Enrichment fails with auth error | Bad `METRAS_API_KEY` |
| File observable not enriched | Ensure `StixFile` is in `CONNECTOR_SCOPE` |
1 change: 1 addition & 0 deletions internal-enrichment/metras/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
19 changes: 19 additions & 0 deletions internal-enrichment/metras/__metadata__/connector_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"title": "Metras Enrichment Connector",
"slug": "metras-enrichment",
"description": "Enriches IPv4 and file-hash observables by querying Metras for fleet presence (EDR alerts, endpoint inventory, binary inventory), returning context Notes and system-identity links ('have I seen this in my environment?').",
"short_description": "Enrichment connector for Metras fleet sightings",
"logo": null,
"use_cases": ["Enrichment & Analysis"],
"verified": false,
"last_verified_date": null,
"playbook_supported": true,
"max_confidence_level": 50,
"support_version": ">=7.260529.0",
"subscription_link": null,
"source_code": null,
"manager_supported": false,
"container_version": "1.0.0",
"container_image": "opencti/connector-metras-enrichment",
"container_type": "INTERNAL_ENRICHMENT"
}
17 changes: 17 additions & 0 deletions internal-enrichment/metras/config.yml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
opencti:
url: 'http://localhost:8080'
token: 'ChangeMe'

connector:
type: 'INTERNAL_ENRICHMENT'
id: 'ChangeMe' # UUIDv4
name: 'Metras-Enrichment'
scope: 'IPv4-Addr,StixFile'
log_level: 'info'
auto: false

metras:
api_base_url: 'https://api.metras.sa/api'
api_key: 'ChangeMe'
verify_ssl: true
max_tlp: 'amber+strict'
29 changes: 29 additions & 0 deletions internal-enrichment/metras/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
services:
connector-metras-enrichment:
build:
context: .
dockerfile: Dockerfile
image: opencti/connector-metras-enrichment:1.0.0
environment:
- OPENCTI_URL=http://opencti:8080
- OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
- CONNECTOR_ID=${CONNECTOR_METRAS_ENRICHMENT_ID}
- CONNECTOR_TYPE=INTERNAL_ENRICHMENT
- CONNECTOR_NAME=Metras-Enrichment
- CONNECTOR_SCOPE=IPv4-Addr,StixFile
- CONNECTOR_LOG_LEVEL=info
- CONNECTOR_AUTO=false
- METRAS_API_BASE_URL=https://api.metras.sa/api
- METRAS_API_KEY=${METRAS_API_KEY}
- METRAS_VERIFY_SSL=true
- METRAS_MAX_TLP=amber+strict
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 internal-enrichment/metras/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
cd /opt/opencti-connector-metras-enrichment
exec python3 main.py
5 changes: 5 additions & 0 deletions internal-enrichment/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 internal-enrichment/metras/src/connector/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from connector.connector import MetrasEnrichmentConnector
from connector.settings import ConnectorSettings

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