supermicro / java / console redirection / kvm
Connecting to the new SuperMicro iKVM management interfaces requires a working Java in your browser (IcedTea browser plugin). It will launch a Java Web Start (javaws) application. And java plugins (and needless web forms) are a pain in the behind. Is there a better way?
Obviously, running from the web interface just means downloading a Java application, and running it locally. So why can’t we do that directly, and skip the IcedTea Java browser plugins?
Previously, we could connect to most hosts using the IPMIView utility, downloadable from the SuperMicro FTP server. Nowadays, that doesn’t work for two reasons:
- A TLS layer is added to all connections (KVM port 5900, and virtual storage port 623). That’s a good thing.
- The KVM username and password are rotated instead of being the same as the one used to connect to the HTTPS interface. A less obvious decision.
Wrapping the connection in TLS
IPMIView already had the capability of wrapping some connections in TLS. As can be seen in ipmikvm (2019):
# spawn socat
crtpath="$ipmipath/BMCSecurity" # "/opt/ipmiview/BMCSecurity/client.crt"
crtopt="commonname=IPMI,cafile=$crtpath/server.crt"
crtopt="$crtopt,cert=$crtpath/client.crt,key=$crtpath/client.key"
socat "TCP4-LISTEN:$port" "OPENSSL:$remotehost:$remoteport,$crtopt" &
That works for most current SuperMicro iKVM interfaces. However, that script did not provide for Virtual Storage (port 623) TLS wrapping.
Fetching the rotated username and password
With the newer SuperMicro management interfaces, you can log in on
https://MANAGEMENT_IP/
using the configured username+password, but
connecting to the KVM interface yields a Authentication Failed
.
If you go to Remote Control, Console Redirection and click Launch
Console, you’re presented with a launch.jnlp
download — which may or
may not get picked up by your Java plugins, if you have them.
You can fetch this file programmatically, using a bit of curl and bash:
get_launch_jnlp() {
management_ip="$1"
user="$2"
pass="$3"
fail=1
url="https://$management_ip"
temp=$(mktemp)
b64_user=$(echo -n "$user" | base64 -w0 | sed -e 's/=/%3D/g;s/+/%2B/g')
b64_pass=$(echo -n "$pass" | base64 -w0 | sed -e 's/=/%3D/g;s/+/%2B/g')
if curl --fail -sk --cookie-jar "$temp" -XPOST "$url/cgi/login.cgi" \
--data "name=$b64_user&pwd=$b64_pass&check=00" -o/dev/null; then
launch_jnlp=$(curl --fail -sk --cookie "$temp" \
"$url/cgi/url_redirect.cgi?url_name=man_ikvm&url_type=jwsk")
test $? -eq 0 && fail=
fi
rm "$temp"
test -z "$fail" && echo "$launch_jnlp"
}
# SYNOPSIS: get_launch_jnlp 10.x.x.x USERNAME PASSWORD
And that launch.jnlp
file will look somewhat like this:
<jnlp spec="1.0+" codebase="http://10.x.x.x:80/">
<information>
<title>ATEN Java iKVM Viewer</title>
<vendor>ATEN</vendor>
<description>Java Web Start Application</description>
</information>
<security>
<all-permissions/>
</security>
<resources>
<property name="jnlp.packEnabled" value="true"/>
<j2se version="1.6.0+" initial-heap-size="128M"
max-heap-size="128M" java-vm-args="-XX:PermSize=32M -XX:MaxPermSize=32M"/>
<jar href="iKVM__V1.69.39.0x0.jar" download="eager" main="true"/>
</resources>
<!-- ... -->
<resources os="Linux" arch="amd64">
<nativelib href="liblinux_x86_64__V1.0.12.jar" download="eager"/>
</resources>
<!-- ... -->
<application-desc main-class="tw.com.aten.ikvm.KVMMain">
<!-- arguments 0..9 -->
<argument>http://10.x.x.x:80/</argument>
<argument>iKVM__V1.69.39.0x0.jar</argument>
<argument>libwin_x86__V1.0.12.jar</argument>
<argument>libwin_x86_64__V1.0.12.jar</argument>
<argument>libwin_x86_64__V1.0.12.jar</argument>
<argument>liblinux_x86__V1.0.12.jar</argument>
<argument>liblinux_x86__V1.0.12.jar</argument>
<argument>liblinux_x86_64__V1.0.12.jar</argument>
<argument>liblinux_x86_64__V1.0.12.jar</argument>
<argument>libmac_x86_64__V1.0.12.jar</argument>
<!-- arguments 10..21 -->
<argument>10.x.x.x</argument><!-- 10 management_ip -->
<argument>otherUserName</argument><!-- 11 username -->
<argument>otherPassWord</argument><!-- 12 password -->
<argument>null</argument><!-- 13 hostname (unused) -->
<argument>63630</argument><!-- 14 non-tunnel KVM port -->
<argument>63631</argument><!-- 15 non-tunnel VM port -->
<argument>0</argument><!-- 16 companyId? -->
<argument>0</argument><!-- 17 boardId? -->
<argument>1</argument><!-- 18 use TLS tunnel -->
<argument>5900</argument><!-- 19 remote KVM port -->
<argument>623</argument><!-- 20 remote VM port -->
<argument>1</argument><!-- 21 VM/USB service -->
</application-desc>
</jnlp>
Using the socat
trick as mentioned above works when supplying these
emphemeral otherUserName and otherPassWord to the old iKVM.jar
as
seen in the ipmikvm (2019) script.
However, getting the Virtual Storage to work was less trivial than
simply setting up a second socat
(with forking mode, needed because
the Java iKVM will connect to it multiple times):
socat "TCP4-LISTEN:$sport,bind=127.0.0.1,fork" "OPENSSL:$dhost:$dport,$crtopt"
That did not do the trick. Instead, it rained
Close Socket Beause Socket Pipe is broken
errors.
Luckily there is a better way: fetch the new Java application from the SuperMicro management interface directly, and use that.
Fetching the Java Web Start application
If we take the URLs from the launch.jnlp
above, we get:
http://10.x.x.x:80/iKVM__V1.69.39.0x0.jar
However, it is jnlp.packEnabled
packed, so it becomes:
http://10.x.x.x:80/iKVM__V1.69.39.0x0.jar.pack.gz
Fetching those:
$ for x in iKVM__V1.69.39.0x0.jar liblinux_x86_64__V1.0.12.jar; do
curl -o $x.pack.gz http://10.x.x.x:80/$x.pack.gz
done
$ for x in *.pack.gz; do unpack200 $x ${x%.pack.gz}; done
$ ls -l
total 7968
-rw-r--r-- 1 walter walter 3985027 feb 12 16:21 iKVM__V1.69.39.0x0.jar
-rw-r--r-- 1 walter walter 3811134 feb 12 16:20 iKVM__V1.69.39.0x0.jar.pack.gz
-rw-r--r-- 1 walter walter 178685 feb 12 16:21 liblinux_x86_64__V1.0.12.jar
-rw-r--r-- 1 walter walter 178699 feb 12 16:20 liblinux_x86_64__V1.0.12.jar.pack.gz
(Seriously? Why bother packing it?)
$ unzip liblinux_x86_64__V1.0.12.jar
Archive: liblinux_x86_64__V1.0.12.jar
PACK200
inflating: META-INF/MANIFEST.MF
inflating: META-INF/SMCCERT2.SF
inflating: META-INF/SMCCERT2.RSA
inflating: libSharedLibrary64.so
inflating: libiKVM64.so
$ rm -rf META-INF
Alright! Now we have everything we need. (It needs the libiKVM64.so
in
the current path.)
$ java -cp iKVM__V1.69.39.0x0.jar tw.com.aten.ikvm.KVMMain
no iKVM64 in java.library.path
$ java -Djava.library.path=. -cp iKVM__V1.69.39.0x0.jar tw.com.aten.ikvm.KVMMain
Usage: IP USER_NAME PASSWORD PORT
$ java -Djava.library.path=. -cp iKVM__V1.69.39.0x0.jar tw.com.aten.ikvm.KVMMain \
1 2 3 4
Usage: IP USER_NAME PASSWORD PORT
Interesting. Somehow that help text did not keep up with development.
$ java -Djava.library.path=. -cp iKVM__V1.69.39.0x0.jar tw.com.aten.ikvm.KVMMain \
$(seq 10) 10 11 12 13
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 14
$ java -Djava.library.path=. -cp iKVM__V1.69.39.0x0.jar tw.com.aten.ikvm.KVMMain \
$(seq 10) 10 11 12 13 14
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 15
$ java -Djava.library.path=. -cp iKVM__V1.69.39.0x0.jar tw.com.aten.ikvm.KVMMain \
$(seq 10) 10 11 12 13 14 15
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 21
Ok, ok, I get it! All arguments it is…
(Full disclosure: decompiling
KVMMain.class
from iKVM__V1.69.39.0x0.jar
helped in determining the arguments seen
in the annotated xml above.)
So, connecting to the KVM then becomes something like this:
exec_ikvm_tls2020() {
jar="$1"
management_ip="$2"
user="$3"
pass="$4"
# If you look at the (decompiled) source, you'll notice that the
# two local ports (65534 65535) get replaced, and the two remote ports
# are set to default (5900 623). We'll set them here in case an updated
# jar file fixes it.
# Additionally, if you set use_tls (argument 18) to 0, the code
# path simply ends. So we cannot use this one to connect to older
# plain iKVM interfaces.
exec java -Djava.library.path="$(dirname "$jar")" \
-cp "$jar" tw.com.aten.ikvm.KVMMain \
0 1 2 3 4 5 6 7 8 9 \
"$management_ip" "$user" "$pass" null 65534 65535 0 0 1 5900 \
623 1
}
# SYNOPSIS: exec_ikvm_tls2020 10.x.x.x KVM_USERNAME KVM_PASSWORD
Combined with the code that fetched the JNLP, and the java application,
we’ll end up with something like this:
ipmikvm-tls2020
(see also: ossobv/vcutil on GitHub).