feat(metras): add Metras internal-enrichment connector for fleet-presence lookups (#7042) - #7040
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new Metras INTERNAL_ENRICHMENT connector under internal-enrichment/metras/ to enrich IPv4-Addr and StixFile observables with “fleet presence” context from the Metras EDR API, emitting Notes and optional System identities + related-to relationships.
Changes:
- Introduces the connector runtime (settings model, Metras API client with retries, enrichment orchestration, and STIX conversion).
- Adds containerization + deployment artifacts (Dockerfile, compose, entrypoint, sample config, metadata/manifest, versioning).
- Adds a unit test suite for settings, API client, orchestration, and utils (but no converter-specific tests despite the PR description stating otherwise).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| internal-enrichment/metras/VERSION | Declares connector version. |
| internal-enrichment/metras/src/requirements.txt | Pins runtime dependencies (pycti, stix2, requests, connectors-sdk). |
| internal-enrichment/metras/src/main.py | Connector entrypoint wiring settings → helper → connector run loop. |
| internal-enrichment/metras/src/connector/init.py | Exposes main connector classes for imports. |
| internal-enrichment/metras/src/connector/settings.py | Pydantic/SDK-based configuration models (connector + Metras settings). |
| internal-enrichment/metras/src/connector/utils.py | Refang + basic validators used by connector/tests. |
| internal-enrichment/metras/src/connector/converter_to_stix.py | Builds deterministic STIX Note/Identity/Relationship outputs. |
| internal-enrichment/metras/src/connector/connector.py | Enrichment orchestration (scope/TLP gate, lookups, bundle send). |
| internal-enrichment/metras/src/metras_client/init.py | Exports client + error type. |
| internal-enrichment/metras/src/metras_client/api_client.py | HTTP client wrapper for Metras API endpoints + retry strategy. |
| internal-enrichment/metras/tests/test-requirements.txt | Test dependency entrypoint for isolated test runs. |
| internal-enrichment/metras/tests/conftest.py | Adds src/ to import path for tests. |
| internal-enrichment/metras/tests/test_main.py | Unit tests for utils (refang/validators). |
| internal-enrichment/metras/tests/test_connector/test_settings.py | Unit tests for settings/config model behavior. |
| internal-enrichment/metras/tests/test_connector/test_client.py | Unit tests for HTTP client behaviors (mocked session). |
| internal-enrichment/metras/tests/test_connector/test_orchestration.py | Unit tests for enrichment orchestration (mocked helper/client). |
| internal-enrichment/metras/README.md | Connector documentation (scope, behavior, configuration, usage). |
| internal-enrichment/metras/pyproject.toml | Coverage configuration for connector packages. |
| internal-enrichment/metras/entrypoint.sh | Container entrypoint script. |
| internal-enrichment/metras/Dockerfile | Container build (Alpine Python 3.12, non-root user). |
| internal-enrichment/metras/docker-compose.yml | Hardened compose example for running the connector. |
| internal-enrichment/metras/config.yml.sample | Sample YAML config for local/manual runs. |
| internal-enrichment/metras/.dockerignore | Docker build context exclusions. |
| internal-enrichment/metras/metadata/connector_manifest.json | Connector manifest metadata for OpenCTI ecosystem. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (3)
internal-enrichment/metras/src/connector/connector.py:101
- Logging context should be passed via the
meta=keyword for consistency with other connectors, rather than as a positional dict argument. See e.g. external-import/email-intel-imap/src/base_connector/connector.py:103-123.
self.helper.connector_logger.error(
f"[CONNECTOR] {getattr(func, '__name__', 'call')} failed",
{"error": str(exc)},
)
internal-enrichment/metras/src/connector/connector.py:133
- Logging context should be passed via the
meta=keyword for consistency with other connectors, rather than as a positional dict argument. See e.g. external-import/email-intel-imap/src/base_connector/connector.py:103-123.
self.helper.connector_logger.warning(
"[CONNECTOR] Skipped: TLP exceeds max",
{"tlp": level, "max": self._max_tlp.value},
)
internal-enrichment/metras/src/connector/connector.py:166
- Logging context should be passed via the
meta=keyword for consistency with other connectors, rather than as a positional dict argument. See e.g. external-import/email-intel-imap/src/base_connector/connector.py:103-123.
self.helper.connector_logger.error("[CONNECTOR] Error", {"error": str(err)})
There was a problem hiding this comment.
Hi @Khidr6G , thanks for your contribution.
Before addressing Copilot's comments, please rebase your branch onto the latest version of master to ensure the CI performs the same checks as on master 🙏
Also, please rename the connector folder so it has a unique name (currently same as external-import/metras).
The rule is: folder name == manifest's slug == docker's image name 👌
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #7040 +/- ##
===========================================
+ Coverage 35.64% 84.63% +48.98%
===========================================
Files 2048 10 -2038
Lines 125310 716 -124594
===========================================
- Hits 44672 606 -44066
+ Misses 80638 110 -80528
📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
Proposed changes
INTERNAL_ENRICHMENTconnector for the Metras EDR platform(
api.metras.sa,X-API-KEY) underinternal-enrichment/metras/.IPv4-AddrandStixFileobservables:agent_ip) + endpoint inventory (interface_ip).sha256/sha1) + binary details (md5).Notesummarizing local presence, plus an optionalIdentity(identity_class="system")for each matched fleet endpoint and arelated-torelationship. No
Sightingis emitted (stix2 forbids sighting an SCO) and noInfrastructureobjects are created.helper.check_max_tlp), refang before externalcalls,
entity_in_scopeguard withfile → stixfilealias, and a raisedValueErrorontotal lookup failure (never silently completes with 0 objects). Playbook-compatible
(
playbook_supported: true).enrichment_entity.standard_idthenstix_entity.id(never the internal DB id); deterministic IDs for notes/identities/relationships.pydantic.SecretStr; typed SDK config (TLPLevel,ListFromString);structured logging; CIS-hardened compose; non-root
python:3.12-alpineimage.pyproject.tomlcoverageconfig.
black/isort --profile black/flake8 --ignore=E,Wclean.7.260529.0(pycti==7.260529.0,connectors-sdk @7.260529.0,support_version ">=7.260529.0").Related issues
Checklist
Further comments
Scope is intentionally limited to
IPv4-AddrandStixFile: Metras has no value-filterablelookup for domains/URLs (the
/v4/threats/detailendpoint requires incident identifiers, not asingle observable value), so enriching those types is not possible with the current API.