vanguards_rs/api.rs
1//! High-level API for vanguards-rs.
2//!
3//! This module provides the [`Vanguards`] struct, which is the main entry point
4//! for programmatic use of the vanguards-rs library.
5//!
6//! # Overview
7//!
8//! The [`Vanguards`] struct combines all protection components into a single
9//! interface that can be used to protect Tor hidden services. It manages:
10//!
11//! - Connection to Tor's control port
12//! - Vanguard state persistence and rotation
13//! - All protection component lifecycles
14//! - Event processing and response
15//!
16//! # Conceptual Role
17//!
18//! The `Vanguards` struct sits at the top of the vanguards-rs architecture,
19//! orchestrating all protection components.
20//!
21//! # Thread Safety
22//!
23//! The [`Vanguards`] struct is `Send` but not `Sync`.
24//! For concurrent access from multiple tasks, wrap in `Arc<Mutex<Vanguards>>`:
25//!
26//! ```rust,no_run
27//! use std::sync::Arc;
28//! use tokio::sync::Mutex;
29//! use vanguards_rs::{Config, Vanguards};
30//!
31//! # async fn example() -> vanguards_rs::Result<()> {
32//! let config = Config::default();
33//! let vanguards = Vanguards::from_config(config).await?;
34//! let shared = Arc::new(Mutex::new(vanguards));
35//!
36//! // Clone Arc for each task
37//! let v1 = shared.clone();
38//! tokio::spawn(async move {
39//! let guard = v1.lock().await;
40//! println!("State: {}", guard.state().layer2_guardset());
41//! });
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! # Example
47//!
48//! ## Basic Usage
49//!
50//! ```rust,no_run
51//! use vanguards_rs::{Config, Vanguards};
52//!
53//! #[tokio::main]
54//! async fn main() -> vanguards_rs::Result<()> {
55//! let config = Config::default();
56//! let mut vanguards = Vanguards::from_config(config).await?;
57//! vanguards.run().await
58//! }
59//! ```
60//!
61//! ## With Custom Configuration
62//!
63//! ```rust,no_run
64//! use vanguards_rs::{Config, Vanguards, LogLevel};
65//! use std::path::PathBuf;
66//!
67//! #[tokio::main]
68//! async fn main() -> vanguards_rs::Result<()> {
69//! let mut config = Config::default();
70//! config.control_port = Some(9051);
71//! config.state_file = PathBuf::from("/var/lib/tor/vanguards.state");
72//! config.loglevel = LogLevel::Debug;
73//!
74//! let mut vanguards = Vanguards::from_config(config).await?;
75//! vanguards.run().await
76//! }
77//! ```
78//!
79//! ## Using an Existing Controller
80//!
81//! ```rust,no_run
82//! use vanguards_rs::{Config, Vanguards};
83//! use stem_rs::controller::Controller;
84//!
85//! #[tokio::main]
86//! async fn main() -> vanguards_rs::Result<()> {
87//! // Connect and authenticate manually
88//! let mut controller = Controller::from_port("127.0.0.1:9051".parse().unwrap()).await?;
89//! controller.authenticate(None).await?;
90//!
91//! // Create Vanguards with existing controller
92//! let config = Config::default();
93//! let vanguards = Vanguards::new(controller, config)?;
94//!
95//! // Access state without running the loop
96//! println!("Layer2 guards: {}", vanguards.state().layer2_guardset());
97//! Ok(())
98//! }
99//! ```
100//!
101//! # Security
102//!
103//! - Passwords are cleared from memory after authentication using [`zeroize`]
104//! - State files are written with 0600 permissions on Unix
105//! - All inputs are validated before use
106//! - The [`SecurePassword`] wrapper ensures passwords don't leak in debug output
107//!
108//! # See Also
109//!
110//! - [`Config`] - Configuration options
111//! - [`VanguardState`] - Guard state management
112//! - [`control::run_main`] - Main event loop
113//! - [Python vanguards](https://github.com/mikeperry-tor/vanguards) - Original implementation
114
115use stem_rs::controller::Controller;
116use zeroize::Zeroize;
117
118use crate::config::Config;
119use crate::control::{self, AppState};
120use crate::error::Result;
121use crate::logger::plog;
122use crate::vanguards::VanguardState;
123use crate::LogLevel;
124
125/// A wrapper for sensitive password data that clears itself on drop.
126///
127/// `SecurePassword` provides a secure container for password strings that
128/// automatically clears the password from memory when the wrapper is dropped.
129/// This prevents passwords from lingering in memory where they could be
130/// extracted by memory inspection attacks.
131///
132/// # Security Properties
133///
134/// - **Zeroization**: Password bytes are overwritten with zeros on drop
135/// - **Debug Safety**: Debug output shows `[REDACTED]` instead of the password
136/// - **Clone Safety**: Cloning creates a new secure copy (both are zeroized independently)
137///
138/// # Thread Safety
139///
140/// `SecurePassword` is `Send` and can be moved between threads. It is also
141/// `Clone`, creating independent copies that are each zeroized on drop.
142///
143/// # Example
144///
145/// ```rust
146/// use vanguards_rs::SecurePassword;
147///
148/// // Create a secure password
149/// let password = SecurePassword::new("my_secret_password".to_string());
150///
151/// // Access the password when needed
152/// assert_eq!(password.as_str(), "my_secret_password");
153///
154/// // Debug output is safe
155/// let debug = format!("{:?}", password);
156/// assert!(debug.contains("REDACTED"));
157/// assert!(!debug.contains("my_secret"));
158///
159/// // Password is automatically cleared when dropped
160/// drop(password);
161/// ```
162///
163/// # See Also
164///
165/// - [`Vanguards::from_config`] - Uses SecurePassword internally
166/// - [`zeroize`](https://docs.rs/zeroize) - The underlying zeroization library
167#[derive(Clone)]
168pub struct SecurePassword(String);
169
170impl SecurePassword {
171 /// Creates a new secure password wrapper.
172 ///
173 /// # Arguments
174 ///
175 /// * `password` - The password string to wrap securely
176 ///
177 /// # Example
178 ///
179 /// ```rust
180 /// use vanguards_rs::SecurePassword;
181 ///
182 /// let password = SecurePassword::new("secret123".to_string());
183 /// ```
184 pub fn new(password: String) -> Self {
185 Self(password)
186 }
187
188 /// Returns a reference to the password string.
189 ///
190 /// # Security Note
191 ///
192 /// The returned reference is valid only while the `SecurePassword` exists.
193 /// Avoid storing this reference or converting it to an owned `String`,
194 /// as that would defeat the purpose of secure password handling.
195 ///
196 /// # Example
197 ///
198 /// ```rust
199 /// use vanguards_rs::SecurePassword;
200 ///
201 /// let password = SecurePassword::new("secret123".to_string());
202 /// assert_eq!(password.as_str(), "secret123");
203 /// ```
204 pub fn as_str(&self) -> &str {
205 &self.0
206 }
207}
208
209impl Drop for SecurePassword {
210 fn drop(&mut self) {
211 self.0.zeroize();
212 }
213}
214
215impl std::fmt::Debug for SecurePassword {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 write!(f, "SecurePassword([REDACTED])")
218 }
219}
220
221/// Main vanguards manager combining all protection components.
222///
223/// This struct provides a high-level interface for running vanguards protection
224/// on a Tor hidden service. It manages the complete lifecycle of protection,
225/// from initialization through event processing.
226///
227/// # Managed Components
228///
229/// The `Vanguards` struct orchestrates these protection components:
230///
231/// | Component | Purpose | Config Flag |
232/// |-----------|---------|-------------|
233/// | Vanguard State | Layer 2/3 guard selection | `enable_vanguards` |
234/// | Bandguards | Bandwidth attack detection | `enable_bandguards` |
235/// | Rendguard | Rendezvous point monitoring | `enable_rendguard` |
236/// | Logguard | Tor log monitoring | `enable_logguard` |
237/// | CBT Verify | Circuit timeout verification | `enable_cbtverify` |
238/// | Path Verify | Circuit path verification | `enable_pathverify` |
239///
240/// # Lifecycle
241///
242/// ```text
243/// ┌─────────────────┐
244/// │ from_config() │ ◄── Load config, create state
245/// └────────┬────────┘
246/// │
247/// ▼
248/// ┌─────────────────┐
249/// │ run() │ ◄── Connect to Tor, start event loop
250/// └────────┬────────┘
251/// │
252/// ▼
253/// ┌─────────────────┐
254/// │ Event Loop │ ◄── Process events until shutdown
255/// │ (control.rs) │
256/// └────────┬────────┘
257/// │
258/// ▼
259/// ┌─────────────────┐
260/// │ Cleanup │ ◄── Save state, close connection
261/// └─────────────────┘
262/// ```
263///
264/// # Thread Safety
265///
266/// `Vanguards` is `Send` but not `Sync`. For concurrent access:
267///
268/// ```rust,no_run
269/// use std::sync::Arc;
270/// use tokio::sync::Mutex;
271/// use vanguards_rs::{Config, Vanguards};
272///
273/// # async fn example() -> vanguards_rs::Result<()> {
274/// let vanguards = Vanguards::from_config(Config::default()).await?;
275/// let shared = Arc::new(Mutex::new(vanguards));
276/// # Ok(())
277/// # }
278/// ```
279///
280/// # Example
281///
282/// ## Basic Usage
283///
284/// ```rust,no_run
285/// use vanguards_rs::{Config, Vanguards};
286///
287/// #[tokio::main]
288/// async fn main() -> vanguards_rs::Result<()> {
289/// // Create with default configuration
290/// let config = Config::default();
291/// let mut vanguards = Vanguards::from_config(config).await?;
292///
293/// // Run the protection loop
294/// vanguards.run().await
295/// }
296/// ```
297///
298/// ## Inspecting State
299///
300/// ```rust,no_run
301/// use vanguards_rs::{Config, Vanguards};
302///
303/// #[tokio::main]
304/// async fn main() -> vanguards_rs::Result<()> {
305/// let config = Config::default();
306/// let vanguards = Vanguards::from_config(config).await?;
307///
308/// // Access current state
309/// let state = vanguards.state();
310/// println!("Layer2 guards: {}", state.layer2_guardset());
311/// println!("Layer3 guards: {}", state.layer3_guardset());
312///
313/// // Access configuration
314/// let config = vanguards.config();
315/// println!("Vanguards enabled: {}", config.enable_vanguards);
316/// Ok(())
317/// }
318/// ```
319///
320/// # Security Considerations
321///
322/// - Passwords are cleared from memory after authentication
323/// - State files are written with restrictive permissions (0600 on Unix)
324/// - All external inputs are validated before use
325/// - Guard selections persist across restarts to prevent discovery attacks
326///
327/// # See Also
328///
329/// - [`Config`] - Configuration options
330/// - [`VanguardState`] - Guard state details
331/// - [`SecurePassword`] - Secure password handling
332/// - [`control::run_main`] - Event loop implementation
333pub struct Vanguards {
334 /// Application state containing all protection components.
335 state: AppState,
336 /// Secure password wrapper (cleared on drop).
337 _password: Option<SecurePassword>,
338}
339
340impl Vanguards {
341 /// Creates a new Vanguards instance from an existing controller and configuration.
342 ///
343 /// This method is useful when you already have a connected and authenticated
344 /// controller.
345 ///
346 /// # Arguments
347 ///
348 /// * `controller` - An authenticated Tor controller
349 /// * `config` - The vanguards configuration
350 ///
351 /// # Errors
352 ///
353 /// Returns an error if state loading fails.
354 ///
355 /// # Example
356 ///
357 /// ```rust,no_run
358 /// use vanguards_rs::{Config, Vanguards};
359 /// use stem_rs::controller::Controller;
360 ///
361 /// #[tokio::main]
362 /// async fn main() -> vanguards_rs::Result<()> {
363 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse().unwrap()).await?;
364 /// controller.authenticate(None).await?;
365 ///
366 /// let config = Config::default();
367 /// let vanguards = Vanguards::new(controller, config)?;
368 /// Ok(())
369 /// }
370 /// ```
371 pub fn new(_controller: Controller, config: Config) -> Result<Self> {
372 let state_path = &config.state_file;
373 let vanguard_state = match VanguardState::read_from_file(state_path) {
374 Ok(mut state) => {
375 plog(
376 LogLevel::Info,
377 &format!(
378 "Loaded state with {} layer2 and {} layer3 guards",
379 state.layer2.len(),
380 state.layer3.len()
381 ),
382 );
383 state.enable_vanguards = config.enable_vanguards;
384 state
385 }
386 Err(_) => {
387 plog(
388 LogLevel::Notice,
389 &format!("Creating new vanguard state at: {}", state_path.display()),
390 );
391 let mut state = VanguardState::new(&state_path.to_string_lossy());
392 state.enable_vanguards = config.enable_vanguards;
393 state
394 }
395 };
396
397 let app_state = AppState::new(vanguard_state, config);
398
399 Ok(Self {
400 state: app_state,
401 _password: None,
402 })
403 }
404
405 /// Creates a new Vanguards instance by connecting to Tor.
406 ///
407 /// This method handles connection, authentication, and state initialization.
408 /// The password (if provided) is securely cleared from memory after use.
409 ///
410 /// # Arguments
411 ///
412 /// * `config` - The vanguards configuration
413 ///
414 /// # Errors
415 ///
416 /// Returns an error if:
417 /// - Connection to Tor fails
418 /// - Authentication fails
419 /// - State loading fails
420 ///
421 /// # Example
422 ///
423 /// ```rust,no_run
424 /// use vanguards_rs::{Config, Vanguards};
425 ///
426 /// #[tokio::main]
427 /// async fn main() -> vanguards_rs::Result<()> {
428 /// let config = Config::default();
429 /// let vanguards = Vanguards::from_config(config).await?;
430 /// Ok(())
431 /// }
432 /// ```
433 pub async fn from_config(config: Config) -> Result<Self> {
434 // Wrap password in secure container
435 let secure_password = config.control_pass.clone().map(SecurePassword::new);
436
437 let state_path = &config.state_file;
438 let vanguard_state = match VanguardState::read_from_file(state_path) {
439 Ok(mut state) => {
440 plog(
441 LogLevel::Info,
442 &format!(
443 "Loaded state with {} layer2 and {} layer3 guards",
444 state.layer2.len(),
445 state.layer3.len()
446 ),
447 );
448 state.enable_vanguards = config.enable_vanguards;
449 state
450 }
451 Err(_) => {
452 plog(
453 LogLevel::Notice,
454 &format!("Creating new vanguard state at: {}", state_path.display()),
455 );
456 let mut state = VanguardState::new(&state_path.to_string_lossy());
457 state.enable_vanguards = config.enable_vanguards;
458 state
459 }
460 };
461
462 let app_state = AppState::new(vanguard_state, config);
463
464 Ok(Self {
465 state: app_state,
466 _password: secure_password,
467 })
468 }
469
470 /// Runs the main vanguards protection loop.
471 ///
472 /// This method connects to Tor, authenticates, initializes protection
473 /// components, and processes events until the connection is closed or
474 /// an error occurs.
475 ///
476 /// # Errors
477 ///
478 /// Returns an error if the protection loop fails.
479 ///
480 /// # Example
481 ///
482 /// ```rust,no_run
483 /// use vanguards_rs::{Config, Vanguards};
484 ///
485 /// #[tokio::main]
486 /// async fn main() -> vanguards_rs::Result<()> {
487 /// let config = Config::default();
488 /// let mut vanguards = Vanguards::from_config(config).await?;
489 /// vanguards.run().await
490 /// }
491 /// ```
492 pub async fn run(&mut self) -> Result<()> {
493 control::run_main(self.state.config.clone()).await
494 }
495
496 /// Returns a reference to the current vanguard state.
497 ///
498 /// # Example
499 ///
500 /// ```rust,no_run
501 /// use vanguards_rs::{Config, Vanguards};
502 ///
503 /// #[tokio::main]
504 /// async fn main() -> vanguards_rs::Result<()> {
505 /// let config = Config::default();
506 /// let vanguards = Vanguards::from_config(config).await?;
507 ///
508 /// let state = vanguards.state();
509 /// println!("Layer2 guards: {}", state.layer2_guardset());
510 /// println!("Layer3 guards: {}", state.layer3_guardset());
511 /// Ok(())
512 /// }
513 /// ```
514 pub fn state(&self) -> &VanguardState {
515 &self.state.vanguard_state
516 }
517
518 /// Returns a reference to the current configuration.
519 ///
520 /// # Example
521 ///
522 /// ```rust,no_run
523 /// use vanguards_rs::{Config, Vanguards};
524 ///
525 /// #[tokio::main]
526 /// async fn main() -> vanguards_rs::Result<()> {
527 /// let config = Config::default();
528 /// let vanguards = Vanguards::from_config(config).await?;
529 ///
530 /// let config = vanguards.config();
531 /// println!("Vanguards enabled: {}", config.enable_vanguards);
532 /// Ok(())
533 /// }
534 /// ```
535 pub fn config(&self) -> &Config {
536 &self.state.config
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn test_secure_password_debug_redacted() {
546 let password = SecurePassword::new("secret123".to_string());
547 let debug_str = format!("{:?}", password);
548 assert!(!debug_str.contains("secret123"));
549 assert!(debug_str.contains("REDACTED"));
550 }
551
552 #[test]
553 fn test_secure_password_as_str() {
554 let password = SecurePassword::new("secret123".to_string());
555 assert_eq!(password.as_str(), "secret123");
556 }
557}