# Technical Architecture

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2F6QcvkiVOzi3kb14aT5CI%2FReference%20-%20Technical%20Architecture.png?alt=media&#x26;token=2ba2089c-1b20-45c3-9e53-f3946c9cfedf" alt=""><figcaption></figcaption></figure>

**Audience:** IT admins and technical staff. This page describes the **technical architecture** of Keeper Privilege Manager: how the system is **event-driven**, how **MQTT** and **HTTPS** are used, how **plugins** are loaded and monitored, and how **jobs** are launched and executed.

***

### Overview

Keeper Privilege Manager is a **central orchestration service** that runs on each endpoint. It does not perform policy evaluation or logging by itself; instead it:

* **Hosts an MQTT broker** and an **HTTP/HTTPS API** so that plugins and jobs can communicate.
* **Loads and monitors plugins** (separate processes such as KeeperPolicy, KeeperAPI, KeeperLogger) and can restart them if they fail.
* **Runs the job service**, which executes jobs when **events** fire (via MQTT), when **schedules** match, or when triggered **manually** via the API.

The system is **event-driven**: most workflows start when something **publishes a message** to the MQTT broker (e.g. “policy evaluation needed,” “launch elevation”). Subscribers (plugins or the job service) react to those messages and either handle the request or trigger a job that runs a sequence of tasks.

***

### High-Level Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                    KeeperPrivilegeManager (main service)                  │
├─────────────────────────────────────────────────────────────────────────┤
│  HTTP/HTTPS API (ports 6888 / 6889)  │  MQTT Broker (port 8675)         │
│  - Health, status, plugins, jobs     │  - Event routing                 │
│  - Settings, registration            │  - Plugin ↔ service messaging   │
│  - Plugin start/stop/restart         │  - Job triggers                 │
├─────────────────────────────────────────────────────────────────────────┤
│  Plugin orchestration        │  Job service                             │
│  - Load plugin JSON          │  - Event triggers (MQTT)                 │
│  - Launch plugin processes   │  - Schedule triggers (timer)            │
│  - Health checks             │  - Manual triggers (API)                │
│  - Auto-restart on failure   │  - Run tasks (executable, HTTP, etc.)   │
└─────────────────────────────────────────────────────────────────────────┘
         │                                          │
         │ launch & monitor                          │ execute
         ▼                                          ▼
┌──────────────────┐  ◄──── MQTT ────►  ┌──────────────────────────────────┐
│  Plugins         │                    │  Job executables / tasks          │
│  KeeperPolicy    │                    │  - Policy controls (MFA, etc.)    │
│  KeeperAPI      │                    │  - LaunchPrivilegeElevation        │
│  KeeperLogger   │                    │  - Inventory, risk, config         │
│  KeeperClient   │                    │  - Run as Service / User / HTTP    │
│  keeperAgent    │                    └──────────────────────────────────┘
│  ...            │
└──────────────────┘
```

* **Plugins** connect to the MQTT broker to receive and send messages. They may also call the HTTP API (e.g. to get settings, trigger a job, or launch a process).
* **Jobs** are triggered by events (MQTT), by schedule, or by API. When a job runs, it executes a list of **tasks** (run an executable, call HTTP, etc.). Task output can be merged into context so later tasks or routing decisions use it.

***

### Event-Driven Model

The system is **event-driven**: actions are triggered by **events** rather than by a single sequential program.

#### How events flow

1. **Something happens** on the endpoint (e.g. user requests elevation, a hook or agent detects it, or the service starts up).
2. A **message is published** to the MQTT broker on a specific **topic** (e.g. a policy evaluation request to the topic that KeeperPolicy subscribes to, or a custom event such as `PolicyEvaluationPending` or `LaunchPrivilegeElevation` to the topic the job service listens on).
3. **Subscribers** receive the message:
   * **Plugins** (e.g. KeeperPolicy) subscribe to topics and process requests (e.g. evaluate policy, return ALLOW/DENY/PENDING).
   * The **job service** subscribes (or is notified) when a **custom event** is published; it finds all jobs that list that event and **launches each matching job** with the event context.
4. **Jobs** run **tasks** in sequence. Tasks can publish more MQTT messages (e.g. send response back to a topic), call the HTTP API (e.g. launch elevated, get settings), or run executables (e.g. KeeperMFA, KeeperApproval). That can trigger **further events** (e.g. after controls complete, publish `LaunchPrivilegeElevation` so the launch job runs).

So one event leads to a job run; that job can publish another event, which leads to another job, and so on. Policy evaluation, controls (MFA, approval, justification), and launch flows are all wired this way.

#### Typical event names (custom events)

* **PolicyEvaluationPending** — A policy evaluation returned PENDING (controls required). Policy control jobs (e.g. privilege-elevation-policy-controls, file-access-policy-controls, default-policy-controls) subscribe to this and run MFA/justification/approval workflows.
* **LaunchPrivilegeElevation** — The user is allowed to elevate; the job that actually launches the process (LaunchPrivilegeElevation) is triggered with context (file path, command line, user, etc.).
* **Startup** — Fired when the service starts. Jobs such as registration or cleanup subscribe to this.
* **LaunchApprovedRequest**, **ShowAgent**, and others — Used by the UI or workflow to trigger specific jobs.

Events can carry **context** (parameters) that is passed into the job as its initial context so tasks can use it (e.g. `{RequestId}`, `{FilePath}`, `{UserName}`).

***

### How MQTT is Used

#### Role of the MQTT Broker

The main service runs an **embedded MQTT broker** (default port **8675**, localhost only). It is the **message bus** for the product:

* **Plugins** connect to the broker as MQTT clients. Each plugin has a **subscription** configuration (topic(s) to subscribe to) and **publish** permissions (topic(s) it can publish to).
* **Process-based security:** Only processes that were **launched by KeeperPrivilegeManager** (and that present a valid certificate) are allowed to connect. This prevents arbitrary applications from publishing or subscribing.
* **Topic-based routing:** Messages are delivered to every client that is subscribed to that topic. So when someone publishes to **EventMessages** (or the topic used for custom events), the job service receives it and can trigger jobs; when someone publishes to **KeeperPolicy**, the KeeperPolicy plugin receives it and evaluates policy.

#### Typical Topics (conceptual)

* **KeeperPolicy** — Policy evaluation requests (in); policy responses (out).
* **KeeperLogger** — Log messages (plugins and jobs publish here so logs are centralized).
* **KeeperApi** — Backend sync, registration, audit (KeeperAPI plugin).
* **EventMessages** (or equivalent) — Custom events that trigger jobs (e.g. PolicyEvaluationPending, LaunchPrivilegeElevation). The job service listens and matches event name to job definitions.
* **JobService** — Job-related messages (e.g. job completion, trigger).
* **keeperAgent**, **KeeperClient**, **RequestApproval**, etc. — Used by UI and approval workflows.

Exact topic names and which plugin publishes/subscribes to what are defined in each plugin’s JSON (`Subscription`, `metadata.mqttTopics`). The important point for architecture is: **all inter-component communication for events and policy flows over MQTT** on localhost, with process and topic checks.

#### MQTT and Event-Driven Jobs

When a **custom event** must trigger a job:

1. Some component (e.g. KeeperPolicy or another job) **publishes** a message to the event topic, with **event name** and **payload** (context).
2. The **job service** receives the message and calls **TriggerJobByEvent(eventName, context)** (or equivalent).
3. Every **job** whose `events` array contains that event name (e.g. `customEvent: "LaunchPrivilegeElevation"`) is **matched** and **started** with that context.
4. Each such job runs its **task list**; tasks can in turn publish to MQTT or call the HTTP API, producing further events or side effects.

So MQTT is used both for **request/response** (e.g. policy request → KeeperPolicy → response) and for **fire-and-forget event** delivery to the job service to **launch jobs**.

***

### How HTTPS (and HTTP) is Used

#### HTTP/HTTPS API Server

The main service exposes an **HTTP/HTTPS API** (Kestrel) bound to **localhost only** (127.0.0.1):

* **HTTP** default port **6888**
* **HTTPS** default port **6889** (recommended; uses a self-signed certificate for localhost)

The API is used for:

* **Health and status** — `GET /health`, `GET /`, `GET /api/system/status` (no auth; monitoring and scripts).
* **Plugin management** — Start/stop/restart plugins, list plugins (Admin or Plugin auth depending on endpoint).
* **Job management** — List jobs, run a job, trigger a job with context, validate job JSON (Plugin or Admin).
* **Settings** — Read/update application and plugin settings; revert plugin settings from JSON (Admin for write).
* **Registration** — Register or unregister the agent with the Keeper backend (Admin or as configured).
* **Other operations** — File access, user session launch, ephemeral account, audit, etc., as documented in [Local management endpoints](https://docs.keeper.io/en/keeperpam/endpoint-privilege-manager/reference/local-endpoints).

#### Who Calls the API

* **Plugins** — To load their settings (`GET /api/PluginSettings/{pluginName}`), to trigger jobs (`POST /api/Jobs/{id}/trigger`), or to call launch/ephemeral endpoints. They use the HTTPS base URL (e.g. from config or parameter).
* **Job tasks** — **HTTP tasks** inside a job call arbitrary URLs (often the local API) to trigger jobs, launch processes, create execution grants, or revert settings. **Service** tasks that run executables (e.g. KeeperMFA, RedirectEvaluator) may receive the API base URL as an argument so the executable can call back.
* **Admins / scripts** — Management scripts or operators call the API (with Admin privileges or client certificate as required) to check health, restart plugins, run jobs, or change settings.

**Authorization levels** (conceptual): **Public** (health, status), **Plugin** (process launched by KPM + certificate), **Admin** (elevated user or trusted process + certificate). So HTTPS is used for **synchronous** management and integration; MQTT is used for **asynchronous** event and message delivery between plugins and the job system.

***

### How Plugins are Loaded and Monitored

#### Loading Plugins

1. **Discovery** — At startup, the main service scans the **Plugins** directory for **plugin JSON files** (e.g. `KeeperPolicy.json`, `KeeperApi.json`). Each file describes one plugin: id, name, executable path, subscription topic(s), startup priority, autoStart, and other metadata.
2. **Validation** — The service validates the plugin (e.g. path security, certificate if required). Plugins that fail validation are not started.
3. **Startup order** — Plugins are ordered by **startupPriority** (lower number = start first). **autoStart: true** plugins are started in that order; **autoStart: false** plugins are loaded but not started until requested (e.g. via API or on demand).
4. **Launch** — For **Executable** plugins, the service launches the plugin process (the path in **executablePath**). The process connects to the MQTT broker and, typically, to the HTTP API to load settings. The plugin then subscribes to its configured topics and processes messages.

So **plugins are loaded from JSON**; they are **not** “registered” via API at runtime—the JSON files are the source of truth at startup. To add a plugin, you add its JSON (and binaries) and restart the service (or start the plugin via API if it has autoStart: false).

#### Monitoring and Restart

* **Health checks** — The main service periodically checks whether each plugin process is still running and optionally checks liveness (e.g. via a simple ping or health callback). Interval and behavior are configurable (e.g. in PluginMonitoring in appsettings).
* **Auto-restart** — If a plugin has **autoRestart: true** and the monitoring detects that it has exited or is unhealthy, the service can **automatically restart** it. This keeps critical plugins (e.g. KeeperPolicy, KeeperLogger) running.
* **Manual control** — An admin can **stop** or **restart** a plugin via the HTTP API (`POST /api/plugins/{name}/stop`, `POST /api/plugins/{name}/restart`) without restarting the whole service.

So: **plugins are loaded from Plugins/\*.json, started in priority order, and monitored; failed plugins can be restarted automatically or manually via the API.**

***

### How Jobs are Launched and Executed

#### Job Definition and Registration

* **Jobs** are defined by **JSON files** in the **Jobs** directory. Each file has an **id**, **events** (optional), **schedule** (optional), **parameters**, **tasks**, **condition**, and optional **alternateJobId**.
* At startup (and when files change, depending on implementation), the **job service** loads all job JSONs, validates them, and **registers** each job. A job is “launched” when one of its **triggers** fires.

#### Trigger Types

Jobs are **launched** when one of these happens:

| Trigger type | How it fires                                                                                                                                                                                | Job matches when                                                                                                                     |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Event**    | A message is published to the event topic with an **event name** (e.g. PolicyEvaluationPending, LaunchPrivilegeElevation). The job service calls **TriggerJobByEvent(eventName, context)**. | The job’s **events** array contains an event with that name (e.g. `eventType: "Custom"`, `customEvent: "LaunchPrivilegeElevation"`). |
| **Schedule** | An internal **timer** runs (e.g. every minute or per cron). The job service evaluates each job’s **schedule** (interval, cron, runAt, calendar).                                            | The current time matches the job’s schedule.                                                                                         |
| **Manual**   | An admin or script calls **POST /api/Jobs/{jobId}/run** or **POST /api/Jobs/{jobId}/trigger** with optional body.                                                                           | The trigger’s job id matches the job’s **id**.                                                                                       |

A job can have both **events** and **schedule**; it runs whenever **either** an event matches **or** the schedule matches.

#### Job Execution Flow (high level)

1. **Trigger fires** → Job service finds all jobs that match (by event name, schedule, or job id).
2. For each matching job (often one), the service **starts** the job run with the **trigger context** (event payload, parameters, defaults).
3. **Job-level condition** (if defined) is evaluated. If it fails, the job is skipped (or **alternateJobId** is run). If it passes, the **task loop** runs.
4. **Task loop** — For each task in order:
   * **Task condition** (if defined) is evaluated against the current **context** (trigger context + parameters + outputs from previous tasks). If false, the task is **skipped** and the next task in list order runs.
   * If the task references a **plugin setting** (e.g. “skip when metadata.redirect.enabled is false”), that is evaluated; if the setting says skip, the task’s command is not run and optional **ContextWhenSkipped** is merged into context.
   * The task is **executed**:
     * **Service** — Run an executable in the service session (e.g. RedirectEvaluator, KeeperMFA). Output (e.g. JSON on stdout) can be merged into context for later tasks.
     * **HTTP** — Issue an HTTP/HTTPS request (e.g. to the local API to launch a process or trigger another job). No executable.
     * **User / UserDesktop / UserElevated** — Run in user session (non-elevated, desktop, or elevated).
   * **Routing** — On success, the next task can be **OnSuccess** (explicit task id) or the next in list. On failure, the next can be **OnFailure** or the next in list; if **ContinueOnFailure** is false, the job stops. If a task is skipped, the next is always the **next in list**.
5. When the task list is done (or a task routes to “stop”), the job run **completes**. No task runs after that unless the job is triggered again.

So **jobs are launched by events (MQTT), schedule (timer), or API**. Each run executes a **sequence of tasks** with optional conditions and routing; tasks can run executables, call HTTP, or run in user context, and can publish MQTT or call the API to trigger further jobs or actions.

#### Task Execution Types (summary)

| Type             | Where it runs                     | Typical use                                                                                                 |
| ---------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Service**      | Service session (no user desktop) | Back-end executables (RedirectEvaluator, risk evaluators), scripts, built-in commands (echo, publish-mqtt). |
| **Http**         | HTTP client                       | Call local API or external URL (trigger job, launch elevated, revert settings).                             |
| **User**         | User session, non-elevated        | Process in user context.                                                                                    |
| **UserDesktop**  | User’s desktop, non-elevated      | UI apps (KeeperMFA, KeeperJustification, KeeperApproval) that need to show dialogs.                         |
| **UserElevated** | User context, elevated            | Run a process elevated in the user session.                                                                 |

***

### End-to-End Flow Example: Privilege Elevation

To tie MQTT, HTTPS, plugins, and jobs together:

1. **User** requests elevation (e.g. right-click “Run as administrator” on an app). The OS or an agent (e.g. KeeperRunAs, hook) captures this and needs a policy decision.
2. **Request to KeeperPolicy** — The component that needs the decision **publishes a message** to the **KeeperPolicy** topic (or equivalent) with context (user, machine, application, command line, event type).
3. **KeeperPolicy** (plugin) receives the message, evaluates policies, and decides **ALLOW**, **DENY**, or **PENDING** (controls required). It **publishes the response** (e.g. to a response topic or EventMessages).
4. If the result is **PENDING**, the job service (or the component that published the event) **triggers** the **PolicyEvaluationPending** event with context (ControlList, SessionId, RespondToTopic, etc.). The **privilege-elevation-policy-controls** (or similar) job **matches** and **runs**.
5. That job runs **tasks**: e.g. PendingApprovals check, then **KeeperMfa** / **KeeperJustification** / **KeeperApproval** (UserDesktop tasks), then **publish-mqtt** to send audit and, when controls succeed, **publish** a **LaunchPrivilegeElevation** event with context (FilePath, CommandLine, UserName, etc.).
6. The **LaunchPrivilegeElevation** job **matches** that event and runs. Its tasks may include **check-redirect** (reads RedirectEvaluator setting; runs RedirectEvaluator executable if redirect enabled), then either **launch-substitute** (HTTP call to launch the substitute exe elevated) or **launch-elevated** (HTTP call to launch the requested exe elevated), then **publish-mqtt** to send the final response (e.g. DidElevate or Deny) to the caller.
7. The **caller** (e.g. hook or agent) receives the response and allows or blocks the user action accordingly.

Throughout, **MQTT** carries the events and request/response messages; **HTTPS** is used for plugin settings, job triggers, and launch API calls; **plugins** are loaded from JSON and monitored; **jobs** are triggered by events (or schedule/manual) and run tasks that can invoke executables and HTTP.

***

### Summary

| Topic            | Summary                                                                                                                                                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Event-driven** | Actions are driven by **events** (MQTT messages). Policy requests, control workflows, and launch flows are all triggered by publishing to the broker; subscribers (plugins, job service) react and run logic or jobs.      |
| **MQTT**         | Embedded broker on port 8675 (localhost). Used for policy request/response, custom events (job triggers), logging, and inter-plugin messaging. Process and topic restrictions apply.                                       |
| **HTTPS**        | Kestrel on ports 6888 (HTTP) and 6889 (HTTPS), localhost only. Used for health, plugin/job management, settings, registration, launch, and other API operations.                                                           |
| **Plugins**      | Loaded from **Plugins/\*.json** at startup; started by priority; **monitored** and **auto-restarted** if configured. They connect to MQTT and optionally call the HTTP API.                                                |
| **Jobs**         | Defined in **Jobs/\*.json**; **launched** by **events** (MQTT), **schedule** (timer), or **manual** (API). Each run executes a **task list** (Service, HTTP, User, UserDesktop, UserElevated) with conditions and routing. |
