Skip to content
Merged
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
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,42 @@ Detailed per-version release notes are available in [`docs/releases/`](docs/rele

---

## [Unreleased]

### Added — Federated Multi-GNAT Deployment

**Federation layer (`gnat/federation/`)**
- `FederationPeer` dataclass: models a remote GNAT peer with `peer_id`, `taxii_url`, `api_key`, `direction` (pull/push/both), `max_tlp` ceiling, optional `parent_peer_id` for hierarchical topologies, `workspace_filter` (explicit opt-in required — empty list = nothing shared), and sync state tracking (`last_sync_at`, `last_sync_status`)
- `PeerRegistry`: JSON-backed CRUD store for peer configuration (`~/.gnat/federation_peers.json`); `from_config()` parses `[federation.peer.*]` INI sections; `update_sync_status()` persists last sync result for incremental resumption
- `PeerSyncService`: pull and push orchestration with TLP gate (`_tlp_allowed`) enforced on every object before transmission; last-write-wins conflict resolution on STIX `modified` timestamp; `sync_from_peer()` and `push_to_peer()` with `FederationError` for unrecoverable failures; `PullResult` and `PushResult` summary classes
- `FederationScheduler`: creates one `FeedJob` per enabled peer; `start()` / `stop()` lifecycle; `trigger(peer_id)` for immediate one-off sync; `status()` returns per-peer sync state; persists `last_sync_at` to `PeerRegistry` via `on_success` callback for incremental resumption across restarts
- `FederationTopology`: `ancestors()`, `descendants()`, `parent()`, `children()`, `is_leaf()`, `is_root()`, cycle detection; `effective_max_tlp()` applies hierarchy defaults (AMBER up child→parent, GREEN down parent→child); `hierarchy_graph()` returns JSON topology for REST API

**GNATRemoteConnector (`gnat/connectors/gnat_remote/`)**
- `GNATRemoteConnector(BaseClient, ConnectorMixin)`: TAXII 2.1 client for remote GNAT instances; `authenticate()` sets Bearer token; `health_check()` pings discovery endpoint; `list_collections()`, `fetch_objects()`, `push_bundle()`, `list_objects()`, `get_object()`, `upsert_object()`, `delete_object()`; `to_stix()` / `from_stix()` are pass-throughs (both sides speak STIX 2.1 natively)
- Registered as `"gnat_remote"` in `CLIENT_REGISTRY`

**REST API (`gnat/serve/routers/federation.py`)**
- `GET /api/federation/peers` — list all peers with current sync status
- `POST /api/federation/peers` — register a new peer
- `DELETE /api/federation/peers/{peer_id}` — remove a peer and cancel its sync job
- `GET /api/federation/peers/{peer_id}/health` — ping remote TAXII discovery endpoint, return latency
- `POST /api/federation/peers/{peer_id}/sync` — trigger immediate sync (uses scheduler if running, falls back to direct sync)
- `GET /api/federation/topology` — mesh/hierarchy graph JSON (nodes, edges, hierarchy_edges)
- `create_app()` accepts `federation_registry`, `federation_scheduler`, `federation_sync_service` parameters

**Export (`gnat/export/delivery/targets.py`)**
- `TAXIIPushDelivery`: pushes STIX 2.1 bundles to a remote TAXII collection; wraps `HTTPDelivery` with TAXII media type headers

**Configuration**
- `config/config.ini.example`: added `[federation]`, `[federation.peer.acme-east]` (mesh), `[federation.peer.hospital-a]` and `[federation.peer.health-system-parent]` (hierarchical healthcare example)

**Tests**
- `tests/unit/federation/test_federation.py`: 60 tests covering `FederationPeer`, `PeerRegistry`, `PeerSyncService` TLP gate + conflict resolution, `FederationTopology` traversal + hierarchy graph, `FederationScheduler` lifecycle
- `tests/unit/connectors/test_gnat_remote.py`: 19 tests covering authenticate, health_check, list_collections, fetch_objects, push_bundle, CRUD operations

---

## [1.4.0]

### Added — Analyst OS Layer (Phase 3)
Expand Down
40 changes: 40 additions & 0 deletions config/config.ini.example
Original file line number Diff line number Diff line change
Expand Up @@ -801,3 +801,43 @@ auth_type = api_key
api_key = YOUR_FEED_API_KEY
api_key_header = X-Api-Key
stix_types = indicator

# =============================================================================
# Federation — Federated multi-GNAT deployment (TAXII 2.1 peer sync)
# =============================================================================
# Enable to share STIX 2.1 threat intelligence between independent GNAT
# instances using either peer-to-peer (mesh) or hierarchical topologies.
# Explicit workspace_filter required — empty list means nothing is shared.

[federation]
enabled = true
registry = ~/.gnat/federation_peers.json

# --- Mesh peer example (peer-to-peer, both directions) ---
[federation.peer.acme-east]
taxii_url = https://gnat-east.acme.com/taxii2/
api_key = Bearer peer-secret-here
direction = both
max_tlp = amber
sync_interval = 3600
workspace_filter = threats-2025,apt-tracking
# parent_peer_id = (leave empty for mesh peer)

# --- Hierarchical example: subsidiary → parent (healthcare) ---
[federation.peer.hospital-a]
taxii_url = https://hospital-a.health-system.example/taxii2/
api_key = Bearer hosp-a-secret
direction = both
max_tlp = amber
parent_peer_id = health-system-parent
sync_interval = 1800
workspace_filter = clinical-indicators,threat-intel

# Parent node (receives AMBER from children, sends GREEN down)
[federation.peer.health-system-parent]
taxii_url = https://gnat.health-system.example/taxii2/
api_key = Bearer parent-secret
direction = both
max_tlp = green
sync_interval = 3600
workspace_filter = sector-intel,advisories
4 changes: 4 additions & 0 deletions gnat/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
from gnat.connectors.xsoar.client import XSOARClient
from gnat.connectors.yeti.client import YetiClient
from gnat.connectors.zeek.client import ZeekClient
from gnat.connectors.gnat_remote.connector import GNATRemoteConnector
from gnat.connectors.zerofox.client import ZeroFoxClient

CLIENT_REGISTRY: dict = {
Expand Down Expand Up @@ -212,6 +213,8 @@
# OSINT feed connectors
"osint_feed": OsintFeedConnector,
"cisco_umbrella": CiscoUmbrellaClient,
# Federation
"gnat_remote": GNATRemoteConnector,
}

__all__ = [
Expand Down Expand Up @@ -268,4 +271,5 @@
"ShodanClient",
"OsintFeedConnector",
"CiscoUmbrellaClient",
"GNATRemoteConnector",
]
7 changes: 7 additions & 0 deletions gnat/connectors/gnat_remote/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Bill Halpin
"""gnat.connectors.gnat_remote — Remote GNAT instance connector (TAXII 2.1)."""

from gnat.connectors.gnat_remote.connector import GNATRemoteConnector

__all__ = ["GNATRemoteConnector"]
Loading
Loading