siem_connector/lib.rs
1//! Shared connector runtime for SIEM Studio apps.
2//!
3//! This crate extracts the ~1500 lines of duplicated infrastructure from each
4//! SIEM connector binary (Devo Studio, Splunk Studio, etc.) into a single
5//! reusable framework. A new connector binary shrinks to ~50-80 lines:
6//!
7//! 1. Load env config, create your provider, call `probe()`.
8//! 2. Implement [`SiemConnectorApp`] (app name, icon, Dioxus entry point, provider).
9//! 3. Call [`run`]`(app).await`.
10//!
11//! # What this crate handles
12//!
13//! | Module | What it does |
14//! |--------|------|
15//! | [`ipc`] | Unix socket / named pipe transport |
16//! | [`session`] | JWT token + display name storage |
17//! | [`token_refresh`] | Background JWT refresh loop |
18//! | [`ott_auth`] | RSA keypair, Keycloak OAuth, credential persistence |
19//! | [`ws_proxy`] | Bidirectional WS relay (Matrix <-> Dioxus) |
20//! | [`dioxus_server`] | LiveView IPC server + HTML rewriting |
21//! | [`capability`] | Generic siem.* dispatch using `dyn SiemProvider` |
22//! | [`registration`] | Registration message building |
23//! | [`run`] | Orchestrates everything |
24//!
25//! # Environment variables
26//!
27//! | Variable | Required | Description |
28//! |---|---|---|
29//! | `STRIKE48_URL` / `STRIKE48_HOST` | Yes | Matrix gRPC endpoint |
30//! | `TENANT_ID` | Yes | Tenant identifier |
31//! | `INSTANCE_ID` | No | Unique instance ID (auto-generated if unset) |
32//! | `CONNECTOR_NAME` | No | Override the default connector type |
33//! | `STRIKEHUB_SOCKET` | No | Override the IPC socket path (Unix only) |
34//! | `STRIKE48_API_URL` | No | Matrix API base URL (for OTT registration + token refresh) |
35//! | `MATRIX_KEYS_DIR` | No | Override keypair storage (default: `~/.matrix/keys/`) |
36//! | `MATRIX_TLS_INSECURE` | No | Set to `"true"` to skip TLS verification |
37//!
38//! # Adding a new SIEM connector
39//!
40//! ```rust,ignore
41//! struct MyApp { provider: Option<Arc<dyn SiemProvider>> }
42//!
43//! impl SiemConnectorApp for MyApp {
44//! fn app_name(&self) -> &str { "My SIEM" }
45//! fn app_icon(&self) -> &str { "hero-shield-check" }
46//! fn default_connector_type(&self) -> &str { "app-my-siem" }
47//! fn nav_order(&self) -> u32 { 32 }
48//! fn dioxus_app(&self) -> fn() -> dioxus::prelude::Element { MyDioxusApp }
49//! fn ipc_prefix(&self) -> &str { "my-siem-connector" }
50//! fn provider(&self) -> Option<Arc<dyn SiemProvider>> { self.provider.clone() }
51//! }
52//!
53//! #[tokio::main]
54//! async fn main() -> anyhow::Result<()> {
55//! let app = MyApp { provider: /* ... */ };
56//! siem_connector::run(app).await
57//! }
58//! ```
59
60pub mod capability;
61pub mod construct_proxy;
62pub mod dioxus_server;
63pub mod ipc;
64pub mod message_loop;
65pub mod ott_auth;
66pub mod registration;
67pub mod session;
68pub mod token_refresh;
69pub mod ws_proxy;
70
71use std::sync::Arc;
72
73/// What each SIEM connector provides to the shared framework.
74///
75/// Implement this trait in your connector binary and pass it to [`run`].
76/// All the boilerplate (IPC transport, OTT auth, WS proxying, capability
77/// dispatch, heartbeat, reconnection) is handled by the framework.
78pub trait SiemConnectorApp: Send + Sync + 'static {
79 /// Human-readable application name shown in the Matrix sidebar.
80 ///
81 /// Examples: `"Devo Studio"`, `"Splunk Studio"`.
82 fn app_name(&self) -> &str;
83
84 /// Hero icon identifier for the sidebar entry.
85 ///
86 /// Examples: `"hero-shield-exclamation"`, `"hero-shield-check"`.
87 fn app_icon(&self) -> &str;
88
89 /// Connector type registered with Matrix (used in `RegisterConnectorRequest`).
90 ///
91 /// Can be overridden at runtime by the `CONNECTOR_NAME` env var.
92 ///
93 /// Examples: `"app-devo-studio"`, `"app-splunk-studio"`.
94 fn default_connector_type(&self) -> &str;
95
96 /// Sidebar ordering weight (lower = higher in the list).
97 fn nav_order(&self) -> u32;
98
99 /// Zero-capture Dioxus app entry point (`fn() -> Element`).
100 ///
101 /// This function must read its `DataContext` from a global/static, since
102 /// Dioxus LiveView requires a bare function pointer.
103 fn dioxus_app(&self) -> fn() -> dioxus::prelude::Element;
104
105 /// Prefix used for IPC socket/pipe names.
106 ///
107 /// The socket will be `/tmp/{prefix}-{pid}.sock` on Unix or
108 /// `\\.\pipe\{prefix}-{pid}` on Windows.
109 ///
110 /// Examples: `"devo-connector"`, `"splunk-connector"`.
111 fn ipc_prefix(&self) -> &str;
112
113 /// The SIEM provider for handling `siem.*` capability requests.
114 ///
115 /// Return `None` if no provider credentials were configured (the UI
116 /// will still load, but API calls will return errors).
117 fn provider(&self) -> Option<Arc<dyn siem_core::SiemProvider>>;
118
119 /// Optional [`ConstructBridge`] for routing data requests through Matrix → Construct.
120 ///
121 /// When provided, the framework will bind the bridge to the gRPC stream
122 /// after connection is established. This enables the proxy provider to
123 /// send `construct://` invocations upstream.
124 fn construct_bridge(&self) -> Option<construct_proxy::ConstructBridge> {
125 None
126 }
127}
128
129/// Run the connector.
130///
131/// This is the main entry point that orchestrates the full lifecycle:
132/// 1. Start the Dioxus LiveView server on an IPC socket.
133/// 2. Connect to Matrix via gRPC.
134/// 3. Register as an APP+TOOL connector.
135/// 4. Run the message loop (HTTP proxy, WS relay, capability dispatch, heartbeat).
136/// 5. Handle OTT auth, token refresh, and reconnection.
137///
138/// This function blocks until shutdown (Ctrl+C) or an unrecoverable error.
139pub async fn run(app: impl SiemConnectorApp) -> anyhow::Result<()> {
140 crate::ws_proxy::run_connector(app).await
141}