Skip to content
Open
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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
### 1.3.4 (unreleased)

* Added support for WinRS shell command execution (MS-WSMV): `WSManClient.runCommand()`
runs a single command on a short-lived remote shell and captures exit code, stdout,
and stderr. Available from the CLI via `-o SHELL`.
* Added support for Kerberos message encryption (MS-WSMV 2.2.9.1), enabled with
`WSManEndpoint.Builder.withKerberosEncryption()` or the CLI flag `-kerberosEncryption`.
Allows WS-Man over plain HTTP (port 5985) with confidentiality and integrity provided
by GSS-API wrap tokens (AES256-CTS-HMAC-SHA1-96 only). The encrypted transport rides a
dedicated connection owned by the client; all operations on one client share a single
Kerberos session and serialize on it.
* `WSManClient` now extends `AutoCloseable`. Call `close()` when done with a client to
release the Kerberos session (connection, GSS context, JAAS login); the default
implementation is a no-op for clients without such resources. As a backstop, a shared
background daemon thread reaps idle Kerberos sessions: the connection is closed after
60 seconds of inactivity and the GSS context and JAAS login are released after
15 minutes, so a client that is never closed does not hold resources forever. A reaped
session re-establishes itself transparently on next use.

### 1.2.1

* Added support for handling XmlFragment elements in pull responses.
Expand Down
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ A pure Java WS-Man client implemented using JAX-WS & CXF with support for:
* Enumerate and Pull Operations (DSP8037)
* Get Operations (DSP8035)
* Identify Operation (DSP0226)
* WinRS Shell Command Execution (MS-WSMV)
* Basic, NTLM and SPNEGO Authentication
* Kerberos Message Encryption (MS-WSMV 2.2.9.1), allowing encrypted WS-Man over plain HTTP
* OSGi Compatible

## Java Example
Expand Down Expand Up @@ -55,6 +57,34 @@ public class WSManClientExample {
}
```

### Kerberos message encryption and WinRS

Kerberos message encryption protects the SOAP payload itself (MS-WSMV 2.2.9.1), so it is
typically used over plain HTTP on port 5985. Credentials come either from a
username/password pair passed to the builder, or, when no password is given, from a JAAS
login configuration entry named `WSManClient` (set via
`-Djava.security.auth.login.config`, e.g. for keytab or ticket-cache authentication).
Windows WinRM expects the 2004/08 WS-Addressing namespace, so use `WSManVersion.WSMAN_1_0`.

All operations on one client share a single Kerberos session; call `close()` (the client
is `AutoCloseable`) to release the connection, GSS context, and JAAS login. Sessions that
are never closed are reaped automatically after sitting idle (the connection after 60
seconds, everything else after 15 minutes) and re-establish themselves transparently on
next use, so a forgotten `close()` does not pin resources forever; closing promptly is
still preferred.

```java
WSManEndpoint endpoint = new WSManEndpoint.Builder("http://windows-host:5985/wsman")
.withServerVersion(WSManVersion.WSMAN_1_0)
.withKerberosEncryption()
.build();
try (WSManClient client = new CXFWSManClientFactory().getClient(endpoint)) {
// WS-Man operations work as usual, and WinRS runs remote commands:
CommandResult result = client.runCommand("ipconfig", new String[] {"/all"}, Duration.ofSeconds(60));
System.out.println("exit=" + result.exitCode() + " stdout=" + result.stdout());
}
```

## Compiling From Source

Requires Java 8 and Maven 3 (tested with 3.3.3)
Expand Down Expand Up @@ -101,6 +131,48 @@ java -jar $WSMAN_CLI_JAR -w WSMAN_1_0 -r https://idrac/wsman -u root -p calvin "
java -jar $WSMAN_CLI_JAR -w WSMAN_1_0 -r https://idrac/wsman -u root -p calvin -o GET -resourceUri http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_ComputerSystem -s CreationClassName=DCIM_ComputerSystem -s Name=srv:system
```

#### Running a command via WinRS

Everything after `--` is passed to the remote shell verbatim, so command flags don't
collide with the CLI's own options. The command's stdout/stderr are passed through and
its exit code becomes the CLI's exit code. Use `-timeout` to bound the run (seconds,
default 60).

```sh
java -jar $WSMAN_CLI_JAR -w WSMAN_1_0 -r http://win-host:5985/wsman -u Administrator -p PASsW0rdz -o SHELL -- ipconfig /all
java -jar $WSMAN_CLI_JAR -w WSMAN_1_0 -r http://win-host:5985/wsman -u Administrator -p PASsW0rdz -o SHELL -timeout 120 -- powershell -Command "Get-Service"
```

#### Kerberos message encryption

Encrypts the SOAP payload itself (MS-WSMV 2.2.9.1), so plain HTTP on port 5985 is safe
to use. Requires a Kerberos setup: point the JVM at a krb5.conf, and either pass
`-u user@REALM -p password` or, for keytab/ticket-cache authentication, omit `-u`/`-p`
and provide a JAAS login configuration with an entry named `WSManClient` via
`-Djava.security.auth.login.config`. Use `-w WSMAN_1_0` so the WS-Addressing headers are
emitted in the 2004/08 namespace Windows expects. Works with all operations, including
`-o SHELL`.

```sh
java -Djava.security.krb5.conf=/etc/krb5.conf \
-Djava.security.auth.login.config=login.conf \
-jar $WSMAN_CLI_JAR -w WSMAN_1_0 -r http://win-host:5985/wsman -kerberosEncryption \
-resourceUri http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_OperatingSystem
```

Example `login.conf` using a keytab:

```
WSManClient {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
keyTab="/path/to/opennms-ws.keytab"
principal="opennms-ws@AD.EXAMPLE.COM"
storeKey=true
doNotPrompt=true;
};
```

#### Debugging:

Output the WS-Man requests and responses by setting the `-vvv` flag.
Expand Down
16 changes: 15 additions & 1 deletion api/src/main/java/org/opennms/core/wsman/WSManClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@
* * Get (DSP8035)
* * Identify (DSP0226)
*
* <p>Clients may hold long-lived resources (e.g. a Kerberos-encrypted transport
* session): call {@link #close()} when done with a client instance. The default
* implementation is a no-op, so implementations without such resources need not
* override it.
*
* @author jwhite
*/
public interface WSManClient {
public interface WSManClient extends AutoCloseable {

/**
* Discovers the capabilities and version information of the remote service.
Expand Down Expand Up @@ -138,4 +143,13 @@ public interface WSManClient {
public default CommandResult runCommand(String executable, String[] args, Duration timeout) {
return runCommand(executable, args, timeout, ShellOptions.defaults());
}

/**
* Releases any long-lived resources held by this client (connections, security
* contexts). Safe to call multiple times. No-op by default.
*/
@Override
public default void close() {
// no resources by default
}
}
17 changes: 15 additions & 2 deletions api/src/main/java/org/opennms/core/wsman/WSManEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class WSManEndpoint {
private final String username;
private final String password;
private final boolean gssAuth;
private final boolean kerberosEncryption;
private final boolean strictSSL;
private final WSManVersion serverVersion;
private final Integer maxElements;
Expand All @@ -46,6 +47,7 @@ private WSManEndpoint(Builder builder) {
username = builder.username;
password = builder.password;
gssAuth = builder.gssAuth;
kerberosEncryption = builder.kerberosEncryption;
strictSSL = builder.strictSSL;
serverVersion = builder.serverVersion;
maxElements = builder.maxElements;
Expand All @@ -60,6 +62,7 @@ public static class Builder {
private String username;
private String password;
private boolean gssAuth = false;
private boolean kerberosEncryption = false;
private WSManVersion serverVersion = WSManVersion.WSMAN_1_2;
private Integer maxElements;
private Integer maxEnvelopeSize;
Expand All @@ -85,6 +88,12 @@ public Builder withGSSAuth() {
return this;
}

public Builder withKerberosEncryption() {
kerberosEncryption = true;
gssAuth = true;
return this;
}

public Builder withStrictSSL(boolean strictSSL) {
this.strictSSL = strictSSL;
return this;
Expand Down Expand Up @@ -152,6 +161,10 @@ public boolean isGSSAuth() {
return gssAuth;
}

public boolean isKerberosEncryption() {
return kerberosEncryption;
}

public boolean isStrictSSL() {
return strictSSL;
}
Expand All @@ -177,10 +190,10 @@ public Integer getReceiveTimeout() {
}

public String toString() {
return String.format("WSManEndpoint[url='%s', isGSSAuth='%s', isBasicAuth='%s', isStrictSSL='%s', "
return String.format("WSManEndpoint[url='%s', isGSSAuth='%s', isKerberosEncryption='%s', isBasicAuth='%s', isStrictSSL='%s', "
+ "serverVersion='%s', maxElements='%s', maxEnvelopeSize='%s'"
+ "connectionTimeout='%s', receiveTimeout='%s']",
url, isGSSAuth(), isBasicAuth(), isStrictSSL(), serverVersion,
url, isGSSAuth(), isKerberosEncryption(), isBasicAuth(), isStrictSSL(), serverVersion,
maxElements, maxEnvelopeSize, connectionTimeout, receiveTimeout);
}
}
21 changes: 21 additions & 0 deletions cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,27 @@
<artifactId>jaxws-api</artifactId>
<version>2.3.1</version>
</dependency>
<!--
Java 11+ removed the Java EE APIs that were bundled with JDK 8. The WSDL/XSD-generated
sources and CXF use the old javax.* namespaces (JAXB 2.x, JAX-WS 2.x), so we include the
standalone API jars to make those classes available in the shaded executable jar.
jaxws-api transitively brings in javax.xml.soap (SAAJ) and javax.annotation as well.
-->
<dependency>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

javax.xml.bind:jaxb-api:2.3.1 and javax.xml.ws:jaxws-api:2.3.1 are now declared twice

<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.9</version>
</dependency>
<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
Expand Down
20 changes: 19 additions & 1 deletion cli/src/main/java/org/opennms/core/wsman/WSManCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ public enum WSManOperation {
@Option(name="-gssAuth", usage="GSS authentication")
private boolean gssAuth = false;

@Option(name="-kerberosEncryption", usage="Kerberos message encryption (MS-WSMV §2.2.9.1)")
private boolean kerberosEncryption = false;

@Option(name="-o", usage="operation")
WSManOperation operation = WSManOperation.ENUM;

Expand Down Expand Up @@ -145,15 +148,28 @@ public void doMain(String[] args) {
.withStrictSSL(strictSSL)
.withServerVersion(serverVersion)
.withMaxElements(100);
if (username != null && password != null) {
if (kerberosEncryption) {
if (username != null && password != null) {
builder.withBasicAuth(username, password);
}
builder.withKerberosEncryption();
} else if (username != null && password != null) {
builder.withBasicAuth(username, password);
} else if (gssAuth) {
builder.withGSSAuth();
}
WSManEndpoint endpoint = builder.build();
LOG.info("Using endpoint: {}", endpoint);
WSManClient client = clientFactory.getClient(endpoint);
try {
runOperation(client);
} finally {
// Release any long-lived transport resources (e.g. the Kerberos-encrypted session)
client.close();
}
}

private void runOperation(WSManClient client) {
if (operation == WSManOperation.ENUM) {
List<Node> nodes = new LinkedList<>();
if (arguments.isEmpty()) {
Expand Down Expand Up @@ -199,6 +215,8 @@ public void doMain(String[] args) {
System.out.print(result.stdout());
System.err.print(result.stderr());
LOG.info("Command exited with code {}", result.exitCode());
// Close before exiting so the transport session is torn down cleanly
client.close();
System.exit(result.exitCode());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

}
}
Expand Down
Loading