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}