Skip to content

Building Tuoni Plugins

Classloader isolation

In order to avoid classpath conflicts between different plugins, Tuoni server loads each plugin with an isolated class loader so that the plugin has access to the JVM standard library and few allowlisted libraries, but does not see server’s own classes/dependencies nor the classes of other plugins. Due to this, every plugin must bundle all of its dependencies into single uber jar. When using Gradle, it is recommended to use shadow plugin. For Maven, use maven-shade-plugin.

Currently, the only dependencies provided by the Tuoni server are:

  • SLF4J for logging - org.slf4j:slf4j-api
  • Tuoni SDK for plugin development - com.shelldot:tuoni-plugin-sdk

These dependencies must not be bundled into the plugin jar, as they are provided by the server. So these dependencies should have provided scope in Maven or compileOnly in Gradle.

Plugin discovery

In order for Tuoni server to recognize the plugin, it must contain both a service provider configuration file and a MANIFEST.MF in the jar.

MANIFEST.MF

The manifest must contain the following entries:

  • Plugin-Id (Mandatory) - Mandatory unique text id to identify this plugin. It is strongly recommended to use the following naming scheme: $orgName.$pluginType.$pluginName. For example: “exampleorg.listener.http” or "myorg.command.ping"
  • Plugin-Version (Mandatory) - Semver version of the plugin. Version can later be used for migrating configurations from older versions of the plugin to newer versions
  • Plugin-Provider (Mandatory) - Vendor/organisation/company/user who is the author and maintainer of the plugin
  • Plugin-Name (Mandatory) - Name of the plugin
  • Plugin-Description (Optional) - Description about what this plugin does
  • Plugin-Url (Optional) - URL to the source-code or distribution of the plugin.

ServiceLoader

The service provider configuration file follows Java ServiceLoader conventions, therefore there must exist a file under META-INF/services that is named after the plugin type that is implemented file and must contain the implementation class name of the custom plugin.

Plugin service paths for different plugin types:

  • Command Plugin: META-INF/services/com.shelldot.tuoni.plugin.sdk.command.CommandPlugin
  • Listener Plugin: META-INF/services/com.shelldot.tuoni.plugin.sdk.listener.ListenerPlugin
  • Payload Plugin: META-INF/services/com.shelldot.tuoni.plugin.sdk.payload.PayloadPlugin

Automation

If manually creating manifest and service files seems too cumbersome, then it is recommended to use Google Auto Service for ServiceLoader and to generate the MANIFEST.MF using the build tool (Gradle or Maven).

Shellcode

If the shellcode for the plugin is compiled ahead-of-time, then the binary for this shellcode should be bundled also inside the JAR file.

Keep in mind that Tuoni server is expected to run in an VM, container or environment that does not have any other tools/executables present, so compilation cannot occur inside the server code. In case dynamic compilation is needed, then this needs to be handled by creating a new separate service and implement communication between that service and the plugin. That however introduces friction to the end user since in addition to installing the plugin, they also need to spin up and configure your service. So such approach should only be used if strictly necessary.

Example build script

Here is an example of a build script that bundles an uber jar with dependencies and the built shellcode binary.