Plugins

Bastille added support for running custom plugins in version 1.5.0. A plugin is simply some additional files located in the plugins directory, which allow Bastille to be extended beyond what it officially supports.

As of version 1.5.0, these files must be shell scripts, written in POSIX sh. Some basic things need to be in place in order to run a plugin.

You can either manually build a plugin, or place it on a remote repo (only Github for now) to be easily bootstrapped to any system.

Attention

Plugins are an experiemntal feature, and might be removed in a future version of bastille.

Plugin Structure

A plugin is simply a directory inside ${bastille_sharedir}/plugins that include any number of customized shell scripts. The scripts must end in the .sh suffix or Bastille will not recognize them.

If you are planning to place the plugin in a remote repository such as Github, you should include plugin.conf at the root of the repo. The plugin.conf must have the following contents:

name=""
min_version=""
depends_kmods=""
depends_pkgs=""
  • name should be the name of your plugin (this will be the directory name inside the plugins directory)

  • min_version should most always be set to 1.5.0, as that is the first version to support plugins, but can be higher for future additional features

  • depends_kmods should include any kernel modules that the plugin might need

  • depends_pkgs should include any additional packages the plugin might need

If the name filed is left blank, the plugin directory will simply be named after the repo name. Also, if the min_version is left blank, Bastille will just install the plugin without verifying the version.

The rest of the contents of the plugin directory should include any custom files you so desire. One thing to note is that the files must end in the .sh suffix. Bastille first validates the plugin directory, then checks if the next parameter is a file inside the directory ending in .sh. If it is not, Bastille will not run the command.

Parameter Order

Bastille follows a strict parameter order for each of its subcommands. Plugins should follow the same order to keep things simple. The order is COMMAND OPTIONS TARGET ARGS.

  • COMMAND should be your custom plugin command

  • OPTIONS should be any flags passed to the command

  • TARGET should be the jail or release the command is to act on

  • ARGS can be any other parameters passed to the command

In order to demonstrate what a command should look like, we will have a look at the official cmd.sh file. This command runs arbitrary commands inside a jail.

. /usr/local/share/bastille/common.sh

The first line simply sources the file that includes Bastille specific functions.

usage() {
    error_notify "Usage: bastille cmd [option(s)] TARGET COMMAND"
    cat << EOF

    Options:

    -a | --auto      Auto mode. Start/stop jail(s) if required.

EOF
    exit 1
}

This block is a command specific usage function that can be shown when an error occurs.

# Handle options
AUTO=0
while [ "$#" -gt 0 ]; do
    case "${1}" in
        -h|--help|help)
            usage
            ;;
        -a|--auto)
            AUTO=1
            shift
            ;;
    -*)
        error_exit "[ERROR]: Unknown Option: \"${1}\"" ;;

     *)
        break
        ;;
    esac
done

# Verify parameter count
if [ $# -eq 0 ]; then
    usage
fi

This code handles any options or flags passed to the given command. Once the flags are parsed, an optional block checks for valid parameter counts for the command. In the case of cmd.sh, the parameter count can be fairly large, so we simply error only when none are supplied.

Any flags that apply to the command should immediately follow the command. Eg: bastille plugin myplugin command --flag1 --flag2...

TARGET="${1}"
shift 1
ERRORS=0

bastille_root_check
set_target "${TARGET}"

Next we set the TARGET variable. If a command is to target a jail or release, it should be the next parameter. For example, if I want to target a jail named nextcloud with my plugin, and use -a|--auto mode, we would run the following: bastille plugin myplugin plugincmd --auto nextcloud....

The set_target function takes a single parameter and validates the target exists, then exports it into two variable called TARGET and JAILS. The reason we have two, is for reasons shown in the following code block.

Once you have set the target, you can use TARGET and JAILS in your command.

for jail in ${JAILS}; do

    # Validate jail state
    check_target_is_running "${jail}" || if [ "${AUTO}" -eq 1 ]; then
        bastille start "${jail}"
    else
        info 1 "\n[${jail}]:"
        error_notify "Jail is not running."
        error_continue "Use [-a|--auto] to auto-start the jail."
    fi

    info 1 "\n[${jail}]:"

    check_fib "${jail}"
    # Allow executing commands on linux jails
    if grep -qw "linsysfs" "${bastille_jailsdir}/${jail}/fstab"; then
        ${SETFIB} jexec -l -u root "${jail}" "$@"
    else
        ${SETFIB} jexec -l -U root "${jail}" "$@"
    fi

    if [ "$?" -ne 0 ]; then
        ERRORS=$((ERRORS + 1))
    fi

done

if [ "${ERRORS}" -ne 0 ]; then
    error_exit "[ERROR]: Command failed on ${ERRORS} jails."
fi

The last block of code in the cmd.sh command runs any code inside each jail in the JAILS variable. JAILS can contain more than one jail, which is why we do the for loop. There is also a set_target_single function that will set only a single target. This goes into the TARGET parameter.

The set_target* functions only apply to jails, not releases.

Building a Plugin

Before we start buliding a plugin, we must create a directory inside ${bastille_sharedir}/plugins and name it according to our plugins desired name. For example, if my plugin is to be called myplugin, I would create ${bastille_sharedir}/plugins/myplugin.

For this guide, we will assume you are building it locally. Once the directory is in place, we can start creating our *.sh files.

When the plugin is built, the directory structure should be as follows:

/usr/local/share/bastille/plugins/myplugin
/usr/local/share/bastille/plugins/myplugin/plugin.conf
/usr/local/share/bastille/plugins/myplugin/cmd1.sh
/usr/local/share/bastille/plugins/myplugin/cmd2.sh
/usr/local/share/bastille/plugins/myplugin/cmd3.sh

The plugin.conf file will really only be necessary when bootstrapping a plugin from a remote repo, but its best to include it anyway.

If you decide to create your plugin in a remote repo such as Github, you can bootstrap the plugin with bastille plugin https://github.com/myuser/myplugin. This will validate the manifest file we created (plugin.conf) and install the plugin at ${bastille_sharedir}/plugins/${name}. It will name the plugin after the name variable in plugin.conf, load any kernel modules, and install any pkgs included in the manifest file. If the name variable is empty, it will default to the repo name.

Running a Plugin

Once bootstrapped, you can run your plugin using bastille plugin myplugin cmd1.sh. You can also do bastille p myplugin mycmd... as a shorthand version of the command above.

Any options and parameters passed to the plugin should be validate and parsed by the plugin commands. Bastille is only responsible for passing the plugin name and additional parameters to whichever plugin command is given.

Functions

If you want to use any of the official Bastille functions, you can add the following line to the top of your commands.

. /usr/local/share/bastille/include

Some functions that might be helpful to use are:

bastille_root_check
info
warn
error_exit
error_continue
error_notify
check_target_exists
check_target_is_running
check_target_is_stopped

There are obviously more functions, and you will have to browse through the code to discover how each on is meant to be used.

You can also create you own functions and files and include them inside your plugin in the same way Bastille does.

Example Plugin

An example plugin is provided here. We will call this plugin custom-restart.

Inside ${bastille_sharedir}/plugins/custom-restart we have a single file called restart.sh which contains the following code:

#!/bin/sh

TARGET="${1}"
set_target "${TARGET}"

for jail in ${JAILS}; do
  bastille restart ${jail}
      info 1 "\nSuccessfully restarted ${jail}, moving to the next one..."
done

I can this command with bastille plugin custom-restart ALL. ALL here is the target, and Bastille will target all jail in this case. To target only select jails, we can run bastille plugin custom-restart 'jail1 jail2 jail3'.

One of the first plugins Bastille supports is found at https://github.com/usenix17/bastille-vm-plugin.

Try bootstrapping it, and see how it was built.

Notes

When uninstalling Bastille, all plugins will be removed also. Make sure to back them up, or store them remotely if you so choose.