Skip to content

Commit d615019

Browse files
bb-connorclaude
andauthored
refactor(agent): split api_server.rs into api/{routes,middleware,proxy,...} (#349)
api_server.rs was 6366 lines mixing types, server bootstrap, 110 axum route registrations, handler bodies, daemon-proxy helpers, UI bootstrap HTML, fleet hunt outbox sync, and ledger constructors. Split into a thin re-export hub plus 38 new submodule files; the existing sibling submodules (control_sync, receipts, response_actions, observations, policy_history, policy_delta, evidence_archives) are untouched and remain owned by P2-B/C/H. No public API or behavior changes. api_server.rs: 6366 -> 202 lines. Every new production file is under 600 lines. The only files in src/api_server/ over 600 lines are the seven owned by parallel P2 splits. Co-authored-by: bb-connor <bb-connor@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 443f8bd commit d615019

39 files changed

Lines changed: 6748 additions & 6232 deletions

apps/agent/src-tauri/src/api_server.rs

Lines changed: 68 additions & 6232 deletions
Large diffs are not rendered by default.
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
//! Default file paths and on-disk ledger constructors for the EDR pipeline.
2+
//!
3+
//! These functions return `PathBuf`s rooted in the agent config directory and
4+
//! open the corresponding durable JSONL ledgers. They are called by
5+
//! `AgentApiServer::try_new` when seeding `AgentApiState`.
6+
7+
use super::*;
8+
use anyhow::{Context, Result};
9+
use hush_core::{Keypair, SignedReceipt};
10+
use std::path::PathBuf;
11+
12+
use crate::settings::Settings;
13+
14+
pub(crate) fn default_edr_flight_recorder_path() -> PathBuf {
15+
crate::settings::get_config_dir()
16+
.join("edr")
17+
.join("flight-recorder.jsonl")
18+
}
19+
20+
pub(crate) fn default_edr_flight_recorder(
21+
) -> Result<clawdstrike_policy_event::edr::EndpointFlightRecorder> {
22+
let path = default_edr_flight_recorder_path();
23+
clawdstrike_policy_event::edr::EndpointFlightRecorder::open(&path)
24+
.with_context(|| format!("open durable endpoint flight recorder {}", path.display()))
25+
}
26+
27+
pub(crate) fn default_edr_receipt_ledger_path() -> PathBuf {
28+
crate::settings::get_config_dir()
29+
.join("edr")
30+
.join("decision-receipts.jsonl")
31+
}
32+
33+
pub(crate) fn default_edr_honey_registry_path() -> PathBuf {
34+
crate::settings::get_config_dir()
35+
.join("edr")
36+
.join("honey-artifacts.jsonl")
37+
}
38+
39+
pub(crate) fn default_edr_evidence_bundle_dir() -> PathBuf {
40+
crate::settings::get_config_dir()
41+
.join("edr")
42+
.join("evidence-bundles")
43+
}
44+
45+
pub(crate) fn default_edr_quarantine_dir() -> PathBuf {
46+
crate::settings::get_config_dir()
47+
.join("edr")
48+
.join("quarantine")
49+
}
50+
51+
pub(crate) fn default_edr_response_execution_ledger_path() -> PathBuf {
52+
crate::settings::get_config_dir()
53+
.join("edr")
54+
.join("response-executions.jsonl")
55+
}
56+
57+
pub(crate) fn default_edr_response_acknowledgement_ledger_path() -> PathBuf {
58+
crate::settings::get_config_dir()
59+
.join("edr")
60+
.join("response-acknowledgements.jsonl")
61+
}
62+
63+
pub(crate) fn default_edr_control_ack_postback_retry_ledger_path() -> PathBuf {
64+
crate::settings::get_config_dir()
65+
.join("edr")
66+
.join("control-ack-postback-retries.json")
67+
}
68+
69+
pub(crate) fn default_edr_control_archive_upload_retry_ledger_path() -> PathBuf {
70+
crate::settings::get_config_dir()
71+
.join("edr")
72+
.join("control-archive-upload-retries.json")
73+
}
74+
75+
pub(crate) fn default_edr_control_receipt_upload_retry_ledger_path() -> PathBuf {
76+
crate::settings::get_config_dir()
77+
.join("edr")
78+
.join("control-receipt-upload-retries.json")
79+
}
80+
81+
pub(crate) fn default_edr_fleet_hunt_event_outbox_path() -> PathBuf {
82+
crate::settings::get_config_dir()
83+
.join("edr")
84+
.join("fleet-hunt-event-outbox.json")
85+
}
86+
87+
pub(crate) fn default_edr_egress_restriction_ledger_path() -> PathBuf {
88+
crate::settings::get_config_dir()
89+
.join("edr")
90+
.join("egress-restrictions.jsonl")
91+
}
92+
93+
pub(crate) fn default_edr_staged_detection_ledger_path() -> PathBuf {
94+
crate::settings::get_config_dir()
95+
.join("edr")
96+
.join("staged-detections.jsonl")
97+
}
98+
99+
pub(crate) fn default_edr_policy_delta_dir() -> PathBuf {
100+
crate::settings::get_config_dir()
101+
.join("edr")
102+
.join("policy-deltas")
103+
}
104+
105+
pub(crate) fn default_edr_network_extension_egress_policy_path() -> PathBuf {
106+
crate::settings::get_config_dir()
107+
.join("edr")
108+
.join("network-extension-egress-policy.json")
109+
}
110+
111+
pub(crate) fn default_edr_receipt_signing_key_path() -> PathBuf {
112+
crate::settings::get_config_dir()
113+
.join("edr")
114+
.join("receipt-signing.key")
115+
}
116+
117+
pub(crate) fn edr_receipt_signer_requires_enrollment(settings: &Settings) -> bool {
118+
settings.enrollment.enrolled || settings.nats.enabled || settings.control_api.enabled
119+
}
120+
121+
pub(crate) async fn require_cloud_mode_enrolled_receipt_signer(
122+
state: &AgentApiState,
123+
label: &str,
124+
) -> Result<(), (axum::http::StatusCode, String)> {
125+
let settings = state.settings.read().await.clone();
126+
if !edr_receipt_signer_requires_enrollment(&settings) {
127+
return Ok(());
128+
}
129+
require_enrolled_edr_receipt_signer_for_settings(state, &settings, label).await
130+
}
131+
132+
pub(crate) async fn require_enrolled_edr_receipt_signer_for_settings(
133+
state: &AgentApiState,
134+
settings: &Settings,
135+
label: &str,
136+
) -> Result<(), (axum::http::StatusCode, String)> {
137+
use axum::http::StatusCode;
138+
if !edr_receipt_signer_requires_enrollment(settings) {
139+
return Ok(());
140+
}
141+
let (signer_identity, signer_public_key) = {
142+
let ledger = state.edr_receipt_ledger.lock().await;
143+
(
144+
ledger.signer_identity.clone(),
145+
ledger.signer_public_key.clone(),
146+
)
147+
};
148+
let expected_identity = format!("agent-enrollment:{signer_public_key}");
149+
if signer_identity != expected_identity {
150+
return Err((
151+
StatusCode::CONFLICT,
152+
format!("{label} requires an enrolled EDR receipt signer in cloud/control mode"),
153+
));
154+
}
155+
if let Some(key_hex) = crate::enrollment::load_enrollment_key_hex().map_err(internal_error)? {
156+
let enrollment_public_key = Keypair::from_hex(key_hex.trim())
157+
.map_err(|err| internal_error(anyhow::anyhow!("parse enrollment key: {err}")))?
158+
.public_key()
159+
.to_hex();
160+
if signer_public_key != enrollment_public_key {
161+
return Err((
162+
StatusCode::CONFLICT,
163+
format!("{label} signer does not match the enrolled agent key"),
164+
));
165+
}
166+
}
167+
Ok(())
168+
}
169+
170+
pub(crate) async fn require_cloud_mode_receipts_signed_by_current_signer(
171+
state: &AgentApiState,
172+
receipts: &[SignedReceipt],
173+
label: &str,
174+
) -> Result<(), (axum::http::StatusCode, String)> {
175+
use axum::http::StatusCode;
176+
let settings = state.settings.read().await.clone();
177+
if !edr_receipt_signer_requires_enrollment(&settings) {
178+
return Ok(());
179+
}
180+
require_enrolled_edr_receipt_signer_for_settings(state, &settings, label).await?;
181+
let (signer_identity, signer_public_key) = {
182+
let ledger = state.edr_receipt_ledger.lock().await;
183+
(
184+
ledger.signer_identity.clone(),
185+
ledger.signer_public_key.clone(),
186+
)
187+
};
188+
for receipt in receipts {
189+
let receipt_id = receipt.receipt.receipt_id.as_deref().unwrap_or("<missing>");
190+
if receipt_endpoint_decision_str(receipt, &["signer", "signerIdentity"])
191+
!= Some(signer_identity.as_str())
192+
{
193+
return Err((
194+
StatusCode::CONFLICT,
195+
format!("{label} contains receipt {receipt_id} from a non-current EDR signer"),
196+
));
197+
}
198+
if receipt_endpoint_decision_str(receipt, &["signer", "signerPublicKey"])
199+
!= Some(signer_public_key.as_str())
200+
{
201+
return Err((
202+
StatusCode::CONFLICT,
203+
format!("{label} contains receipt {receipt_id} from an untrusted EDR signer"),
204+
));
205+
}
206+
}
207+
Ok(())
208+
}
209+
210+
pub(crate) fn default_edr_receipt_ledger(
211+
require_enrolled_receipt_signer: bool,
212+
) -> Result<EndpointReceiptLedger> {
213+
let path = default_edr_receipt_ledger_path();
214+
let ledger = if require_enrolled_receipt_signer {
215+
EndpointReceiptLedger::open_require_enrollment(&path)
216+
} else {
217+
EndpointReceiptLedger::open(&path)
218+
};
219+
ledger.with_context(|| format!("open durable endpoint receipt ledger {}", path.display()))
220+
}
221+
222+
pub(crate) fn default_edr_honey_registry() -> Result<EndpointHoneyRegistry> {
223+
let path = default_edr_honey_registry_path();
224+
EndpointHoneyRegistry::open(&path)
225+
.with_context(|| format!("open durable endpoint honey registry {}", path.display()))
226+
}
227+
228+
pub(crate) fn default_edr_evidence_bundle_store() -> Result<EndpointEvidenceBundleStore> {
229+
let root = default_edr_evidence_bundle_dir();
230+
EndpointEvidenceBundleStore::open(&root).with_context(|| {
231+
format!(
232+
"open durable endpoint evidence bundle store {}",
233+
root.display()
234+
)
235+
})
236+
}
237+
238+
pub(crate) fn default_edr_response_execution_ledger() -> Result<EndpointResponseExecutionLedger> {
239+
let path = default_edr_response_execution_ledger_path();
240+
EndpointResponseExecutionLedger::open(&path).with_context(|| {
241+
format!(
242+
"open durable endpoint response execution ledger {}",
243+
path.display()
244+
)
245+
})
246+
}
247+
248+
pub(crate) fn default_edr_response_acknowledgement_ledger(
249+
) -> Result<EndpointResponseAcknowledgementLedger> {
250+
let path = default_edr_response_acknowledgement_ledger_path();
251+
EndpointResponseAcknowledgementLedger::open(&path).with_context(|| {
252+
format!(
253+
"open durable endpoint response acknowledgement ledger {}",
254+
path.display()
255+
)
256+
})
257+
}
258+
259+
pub(crate) fn default_edr_control_ack_postback_retry_ledger(
260+
) -> Result<EndpointControlAckPostbackRetryLedger> {
261+
let path = default_edr_control_ack_postback_retry_ledger_path();
262+
EndpointControlAckPostbackRetryLedger::open(&path).with_context(|| {
263+
format!(
264+
"open durable endpoint Control API acknowledgement retry queue {}",
265+
path.display()
266+
)
267+
})
268+
}
269+
270+
pub(crate) fn default_edr_control_archive_upload_retry_ledger(
271+
) -> Result<EndpointControlArchiveUploadRetryLedger> {
272+
let path = default_edr_control_archive_upload_retry_ledger_path();
273+
EndpointControlArchiveUploadRetryLedger::open(&path).with_context(|| {
274+
format!(
275+
"open durable endpoint Control API archive upload retry queue {}",
276+
path.display()
277+
)
278+
})
279+
}
280+
281+
pub(crate) fn default_edr_control_receipt_upload_retry_ledger(
282+
) -> Result<EndpointControlReceiptUploadRetryLedger> {
283+
let path = default_edr_control_receipt_upload_retry_ledger_path();
284+
EndpointControlReceiptUploadRetryLedger::open(&path).with_context(|| {
285+
format!(
286+
"open durable endpoint Control API receipt upload retry queue {}",
287+
path.display()
288+
)
289+
})
290+
}
291+
292+
pub(crate) fn default_edr_fleet_hunt_event_outbox() -> Result<EndpointFleetHuntEventOutbox> {
293+
let path = default_edr_fleet_hunt_event_outbox_path();
294+
EndpointFleetHuntEventOutbox::open(&path).with_context(|| {
295+
format!(
296+
"open durable endpoint fleet hunt-event outbox {}",
297+
path.display()
298+
)
299+
})
300+
}
301+
302+
pub(crate) fn default_edr_egress_restriction_ledger() -> Result<EndpointEgressRestrictionLedger> {
303+
let path = default_edr_egress_restriction_ledger_path();
304+
EndpointEgressRestrictionLedger::open(&path).with_context(|| {
305+
format!(
306+
"open durable endpoint egress restriction ledger {}",
307+
path.display()
308+
)
309+
})
310+
}
311+
312+
pub(crate) fn default_edr_staged_detection_ledger() -> Result<EndpointStagedDetectionLedger> {
313+
let path = default_edr_staged_detection_ledger_path();
314+
EndpointStagedDetectionLedger::open(&path).with_context(|| {
315+
format!(
316+
"open durable endpoint staged detection ledger {}",
317+
path.display()
318+
)
319+
})
320+
}
321+
322+
pub(crate) fn default_edr_policy_delta_store() -> Result<EndpointPolicyDeltaStore> {
323+
let root = default_edr_policy_delta_dir();
324+
EndpointPolicyDeltaStore::open(&root).with_context(|| {
325+
format!(
326+
"open durable endpoint policy delta store {}",
327+
root.display()
328+
)
329+
})
330+
}

0 commit comments

Comments
 (0)