Skip to content

Commit cbce13d

Browse files
authored
Merge pull request #90 from wrhalpin/claude/add-claude-documentation-k8vvJ
Add federated multi-GNAT deployment layer
2 parents c2afa38 + 9898feb commit cbce13d

16 files changed

Lines changed: 3144 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,42 @@ Detailed per-version release notes are available in [`docs/releases/`](docs/rele
88

99
---
1010

11+
## [Unreleased]
12+
13+
### Added — Federated Multi-GNAT Deployment
14+
15+
**Federation layer (`gnat/federation/`)**
16+
- `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`)
17+
- `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
18+
- `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
19+
- `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
20+
- `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
21+
22+
**GNATRemoteConnector (`gnat/connectors/gnat_remote/`)**
23+
- `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)
24+
- Registered as `"gnat_remote"` in `CLIENT_REGISTRY`
25+
26+
**REST API (`gnat/serve/routers/federation.py`)**
27+
- `GET /api/federation/peers` — list all peers with current sync status
28+
- `POST /api/federation/peers` — register a new peer
29+
- `DELETE /api/federation/peers/{peer_id}` — remove a peer and cancel its sync job
30+
- `GET /api/federation/peers/{peer_id}/health` — ping remote TAXII discovery endpoint, return latency
31+
- `POST /api/federation/peers/{peer_id}/sync` — trigger immediate sync (uses scheduler if running, falls back to direct sync)
32+
- `GET /api/federation/topology` — mesh/hierarchy graph JSON (nodes, edges, hierarchy_edges)
33+
- `create_app()` accepts `federation_registry`, `federation_scheduler`, `federation_sync_service` parameters
34+
35+
**Export (`gnat/export/delivery/targets.py`)**
36+
- `TAXIIPushDelivery`: pushes STIX 2.1 bundles to a remote TAXII collection; wraps `HTTPDelivery` with TAXII media type headers
37+
38+
**Configuration**
39+
- `config/config.ini.example`: added `[federation]`, `[federation.peer.acme-east]` (mesh), `[federation.peer.hospital-a]` and `[federation.peer.health-system-parent]` (hierarchical healthcare example)
40+
41+
**Tests**
42+
- `tests/unit/federation/test_federation.py`: 60 tests covering `FederationPeer`, `PeerRegistry`, `PeerSyncService` TLP gate + conflict resolution, `FederationTopology` traversal + hierarchy graph, `FederationScheduler` lifecycle
43+
- `tests/unit/connectors/test_gnat_remote.py`: 19 tests covering authenticate, health_check, list_collections, fetch_objects, push_bundle, CRUD operations
44+
45+
---
46+
1147
## [1.4.0]
1248

1349
### Added — Analyst OS Layer (Phase 3)

config/config.ini.example

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,3 +801,43 @@ auth_type = api_key
801801
api_key = YOUR_FEED_API_KEY
802802
api_key_header = X-Api-Key
803803
stix_types = indicator
804+
805+
# =============================================================================
806+
# Federation — Federated multi-GNAT deployment (TAXII 2.1 peer sync)
807+
# =============================================================================
808+
# Enable to share STIX 2.1 threat intelligence between independent GNAT
809+
# instances using either peer-to-peer (mesh) or hierarchical topologies.
810+
# Explicit workspace_filter required — empty list means nothing is shared.
811+
812+
[federation]
813+
enabled = true
814+
registry = ~/.gnat/federation_peers.json
815+
816+
# --- Mesh peer example (peer-to-peer, both directions) ---
817+
[federation.peer.acme-east]
818+
taxii_url = https://gnat-east.acme.com/taxii2/
819+
api_key = Bearer peer-secret-here
820+
direction = both
821+
max_tlp = amber
822+
sync_interval = 3600
823+
workspace_filter = threats-2025,apt-tracking
824+
# parent_peer_id = (leave empty for mesh peer)
825+
826+
# --- Hierarchical example: subsidiary → parent (healthcare) ---
827+
[federation.peer.hospital-a]
828+
taxii_url = https://hospital-a.health-system.example/taxii2/
829+
api_key = Bearer hosp-a-secret
830+
direction = both
831+
max_tlp = amber
832+
parent_peer_id = health-system-parent
833+
sync_interval = 1800
834+
workspace_filter = clinical-indicators,threat-intel
835+
836+
# Parent node (receives AMBER from children, sends GREEN down)
837+
[federation.peer.health-system-parent]
838+
taxii_url = https://gnat.health-system.example/taxii2/
839+
api_key = Bearer parent-secret
840+
direction = both
841+
max_tlp = green
842+
sync_interval = 3600
843+
workspace_filter = sector-intel,advisories

gnat/clients/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
from gnat.connectors.xsoar.client import XSOARClient
110110
from gnat.connectors.yeti.client import YetiClient
111111
from gnat.connectors.zeek.client import ZeekClient
112+
from gnat.connectors.gnat_remote.connector import GNATRemoteConnector
112113
from gnat.connectors.zerofox.client import ZeroFoxClient
113114

114115
CLIENT_REGISTRY: dict = {
@@ -212,6 +213,8 @@
212213
# OSINT feed connectors
213214
"osint_feed": OsintFeedConnector,
214215
"cisco_umbrella": CiscoUmbrellaClient,
216+
# Federation
217+
"gnat_remote": GNATRemoteConnector,
215218
}
216219

217220
__all__ = [
@@ -268,4 +271,5 @@
268271
"ShodanClient",
269272
"OsintFeedConnector",
270273
"CiscoUmbrellaClient",
274+
"GNATRemoteConnector",
271275
]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright 2026 Bill Halpin
3+
"""gnat.connectors.gnat_remote — Remote GNAT instance connector (TAXII 2.1)."""
4+
5+
from gnat.connectors.gnat_remote.connector import GNATRemoteConnector
6+
7+
__all__ = ["GNATRemoteConnector"]

0 commit comments

Comments
 (0)