feat(fortisiem-incidents): add external-import connector for FortiSIEM incidents (#6721)#6722
feat(fortisiem-incidents): add external-import connector for FortiSIEM incidents (#6721)#6722SamuelHassine wants to merge 5 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6722 +/- ##
===========================================
- Coverage 32.30% 0.42% -31.89%
===========================================
Files 1985 1900 -85
Lines 122106 119738 -2368
===========================================
- Hits 39444 504 -38940
- Misses 82662 119234 +36572
📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a new external-import/fortisiem-incidents connector that pulls incidents from the FortiSIEM REST API and ingests them into OpenCTI as STIX 2.1 Incidents, including related network observables, along with the usual packaging (Docker, compose, sample config) and metadata/testing scaffolding expected in this monorepo.
Changes:
- Implement FortiSIEM incidents REST client, STIX conversion, and connector scheduling/state handling.
- Add unit tests covering settings, client request/retry behavior, converter output, and connector orchestration.
- Add connector packaging + documentation: Dockerfile/entrypoint/compose, sample config, README, and generated metadata (manifest + config schema/doc).
Reviewed changes
Copilot reviewed 24 out of 26 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| external-import/fortisiem-incidents/src/fortisiem_client/api_client.py | FortiSIEM REST client with retry/backoff and incident extraction. |
| external-import/fortisiem-incidents/src/fortisiem_client/init.py | Exposes FortiSIEM client package API. |
| external-import/fortisiem-incidents/src/connector/settings.py | Connector settings models (OpenCTI + external-import + FortiSIEM params). |
| external-import/fortisiem-incidents/src/connector/converter_to_stix.py | Converts FortiSIEM incident payloads into STIX Incident/SCOs/relationships. |
| external-import/fortisiem-incidents/src/connector/connector.py | Main connector loop: incremental “since”, bundle creation, send + state update. |
| external-import/fortisiem-incidents/src/connector/init.py | Exposes connector public API (settings + connector class). |
| external-import/fortisiem-incidents/src/main.py | Entry point wiring settings → helper → connector run. |
| external-import/fortisiem-incidents/src/requirements.txt | Runtime dependencies for the connector. |
| external-import/fortisiem-incidents/src/config.yml.sample | Sample YAML configuration for local/manual use. |
| external-import/fortisiem-incidents/tests/conftest.py | Test path setup for importing connector modules. |
| external-import/fortisiem-incidents/tests/test_main.py | Tests instantiation of settings and connector wiring. |
| external-import/fortisiem-incidents/tests/test_client.py | Tests client auth, response-shape handling, and retry behavior. |
| external-import/fortisiem-incidents/tests/test_converter.py | Tests timestamp/severity mapping and STIX object creation. |
| external-import/fortisiem-incidents/tests/test_connector.py | Tests orchestration: collection, since/state logic, bundling and error handling. |
| external-import/fortisiem-incidents/tests/tests_connector/test_settings.py | Tests configuration validation and defaults. |
| external-import/fortisiem-incidents/tests/test-requirements.txt | Test dependencies for this connector. |
| external-import/fortisiem-incidents/tests/init.py | Marks tests as a package. |
| external-import/fortisiem-incidents/tests/tests_connector/init.py | Marks tests subpackage. |
| external-import/fortisiem-incidents/README.md | Connector usage, configuration, and deployment docs. |
| external-import/fortisiem-incidents/Dockerfile | Builds the connector image (Alpine Python) and installs deps. |
| external-import/fortisiem-incidents/entrypoint.sh | Container entrypoint launching the connector. |
| external-import/fortisiem-incidents/docker-compose.yml | Example docker-compose service configuration. |
| external-import/fortisiem-incidents/.dockerignore | Docker build context exclusions. |
| external-import/fortisiem-incidents/metadata/connector_manifest.json | Connector manifest for the global connectors catalog. |
| external-import/fortisiem-incidents/metadata/connector_config_schema.json | Generated JSON schema for env var configuration. |
| external-import/fortisiem-incidents/metadata/CONNECTOR_CONFIG_DOC.md | Generated configuration documentation from schema. |
Review-and-fix passIndependent senior review of the full connector plus the 5 open Copilot threads (all valid; all fixed). Correctness fixes:
Consistency / docs:
Tests: added coverage for the TLP:CLEAR marking, the SCO Status: all CI checks are green (including
|
Second review-and-fix pass summaryIndependent senior re-review of the full Code fixes (commit d5b1ea0):
Tests: added the fetch-failure raise (request failure and non-JSON), the no-state-advance and in-error work finalize, and the deterministic incident id. Status: all CI checks are green (codecov/patch and codecov/project, target 80%), Remaining (non-CI) blocker: |
Review-and-fix pass summaryIndependent senior re-review of the full
Remaining (non-CI) blocker: |
…M incidents Add a new EXTERNAL_IMPORT connector that periodically pulls FortiSIEM incidents and imports them into OpenCTI as STIX Incidents with related observables. This is the import side of a bidirectional FortiSIEM integration. Refs #6721
Independent review of the new connector plus the open Copilot threads.
- converter_to_stix: tlp_level="clear" no longer aliases stix2.TLP_WHITE. It now
emits a distinct TLP:CLEAR statement marking carrying
x_opencti_definition="TLP:CLEAR", matching the connectors-sdk marking, so
bundles surface TLP:CLEAR instead of TLP:WHITE.
- converter_to_stix: the IPv4/IPv6/domain observables now attribute the author
via the x_opencti_created_by_ref custom property instead of created_by_ref,
which OpenCTI ignores for SCOs (the observables were losing attribution).
- api_client: _request passes its context via meta={...} and now retries only on
connection/timeout errors, 429 and 5xx; other 4xx responses (401/403/404) fail
fast instead of being retried three times (mirrors the paired stream/fortisiem
connector).
- docker-compose.yml uses the "ChangeMe" placeholder consistently (matching
config.yml.sample), and a grammar typo in tests/test-requirements.txt is fixed.
- Extend the converter and client test suites accordingly.
…nd finalize work Address the open Copilot review threads plus an independent senior review. - api_client.py: get_incidents now raises FortiSIEMClientError when the request fails (no response after retries) or returns a non-JSON body, instead of returning [] (which was indistinguishable from "no new incidents"). The connector therefore no longer advances last_run past a window it never fetched; previously a transient FortiSIEM outage advanced the cursor and silently skipped incidents (data loss). - connector.py: the initiated work is finalized with in_error=True on the failure path, so a failed run no longer leaves a dangling in-progress work in OpenCTI. - converter_to_stix.py: Incident.generate_id is seeded with the source timestamp only; with no incidentFirstSeen/firstSeenTime the STIX created still falls back to "now" but the id seed is None, so a re-imported incident keeps a stable id instead of duplicating each run (consistent with the corelight connector). - docs: scope is documented as mandatory in config.yml.sample, docker-compose.yml and README.md (it is required by the SDK base settings, with no default), and the config sample tlp_level list now includes white. - tests: cover the fetch-failure raise (request failure and non-JSON), the no-state-advance and in-error work finalize, and the deterministic incident id.
…test get_incidents raised "no response after retries" even on the fail-fast 4xx path, where a response did exist; the message is now generic (the specifics are already logged in _request). The settings test now asserts on the exception message via pytest.raises(match=...) - matching the connectors-sdk own tests - instead of the brittle str(ExceptionInfo) check.
…60615.0 - Normalize every file under external-import/fortisiem-incidents/ to LF line endings. The connector was committed with CRLF; in a Linux container CRLF breaks the entrypoint shebang (/bin/sh\r) and the cd command, so the container entrypoint can fail at runtime, and it is inconsistent with the rest of the repo. The change is line-endings only - content is unchanged. - Bump the pinned pycti to 7.260615.0 (the latest release) to match connectors-sdk@master and the pycti shipped by opencti@master, so the run_test.sh environment resolves consistently after the rebase onto current master. Align the README minimum and the manifest support_version to the same version.
a3ab669 to
1882b72
Compare
Full rebase + pycti bump
Remaining (non-CI) blocker: |
Proposed changes
This PR adds a new
EXTERNAL_IMPORTconnectorexternal-import/fortisiem-incidentsthat imports FortiSIEM incidents into OpenCTI as STIX Incidents. It is the import side of a bidirectional FortiSIEM integration (paired with thestream/fortisiemconnector that pushes IOCs to FortiSIEM Watch Lists).connectors-sdksettings pattern, with unit tests and connector metadata (manifest, config schema, configuration documentation).Related issues
Closes #6721
Type of change
Maintainer review (independent review-and-fix)
Independent senior review of the full connector across several passes, on top of the Copilot threads. Substantive fixes:
/bin/sh\r) and thecdcommand, so the container entrypoint can fail at runtime; it is also inconsistent with the rest of the repo. The wholeexternal-import/fortisiem-incidents/tree is now normalized to LF (content otherwise unchanged - the diff is line-endings only).get_incidentsraisesFortiSIEMClientErroron a fetch failure (no response after retries, or a non-JSON body) instead of returning[]. The raise propagates beforeprocess_messageadvanceslast_run, so a transient FortiSIEM outage no longer moves the cursor past a window it never fetched and silently skips incidents; the failure path also finalizes the OpenCTI work within_error=Trueinstead of leaving a dangling work.converter_to_stix.py:tlp_level="clear"emits a distinct TLP:CLEAR statement marking (x_opencti_definition="TLP:CLEAR") instead of aliasingstix2.TLP_WHITE, and the IPv4/IPv6/domain observables attribute the author via thex_opencti_created_by_refcustom property (OpenCTI ignorescreated_by_refon SCOs).converter_to_stix.py: the Incident id is seeded with the source timestamp only, so a re-imported incident with no first-seen timestamp keeps a stable id instead of duplicating each run.fortisiem_client/api_client.py:_requestlogs viameta={...}and retries only on connection/timeout errors, 429 and 5xx; other 4xx (401/403/404) fail fast (consistent with the pairedstream/fortisiemconnector).src/requirements.txt/README.md/__metadata__/connector_manifest.json: the pinnedpyctiis bumped to7.260615.0, the latest released version, matching bothconnectors-sdk@masterand thepyctishipped byopencti@master; the README minimum and manifestsupport_versionare aligned to the same version.Tests
Unit tests cover the settings model (validation, defaults), the FortiSIEM client (auth org prefix, list/dict response shapes, fetch-failure raises, 4xx fail-fast vs 429/5xx retry), the converter (TLP:CLEAR marking, SCO
x_opencti_created_by_refattribution, severity mapping, deterministic incident id, observable typing) and the connector (no state advance / workin_erroron fetch failure). 48 unit tests pass;black/isort/flake8 --ignore=E,Wclean locally, and the STIX ID linter is clean (deterministic ids viagenerate_id).Checklist
Status
All CI checks are green (tests, lint/format, STIX ID linter,
codecov/patchandcodecov/project) and there are 0 unresolved review threads (13 resolved). The branch has been rebased onto the latestmasteras a clean linear history of signed commits, so it merges without a merge commit and the localconnectors-sdkused byrun_test.shresolves the samepycti==7.260615.0consistently.mergeStateStatusis BLOCKED only becausereviewDecisionisREVIEW_REQUIRED- the PR needs one approving review from a maintainer other than me (as the author I cannot self-approve).