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}