supermicro / java / console redirection / kvm

supermicro / java / console redirection / kvm

  • Written by Walter Doekes

  • Published on: 12/02/2020

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:

  1. A TLS layer is added to all connections (KVM port 5900, and virtual storage port 623). That’s a good thing.
  2. 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).

Back to overview