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