Skip to main content

winrm_rs/
config.rs

1// Configuration types for the winrm-rs crate.
2//
3// Contains WinrmConfig, AuthMethod, and WinrmCredentials extracted from client.rs.
4
5use secrecy::SecretString;
6
7/// Configuration for a [`WinrmClient`](crate::WinrmClient) connection.
8///
9/// Controls the transport (HTTP vs HTTPS), timeouts, and authentication
10/// method. Use [`Default::default()`] for sensible defaults (HTTP on port
11/// 5985, NTLM auth, 30 s connect / 60 s operation timeouts).
12#[derive(Debug, Clone)]
13pub struct WinrmConfig {
14    /// TCP port of the WinRM listener (default: 5985 for HTTP, 5986 for HTTPS).
15    pub port: u16,
16    /// Whether to connect over HTTPS. When `true`, the endpoint URL uses `https://`.
17    pub use_tls: bool,
18    /// Accept invalid or self-signed TLS certificates.
19    ///
20    /// # Security
21    ///
22    /// Setting this to `true` disables chain verification on **both** the
23    /// regular HTTPS transport and the CredSSP outer TLS leg, leaving the
24    /// connection vulnerable to MITM. Credentials transmitted over Basic,
25    /// NTLM, or CredSSP can then be intercepted.
26    ///
27    /// Use **only** in test environments. The constructor logs a
28    /// `tracing::warn` when this flag is enabled.
29    pub accept_invalid_certs: bool,
30    /// TCP connect timeout in seconds (default: 30).
31    pub connect_timeout_secs: u64,
32    /// WS-Management `OperationTimeout` in seconds (default: 60). The HTTP
33    /// client timeout is set to this value plus 10 seconds to allow the server
34    /// to respond before the transport gives up.
35    pub operation_timeout_secs: u64,
36    /// Authentication method to use for all requests (default: [`AuthMethod::Ntlm`]).
37    pub auth_method: AuthMethod,
38    /// Maximum SOAP envelope size in bytes (default: 153600).
39    ///
40    /// Controls the `MaxEnvelopeSize` header sent in every WS-Management request.
41    /// Increase this for hosts that return large responses.
42    pub max_envelope_size: u32,
43    /// Maximum number of retries for transient HTTP errors (default: 0 = no retry).
44    ///
45    /// Uses exponential backoff starting at 100 ms (100, 200, 400, ...).
46    /// Only `WinrmError::Http` errors trigger a retry; authentication and
47    /// SOAP faults are returned immediately.
48    pub max_retries: u32,
49    /// Path to client certificate PEM file (for `AuthMethod::Certificate`).
50    pub client_cert_pem: Option<String>,
51    /// Path to client private key PEM file (for `AuthMethod::Certificate`).
52    pub client_key_pem: Option<String>,
53    /// HTTP proxy URL (e.g. `"http://proxy:8080"`).
54    ///
55    /// When set, all WinRM HTTP(S) requests are routed through this proxy.
56    pub proxy: Option<String>,
57    /// Console output code page (default: 65001 = UTF-8).
58    ///
59    /// Controls the `WINRS_CODEPAGE` option in the shell creation envelope.
60    /// Common values: 65001 (UTF-8), 437 (US), 850 (Western European).
61    pub codepage: u32,
62    /// Initial working directory for the remote shell (default: `None`).
63    ///
64    /// When set, the shell starts in this directory. Equivalent to running
65    /// `cd <path>` before any command.
66    pub working_directory: Option<String>,
67    /// Environment variables to set in the remote shell (default: empty).
68    ///
69    /// Each `(key, value)` pair is injected into the shell's environment
70    /// at creation time via `<rsp:Environment>`.
71    pub env_vars: Vec<(String, String)>,
72    /// Message encryption mode for NTLM (default: [`EncryptionMode::Auto`]).
73    ///
74    /// Controls whether NTLM sealing is applied to SOAP message bodies.
75    pub encryption: EncryptionMode,
76    /// Custom HTTP `User-Agent` header (default: `None` = `winrm-rs/<version>`).
77    pub user_agent: Option<String>,
78    /// Shell idle timeout in seconds (default: `None` = server default).
79    ///
80    /// When set, the shell will be automatically closed by the server
81    /// after this many seconds of inactivity.
82    pub idle_timeout_secs: Option<u64>,
83    /// Maximum cumulative bytes accepted from the server for a single command's
84    /// stdout+stderr (default: `Some(64 * 1024 * 1024)` = 64 MiB).
85    ///
86    /// Prevents a malicious or runaway server from exhausting client memory
87    /// by streaming an unbounded base64 chunk sequence. When the cap is
88    /// reached, the operation returns [`crate::error::WinrmError::Transfer`]
89    /// and the shell is best-effort closed. Set to `None` to disable the
90    /// cap (pre-1.1 behaviour).
91    pub max_output_bytes: Option<usize>,
92}
93
94impl Default for WinrmConfig {
95    fn default() -> Self {
96        Self {
97            port: 5985,
98            use_tls: false,
99            accept_invalid_certs: false,
100            connect_timeout_secs: 30,
101            operation_timeout_secs: 60,
102            auth_method: AuthMethod::Ntlm,
103            max_envelope_size: 153_600,
104            max_retries: 0,
105            client_cert_pem: None,
106            client_key_pem: None,
107            proxy: None,
108            encryption: EncryptionMode::Auto,
109            user_agent: None,
110            codepage: 65001,
111            working_directory: None,
112            env_vars: Vec::new(),
113            idle_timeout_secs: None,
114            max_output_bytes: Some(64 * 1024 * 1024),
115        }
116    }
117}
118
119/// Controls whether NTLM message encryption (sealing) is applied to SOAP bodies.
120///
121/// When using HTTP (not HTTPS), NTLM sealing encrypts the SOAP body to prevent
122/// eavesdropping. When using HTTPS, the TLS layer provides encryption, making
123/// sealing redundant.
124#[derive(Debug, Clone, Default, PartialEq)]
125pub enum EncryptionMode {
126    /// Encrypt when using HTTP, skip when using HTTPS (default).
127    #[default]
128    Auto,
129    /// Always encrypt SOAP bodies, even over HTTPS.
130    Always,
131    /// Never encrypt SOAP bodies. **Use only for debugging.**
132    Never,
133}
134
135/// Authentication method for the WinRM HTTP transport.
136#[derive(Debug, Clone)]
137pub enum AuthMethod {
138    /// HTTP Basic authentication. Credentials are sent as a base64-encoded
139    /// `Authorization` header on every request. Only safe over HTTPS.
140    Basic,
141    /// NTLMv2 challenge/response authentication (default). Performs a three-step
142    /// handshake (negotiate, challenge, authenticate) per request.
143    Ntlm,
144    /// Kerberos authentication (requires `kerberos` feature and prior `kinit`).
145    ///
146    /// Uses SPNEGO Negotiate tokens via the system Kerberos library.
147    /// The service principal is derived as `HTTP/<hostname>`.
148    Kerberos,
149    /// TLS client certificate authentication.
150    ///
151    /// The `reqwest::Client` is configured with the client cert at construction
152    /// time. No `Authorization` header is sent; the TLS handshake authenticates.
153    Certificate,
154    /// CredSSP authentication for double-hop credential delegation.
155    ///
156    /// Wraps NTLM or Kerberos inside a TLS channel, allowing credentials
157    /// to be delegated to the remote host for accessing network resources.
158    /// Requires HTTPS (`use_tls = true`).
159    ///
160    /// **Not yet implemented** — will return an error at runtime.
161    CredSsp,
162}
163
164/// Credentials for WinRM authentication.
165///
166/// The password is stored as a [`SecretString`] — zeroized on drop and
167/// redacted in `Debug` output. Use [`WinrmCredentials::new`] to construct.
168///
169/// For NTLM, if [`domain`](Self::domain) is empty the client will use the
170/// domain advertised by the server in its Type 2 challenge message.
171#[derive(Clone)]
172pub struct WinrmCredentials {
173    /// Windows account name (e.g. `"administrator"`).
174    pub username: String,
175    /// Password — stored as SecretString, zeroized on drop, redacted in Debug.
176    pub password: SecretString,
177    /// Optional NetBIOS domain. Leave empty to auto-detect from the NTLM
178    /// challenge.
179    pub domain: String,
180}
181
182impl WinrmCredentials {
183    /// Create new credentials.
184    ///
185    /// Wraps the password in a [`SecretString`] for automatic zeroization.
186    pub fn new(
187        username: impl Into<String>,
188        password: impl Into<String>,
189        domain: impl Into<String>,
190    ) -> Self {
191        Self {
192            username: username.into(),
193            password: SecretString::from(password.into()),
194            domain: domain.into(),
195        }
196    }
197}
198
199impl std::fmt::Debug for WinrmCredentials {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        f.debug_struct("WinrmCredentials")
202            .field("username", &self.username)
203            .field("password", &"[REDACTED]")
204            .field("domain", &self.domain)
205            .finish()
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use secrecy::ExposeSecret;
213
214    #[test]
215    fn default_config_uses_http_and_ntlm() {
216        let config = WinrmConfig::default();
217        assert_eq!(config.port, 5985);
218        assert!(!config.use_tls);
219        assert!(matches!(config.auth_method, AuthMethod::Ntlm));
220    }
221
222    #[test]
223    fn default_config_max_envelope_size() {
224        let config = WinrmConfig::default();
225        assert_eq!(config.max_envelope_size, 153_600);
226    }
227
228    #[test]
229    fn custom_max_envelope_size() {
230        let config = WinrmConfig {
231            max_envelope_size: 512_000,
232            ..Default::default()
233        };
234        assert_eq!(config.max_envelope_size, 512_000);
235    }
236
237    #[test]
238    fn default_config_max_retries_is_zero() {
239        let config = WinrmConfig::default();
240        assert_eq!(config.max_retries, 0);
241    }
242
243    #[test]
244    fn default_config_has_no_proxy() {
245        let config = WinrmConfig::default();
246        assert!(config.proxy.is_none());
247    }
248
249    #[test]
250    fn default_config_has_no_cert_paths() {
251        let config = WinrmConfig::default();
252        assert!(config.client_cert_pem.is_none());
253        assert!(config.client_key_pem.is_none());
254    }
255
256    #[test]
257    fn credentials_new_constructor() {
258        let creds = WinrmCredentials::new("admin", "s3cret", "DOMAIN");
259        assert_eq!(creds.username, "admin");
260        assert_eq!(creds.password.expose_secret(), "s3cret");
261        assert_eq!(creds.domain, "DOMAIN");
262    }
263
264    #[test]
265    fn credentials_debug_redacts_password() {
266        let creds = WinrmCredentials::new("admin", "super-secret-password", "DOM");
267        let debug_output = format!("{creds:?}");
268        assert!(debug_output.contains("admin"));
269        assert!(debug_output.contains("DOM"));
270        assert!(debug_output.contains("[REDACTED]"));
271        assert!(!debug_output.contains("super-secret-password"));
272    }
273
274    #[test]
275    fn credentials_expose_secret_works() {
276        let creds = WinrmCredentials::new("user", "my-password", "");
277        let exposed: &str = creds.password.expose_secret();
278        assert_eq!(exposed, "my-password");
279    }
280
281    #[test]
282    fn auth_method_kerberos_variant_exists() {
283        let method = AuthMethod::Kerberos;
284        let debug = format!("{method:?}");
285        assert!(debug.contains("Kerberos"));
286    }
287
288    #[test]
289    fn auth_method_certificate_variant_exists() {
290        let method = AuthMethod::Certificate;
291        let debug = format!("{method:?}");
292        assert!(debug.contains("Certificate"));
293    }
294}