Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ Kerberos:
there is no message protection — the payload travels as plaintext SOAP. The scheme is accepted
over both transports, but it must be used over **HTTPS** in practice, where TLS protects the
credential and the payload (over plain HTTP both travel in the clear).
* A domain-qualified user name (`DOMAIN\user`) keeps its domain prefix on the wire; the server
must have the `Basic` setting enabled on the WinRM service (`winrm/config/service/auth`).
* On Windows, WinRM accepts Basic for **local accounts only**, addressed by their **bare user
name**: a domain account — or even a local account written as `MACHINE\user` — is rejected
with a 401 (verified against a real host). The server must have the `Basic` setting enabled
on the WinRM service (`winrm/config/service/auth`).
* `BASIC` joins `AuthenticationEnum` (legacy API) and participates in the ordered-fallback list
like the other schemes.

Expand Down
10 changes: 6 additions & 4 deletions src/main/java/org/metricshub/winrm/light/BasicAuthScheme.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ final class BasicAuthScheme extends PlaintextSoapAuthScheme {
private final char[] password;

/**
* @param username the account name (a domain-qualified name keeps its domain prefix, which is
* how the server locates the account). The caller passes an already whitespace-stripped
* account (the service rebuilds it from the endpoint's normalized domain/username parts),
* so it is used verbatim here.
* @param username the account name, used verbatim. A qualified name keeps its prefix on the
* wire, but Windows WinRM accepts Basic for bare local account names only and 401s any
* {@code DOMAIN\}/{@code MACHINE\}-qualified form (the qualified form is still sent
* as-is for non-Microsoft WSMan services). The caller passes an already
* whitespace-stripped account (the service rebuilds it from the endpoint's normalized
* domain/username parts).
* @param password the account password, kept as {@code char[]} so the caller owns the single
* wipeable copy of the secret
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,9 @@ private static AuthScheme resolveAuthScheme(
// over both transports. Rebuild the account from the whitespace-normalized
// domain/username parts (the same account NTLM/Kerberos use), domain-qualified when
// the endpoint was given one — the endpoint's raw username is kept verbatim for
// public-API stability and is intentionally NOT used here.
// public-API stability and is intentionally NOT used here. Windows WinRM accepts
// Basic for bare local account names only (a qualified name gets a 401), but the
// qualified form is passed through unchanged for non-Microsoft WSMan services.
final String basicAccount = domain != null ? domain + "\\" + username : username;
schemes.add(new BasicAuthScheme(basicAccount, password));
} else {
Expand Down
23 changes: 17 additions & 6 deletions src/site/markdown/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,19 @@ handshake and no message protection, so the payload travels as plaintext SOAP. I
transports, but over plain HTTP the credential and the data are sent **in the clear**: use Basic
over HTTPS only, where TLS protects both.

The credential is the user name, **with all whitespace removed** before it is sent: a
`DOMAIN\user` account is rebuilt as `DOMAIN` + `\` + the account (the backslash is where the domain
and account are split on), and a bare name is sent as-is. Windows account names contain no
whitespace, so the removal is a no-op in practice — but if you supply one, the header carries the
whitespace-stripped account, not the exact string you typed.
On Windows, WinRM accepts Basic for **local accounts only**, addressed by their **bare user
name**: a domain account is rejected, and so is a *local* account written with a qualifying
prefix — `MACHINE\user` or `DOMAIN\user` gets a `401` even when the password is correct
(verified against a real host). Use `credentials("user", password)`, not
`credentials("MACHINE\\user", password)`. The client itself does not reject a qualified name —
some non-Microsoft WSMan services accept one — and sends the account with all whitespace
removed: a `DOMAIN\user` value is rebuilt as `DOMAIN` + `\` + the account, and a bare name is
sent as-is.

```java
try (WinRMClient client = WinRMClient.builder("server.example.com")
.https()
.credentials("DOMAIN\\Administrator", password)
.credentials("Administrator", password) // Windows: a bare LOCAL account name
.authentication(AuthScheme.BASIC)
.build()) {
...
Expand All @@ -124,6 +127,14 @@ TLS provides the confidentiality. Over plain HTTP — which, as noted, should no
service would additionally have to set `AllowUnencrypted=true` (otherwise it refuses the unprotected
SOAP), which is exactly what the HTTPS recommendation exists to avoid.

Note that most server-side refusals above surface as the same `401`: a Basic authentication
error can mean a wrong password, but also a domain-qualified or domain account, or `Basic`
disabled on the service — check the configuration before suspecting the credential. Unencrypted
HTTP with `AllowUnencrypted=false` is refused too, but not always as a `401`: depending on the
Windows version, the service may authenticate the credential and then reject the unprotected
SOAP with a WSMan fault (a `401` was observed on Server 2008 R2, a fault is reported on later
versions), so that misconfiguration can surface as either an authentication error or a fault.

## Authentication failures

A rejected credential (after every scheme of the fallback list was tried) surfaces as a
Expand Down
2 changes: 2 additions & 0 deletions src/site/markdown/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ requires `--kerberos-kdc`.
`--basic` authenticates with HTTP Basic, sending the credential in the `Authorization` header of
every request. It has no message protection, so the credential and payload are plaintext over HTTP —
combine it with `--https` (and `--https-permissive` for self-signed hosts) so TLS protects them.
On Windows, use a **bare local account name** with `-u`: the WinRM service rejects Basic for
domain accounts and for any `DOMAIN\`-qualified name.

See [Authentication](authentication.html) for how NTLM, Kerberos, and Basic work on the wire.

Expand Down
5 changes: 4 additions & 1 deletion src/site/markdown/migrating-from-winrm4j.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ the switch:
protection: it accepts the scheme over both transports, so **use `https()` with it** — without
TLS, the credential and payload travel in the clear and the client will not stop you. The host
must have the `Basic` setting enabled on the WinRM service and be reachable over HTTPS
([Preparing the Windows Host](preparing-the-host.html)). NTLM remains the recommended scheme.
([Preparing the Windows Host](preparing-the-host.html)), and the credential must be a **bare
local account name** — Windows rejects Basic for domain accounts and for any
`DOMAIN\`/`MACHINE\`-qualified name ([Authentication](authentication.html)). NTLM remains the
recommended scheme.
* **Kerberos requires HTTPS.** winrm4j runs Kerberos over plain HTTP; this client refuses at
`build()`, because it does not implement Kerberos message encryption — without TLS the payload
would travel unprotected. Connect with `https()` and by the FQDN the KDC knows
Expand Down
5 changes: 3 additions & 2 deletions src/site/markdown/preparing-the-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ here:
it refuses the unprotected SOAP — see [Authentication](authentication.html).)
* **The service's `Basic` and `CredSSP` settings stay `False` unless you use HTTP Basic.** NTLM
and Kerberos need neither. To use HTTP Basic, enable the `Basic` setting under the service's
`auth` section — `winrm set winrm/config/service/auth @{Basic=true}` — and connect over HTTPS
([Authentication](authentication.html)).
`auth` section — `winrm set winrm/config/service/auth @{Basic=true}` — connect over HTTPS, and
authenticate with a **bare local account name**: Windows rejects Basic for domain accounts and
for any `DOMAIN\`-qualified name ([Authentication](authentication.html)).
* **`TrustedHosts` is irrelevant.** That is a setting on the *Windows* WinRM **client**, consulted
by the `winrs` command-line tool. A Java client never reads it, so you do not need to add anything
to it on either machine.
Expand Down
Loading