Custom Plugin Guide

Custom Plugin Integration Guide

This guide covers how to register a custom executable as a managed plugin — a long-running component that the agent starts, monitors, and restarts as part of its own lifecycle. If you have not already read the Overview page, start there: it defines the plugin pattern, explains when it is the right choice over a job task, and describes the concepts this guide builds on.

If you are building a tool that runs on a schedule and exits — a scanner, reporter, or maintenance job — you want the Custom Job Integration Guide instead. This guide is specifically for processes that stay running.

1

The Plugin JSON File

Location and Naming

A plugin is registered by placing a JSON file at:

{AgentRoot}/Plugins/{PluginId}.json

The filename without .json must match the id field inside the file. A file named MyBridge.json must contain "id": "MyBridge". The plugin ID is also used as the key when calling Plugin Settings — GET /api/PluginSettings/MyBridge — so choose a stable, unique identifier and do not change it once deployed.

Binaries are typically placed under:

{AgentRoot}/Plugins/bin/{PluginId}/{PluginId}.exe    (Windows)
{AgentRoot}/Plugins/bin/{PluginId}/{PluginId}        (Linux / macOS)

Set executablePath in the JSON to the path relative to the agent root, or to a full absolute path if your deployment layout requires it.

Anatomy of a Plugin: JSON

Here is a complete annotated example:

{
  "id": "MyBridge",
  "name": "My Bridge",
  "description": "Bridges inbound commands to an internal service.",
  "version": "1.0.0",

  "pluginType": "Executable",
  "executablePath": "bin/MyBridge/MyBridge.exe",
  "supportedPlatforms": ["Windows"],

  "Subscription": {
    "Topic": "MyBridge",
    "Qos": 2,
    "CleanSession": true
  },

  "metadata": {
    "mqttRole": ["subscriber", "publisher"],
    "mqttTopics": {
      "publish": ["KeeperLogger", "MyBridge/Responses/+"],
      "subscribe": ["MyBridge/Commands/+"]
    }
  },

  "startupPriority": 60,
  "autoStart": true,
  "executionContext": "Service",
  "requiresMonitoring": true,
  "autoRestart": true
}

Field Reference

Field
Required
Description

id

Yes

Stable identifier. Must match the filename. Used as the key in /api/PluginSettings/{id}.

name

No

Display name shown in logs and admin views.

description

No

Human-readable description.

version

No

Semantic version string. Used for diagnostics and change tracking.

pluginType

Yes

Executable for a standalone binary. Other types may be available depending on your agent version — confirm with your administrator.

executablePath

Yes

Path to the binary. Relative to the agent root or absolute.

arguments

No

Command-line arguments passed to the binary at launch. Supports some variable substitution — confirm supported tokens with your administrator.

supportedPlatforms

Yes

Array of "Windows", "Linux", and/or "macOS".

Subscription

Yes

Primary MQTT subscription for this plugin. See MQTT for Plugins.

metadata

Yes

Container for mqttTopics and any plugin-specific keys.

metadata.mqttTopics.publish

Yes

Topics this plugin may publish to. The broker enforces this list.

metadata.mqttTopics.subscribe

Yes

Topics this plugin subscribes to beyond the primary Subscription topic.

startupPriority

No

Controls the order in which plugins start. Lower numbers start earlier. Look at the values used by other plugins in your deployment and choose a value that fits your component's dependencies.

autoStart

No

true to have the agent start the plugin automatically when the agent starts. false for on-demand or job-triggered invocation. Defaults to false if omitted.

executionContext

No

Service to run as the agent service account. Match this to your security requirements.

requiresMonitoring

No

true to have the agent watch the process and detect if it exits unexpectedly.

autoRestart

No

true to have the agent restart the plugin if monitoring detects it has stopped. Only meaningful when requiresMonitoring is also true.

enabled

No

Set to false to register the plugin without it running. Useful during development.

2

MQTT for Plugins

Plugins interact with the agent's MQTT broker differently from job tasks in three important ways:

  1. They have a primary Subscription .

  2. They declare topics under metadata.mqttTopics rather than on the job root.

  3. They use a different MQTT client ID format.

The Primary Subscription

Every plugin declares a primary MQTT subscription:

"Subscription": {
  "Topic": "MyBridge",
  "Qos": 2,
  "CleanSession": true
}

This is the topic the agent uses to identify and communicate with the plugin as a named component. It is separate from the additional topics declared in metadata.mqttTopics. Use Qos: 2 for commands that must not be lost or duplicated. Set CleanSession: true unless your use case requires a persistent session.

Declaring Publish and Subscribe Topics

All topics your plugin publishes to or subscribes from must be declared in metadata.mqttTopics. The broker enforces these lists — a publish to an undeclared topic will be denied even if the MQTT connection succeeds.

"metadata": {
  "mqttTopics": {
    "publish": ["KeeperLogger", "MyBridge/Responses/+"],
    "subscribe": ["MyBridge/Commands/+"]
  }
}

Declare only what you actually use. Avoid broad wildcards like # unless your use case genuinely requires them — tighter topic lists reduce the blast radius of misconfiguration.

If you publish log messages to KeeperLogger, include it in publish. The message format is the same RequestMessage JSON structure described in the Custom Job Integration Guide.

MQTT Client ID Format for Plugins

Plugin MQTT client IDs use a different format from job tasks. Do not use the job triple ({JobId}_{Token}_{Pid}) for a plugin — the broker uses the client ID format to route topic permission checks, and using the wrong format will cause those checks to fail.

For a plugin running as a service account (the typical case):

{PluginName}_{ProcessId}

For a plugin running in a user session:

{PluginName}_{UserName}_{ProcessId}

Where {PluginName} is a short, stable token — typically aligned with your plugin id. A complete example:

import os

plugin_name = "MyBridge"
pid = os.getpid()
client_id = f"{plugin_name}_{pid}"
# Example result: "MyBridge_51204"

Connecting to the Broker

The broker address is not injected as an environment variable. Your plugin fetches it from Plugin Settings at startup, using the same pattern as job tasks — call GET /api/PluginSettings/KeeperPrivilegeManager using the KeeperApiBaseUrl the agent makes available. See the code example on the Overview page.

Unlike a job task, your plugin does not receive {KeeperApiBaseUrl} as an injected argument automatically. You will need to either pass the API base URL explicitly via arguments in the plugin JSON, or fall back to the default https://127.0.0.1:6889 if your deployment guarantees that port.

The broker uses TLS on the loopback interface. Connect with your MQTT client configured for TLS, using your organization's CA bundle if provided, or disabling certificate verification for loopback-only connections per your security policy.

3

Plugin Settings Over HTTPS

Your plugin can read its own scoped configuration — separate from the system-wide KeeperPrivilegeManager settings — using its own plugin ID:

GET https://127.0.0.1:{httpsPort}/api/PluginSettings/MyBridge

This returns the effective merged settings for your plugin as a flat JSON object of string key-value pairs. Settings can come from:

  • Your plugin JSON file (Plugins/MyBridge.json)

  • Unified storage (applied by policy)

  • System defaults

Always call this endpoint to read effective settings rather than parsing the JSON file on disk directly. The file on disk may not reflect policy overrides.

To update a single setting key from inside a running plugin:

PUT https://127.0.0.1:{httpsPort}/api/PluginSettings/MyBridge/{settingName}

Both endpoints require Plugin-tier authorization — your plugin must be started by the agent to pass process authentication. A manual run of the same binary will receive 403. See the HTTP Reference for the full Plugin Settings API.

4

Signing and Process Trust

Binaries placed under Plugins/ paths are subject to stricter trust rules than binaries under Jobs/bin/. When the plugin orchestrator starts your binary, it registers the process in the launched-process registry, which grants MQTT and Plugin-tier HTTPS access — the same mechanism as job tasks. However, the path expectations and certificate checks that apply to plugin executables are tighter.

Sign all plugin binaries for production. Windows Authenticode is required for the standard plugin trust path on Windows. On macOS, use Apple Developer ID. On Linux, align with your deployment's signing enforcement (GPG-signed packages or IMA where applicable).

Add your code-signing certificate thumbprint to Settings:AlternativeSignatures in appsettings.json if your deployment requires explicit trust registration for the thumbprint:

{
  "Settings": {
    "AlternativeSignatures": [
      "A1B2C3D4E5F6789012345678901234567890ABCD"
    ]
  }
}

Restart the agent service after changing appsettings.json.

Do not rely on manual runs for production validation. If you start your plugin binary manually outside the agent, it must pass a certificate check to connect to MQTT or call Plugin Settings. A binary started by the orchestrator skips that check because it is already registered. Test manual invocations in a development environment before assuming production behavior matches.

5

Deployment

Adding a new plugin to a production agent is a more involved process than deploying a job. It typically requires:

  1. Packaging and signing the binary according to your organization's release process.

  2. Placing the binary at the executablePath specified in the plugin JSON on every target endpoint.

  3. Dropping the plugin JSON at {AgentRoot}/Plugins/{PluginId}.json using a supported deployment mechanism — not a manual file copy if Last Known Good is enabled.

  4. Restarting the agent or triggering a plugin reload so the orchestrator picks up the new registration.

Work with your Keeper administrator to confirm the supported insertion path for new plugins in your environment. Arbitrary file drops without coordination can conflict with Last Known Good protection or packaging expectations.

Updating plugin settings after deployment does not require restarting the agent. Use PUT /api/PluginSettings/{PluginId}/{settingName} from a Plugin-tier authenticated process, or deliver a settings policy update through the console.

If settings are not taking effect, use POST /api/PluginSettings/{PluginId}/revert to re-import the plugin's on-disk JSON into unified storage, then check the effective values with a GET.

Checklist

Run through every item before rolling out to your fleet.

#
Check

1

Plugin id matches filename; binary exists at executablePath on each supported platform

2

Subscription.Topic is set and unique among plugins in the deployment

3

metadata.mqttTopics.publish includes every topic the binary actually publishes to

4

metadata.mqttTopics.subscribe includes every topic the binary actually subscribes to beyond Subscription.Topic

5

MQTT client ID uses the plugin format (PluginName_Pid) — not the job triple

6

Binary is signed; thumbprint is in AlternativeSignatures if required

7

GET /api/PluginSettings/KeeperPrivilegeManager returns broker.host and broker.port from inside the running plugin

8

GET /api/PluginSettings/{PluginId} returns the expected plugin-scoped settings

9

MQTT connects, primary subscription is active, and publish to KeeperLogger produces visible log output

10

autoStart, requiresMonitoring, and autoRestart settings are validated with operations — confirm CPU usage, session behavior, and restart cadence are acceptable

11

No lifecycle conflict with a job running the same binary — separate executables for separate roles

Troubleshooting

Symptom
Where to look

Plugin does not start after deployment

Check executablePath resolves on the target OS; verify binary is signed; check agent logs for orchestrator errors

MQTT connection refused

Binary not registered as trusted; confirm the orchestrator started it rather than a manual run

MQTT connects but publish is denied

Topic missing from metadata.mqttTopics.publish; verify topic string matches exactly

GET /api/PluginSettings/MyBridge returns 403

Process not Plugin-authenticated; ensure the agent (not a manual invocation) started the binary

Settings changes not reflected

Unified storage may be overriding the JSON file; call /revert to re-import, then re-check

Plugin restarts unexpectedly in a loop

autoRestart: true combined with a binary that exits on a startup error; fix the root cause before re-enabling auto-restart

Duplicate processes detected

Same binary registered as both a plugin and a job task; separate the roles into distinct executables

Last updated

Was this helpful?