Skip to main content

Other Wazuh products on your hosts

The connector is already on your hosts. A service uses that same host to run another Wazuh product: its binary is installed beside the connector, under its own system account, and it talks to its own product directly. Nobody opens a shell on the machine. Fleet decides which products may do that on each host. The product does the installing, from its own console.

Which products can do this is compiled into the connector rather than read off the network, and each entry names one product's own plugin download site and its own enrolment address. Wazuh Vesper's plugin comes from dl.vesper.wazuh.com and enrols at connect.vesper.wazuh.com; no other site may serve either, for that product or for any other. The products with an entry today are Wazuh Vesper, Wazuh Mobius, which carries a second entry for a separate development deployment, and Wazuh Pharos. The mechanism is the same for each of them: a service appears in Fleet's Services panel once it has a plugin release published.

A Wazuh Cloud environment has the panel once the connector Wazuh Cloud deployed for it reports it can host services, and one sentence in its place until then. An install requested for it before that, from Fleet or from another product's console, is refused with the same sentence. Wazuh Vesper is not available on Wazuh Cloud environments, by design, and the root shell control below is never offered there.

What a plugin is​

A separate program, in its own process, under its own account (fleet-plugin-<service>), started by systemd from one template unit, fleet-plugin@<service>.service, with its own journal. The connector gives it two files and nothing over the network: targets.yaml (also written as targets.json), carrying the host's name and platform and whether a root shell was granted, and bind/token.json, once. The only thing about Wazuh targets may carry is the login the installer found on the host, when it found one. See The Wazuh login the installer finds. Otherwise a plugin gets the login it needs from its own service, with its enrolment token, and the connector never sees that one. A login set from the service replaces the one the installer found.

It gets no Fleet credential, no gateway address and no query proxy, and its traffic never passes through Fleet. So Fleet answers one question about a plugin: is this process alive and enrolled on this host. Whether the product is working is that product's own console, and the two disagreeing is a real state rather than a bug.

The unit carries the isolation: ProtectSystem=strict, NoNewPrivileges, IPv4 and IPv6 sockets only, an empty CapabilityBoundingSet, and memory, task and CPU caps per instance. More than ten starts inside 300 seconds parks a crash-looping plugin rather than letting it spin.

How one gets onto a host​

  1. Somebody allows the service on the host, in Fleet, on the host's Services panel. Nothing is installed by that click.
  2. Somebody clicks Connect in the other service's console and picks a Fleet host. That service calls Fleet, which checks the acting person is a member of the workspace and that the service is allowed on that host, resolves the targeted hosts, takes custody of the credential and records the binding. Nothing has happened on any host yet. A service that is not allowed is refused with one sentence, which its console can show before the click: Vesper is not allowed on this host. Allow it in Fleet, on the host's Services panel, then connect again.
  3. Each host finds out on its next heartbeat, 30 seconds apart. The connector downloads the plugin from that service's own download site, verifies it and writes a request file. The download host is compiled into the connector and re-checked on every redirect hop, never read from the message.
  4. fleet-connector-reconcile, root and with no network, installs it, smoke tests the candidate as the plugin's own account rather than as root (through setpriv, or runuser where that is what the host has), flips the current symlink and starts the unit.
  5. The plugin reads targets.yaml and its token, enrols with its own service, deletes the token once that enrolment succeeds, and writes a status file every 30 seconds.

The connector daemon cannot do step 4: its unit cannot reach systemd, create an account or write /usr/local. Installing plugins never widens the sandbox of the internet-facing process. A host where the installer found no useradd has no plugin accounts, and says so.

Fleet's own Services panel installs nothing. Its Allow button is the permission the other product's Connect needs, and its Disallow button takes that permission back.

The credential that service needs​

The enrolment token belongs to that service, and Fleet is carrying it to a host that may be rebooting. It is encrypted before storage, bound to the workspace it was sent for, so a copy lifted out of one workspace's row does not decrypt for another. No path keeps it in the clear: with no key available the call is refused with 503 rather than stored. Fleet holds it for at most an hour. A service sends either one token every targeted host shares or one per host, and has to say which: a single-use key fanned out to a cluster enrols one node and is refused by the rest, silently.

On the host it is 0600, owned by the plugin's account, and delivered once: the connector keeps a root-owned marker of the hand-over, because the plugin deletes the token after a successful enrolment and its absence cannot mean "not delivered yet".

The Services panel​

On the host's page, the panel puts two things side by side and never merges them: what Fleet allows, per service, and what the host says. A service allowed an hour ago that its product has not connected yet is the case somebody has to see, and one status word for it would have to lie one way or the other.

The permission is one of four words, with who changed it and when:

Fleet saysWhat it means
Not allowedThe default. The product cannot install here until somebody allows it.
AllowedSomebody allowed it. Nothing is installed until the product connects from its own console.
InstalledThe product connected and its plugin is on the hosts.
DisabledSomebody disallowed an installed service. Its plugin is stopped on every host and everything is kept. Allowing it again starts it with no reinstall.

Then one row per host:

Host saysWhat it means
InstalledThe version, and when that host last reported.
behind vX.Y.ZAn older build. While the release is still rolling out, the host keeps it until its ring's wave reaches it. At 100 % the install is being retried on that host, with the host's own report beside it. Under a halted release the host keeps its version until the halt is lifted.
Waiting for its binaryPrepared here, nothing installed yet.
Not installedNo binary here. Pending when the product has asked for it.
Installed, disabledStopped on Fleet's Disallow. Its identity and credentials stay, and allowing the service again starts it.
RemovedStopped and disabled, and its credentials are gone. The product installs the current release again when it connects.
RefusedThe host tried the last act and could not, with the reason beside it.
Contract mismatchThe candidate does not speak the plugin contract this connector reads, so it was not enabled.

A host that does not report which change it has applied is counted apart and named, never called behind: "it cannot say" is not "it is out of date". The panel also names the hosts a plugin must reach on 443, for whoever runs the firewall.

Health is not here. It is in the Host details card above, from a different call, so the two panels can never disagree about whether a plugin is running.

Allow, Disallow, and what each one does​

Fleet allows, and the product installs. Allow is a permission for one service on one host. It installs nothing, and it needs no plugin release to exist. Until it is given, a Connect from that product's console is refused with the sentence above, and the product's own console can show the same sentence greyed before the click. A per-host grant is permission to open a host, never permission to allow software on it, and a call from another product's console is not a way around that. A Connect needs the acting person to be a member of the Fleet workspace, so Fleet records who asked.

Disallow takes the permission back. On a service whose plugin is installed, it stops and disables that plugin on every host on their next heartbeat, and it deletes nothing: the binary, the identity and the credentials stay, and the binding the product holds stays with them. Allowing the service again starts the plugin with no download and no new enrolment. Disallow is never refused, wherever the host runs.

Two acts sit beside the permission and neither changes it. Disconnect, on a service its product bound, drops the credential Fleet holds, asks every host to delete the plugin's identity and credentials and tells that product, saying plainly when that message could not be delivered. The product's own console can do the same from its side, on membership alone: installing escalates, disconnecting de-escalates, and refusing would leave a plugin installed and running with no work for ever. Remove, on a plugin nothing bound, does the same on the hosts and tells nobody. Removing the host uninstalls nothing on the machine: only the connector's own uninstall.sh does that.

What the machine owner still decides​

A plugin's Wazuh login is asked for in that product's own console and reaches the plugin with its enrolment token, or it is the one the installer found on the host, which a login set from the product replaces. The daemon discovers no login of its own. The one file the machine owner keeps, /etc/fleet-connector/plugins/<service>.yaml, decides a single thing: whether that plugin's unit runs with its sandbox off.

An indexer or wazuh_api section left in that file by an earlier connector is not read. It is not ignored in silence either: the plugin is told, in its status, credentials are no longer read from this file: <service> receives them from its own service, or starts with the login the installer found on this host; remove the indexer/wazuh_api sections, and that sentence reaches the console. A file that cannot be parsed grants nothing and says why.

upgrade.plugins_enabled: false in /etc/fleet-connector/connector.yaml is the other local veto: the host keeps updating its core, and logs and ignores every plugin instruction.

De-sandboxing a plugin is a grant, and it has exactly two sources

Running a plugin's unit as root with its sandbox off is a grant, not a setting, and two independent things can make it. Either is enough on its own.

The machine owner, with one key, exec.enabled: true, in that same per-service file on the host. Deleting the key takes that grant back.

A workspace admin, with the Root shell control in the Services panel. It is granted per host, it appears only beside a service whose plugin advertises the capability, which today is Wazuh Vesper alone, and only once that plugin is installed: a service that is not installed says Allow <service> and connect it from <Service> first. A root shell is granted to a plugin that runs. and a service whose plugin does not advertise a shell has no control at all rather than one that would fail. Only an admin sees it, which is the same rule as installing a plugin and for a heavier reason. Granting is recorded and the panel says so, naming who granted it and when; that record is kept apart from who installed the plugin, so the two can never be read as each other.

The two sources are OR'd. A machine owner keeps their own say, and silence from Fleet never revokes their file. A grant Fleet made is taken back only by an explicit revocation or by removing the plugin, never by a quieter answer. On the host, the connector's journal says which of the two allowed it, and the plugin is told the same in its own targets.json.

The shape of the act is the same whichever source grants it. The reconcile request carries nothing but the service name, so there is no payload for anything on the wire to be. The content of the drop-in at /etc/systemd/system/fleet-plugin@<service>.service.d/50-fleet-exec.conf is a constant in the connector binary, so neither source decides what it says. The recorded grant is 0600 and root-owned, in a directory only root can write, so a plugin running under its own account cannot write itself one, and the authority behind such a grant is the authenticated session that carried it rather than anything on the host. Every read fails closed: a file that is missing, unreadable or unparseable grants nothing. And it de-sandboxes that one unit: the resource limits stay, and the core and every other plugin on the host keep their own sandboxes.

Reading a plugin's health​

The plugin writes a status file every 30 seconds. The connector forwards it verbatim and adds three things: whether it is alive, a refusal when the document is too large, and the reason when there is none.

Once a plugin is installed and its unit has started, its reading is one of these. Before that the row carries the same install-state words the Services panel uses (Not installed, Waiting for its binary, Installed, disabled, Removed, Refused, Contract mismatch), because until the reconciler has enabled it there is no health to report.

ReadingWhat it means
RunningA fresh status. A second chip carries the plugin's own word for itself: starting, unbound, enrolling, bound, degraded, stopping or error.
Running, degradedAlive, and listing what it cannot serve, one sentence each.
Not reportingNo status for more than three of its own intervals.
Crash-loopingsystemd has had to restart it and it is not reporting.
Unit failedsystemd calls the unit failed. Nothing is restarting it.
Contract mismatchAlive, and the connector says the plugin writes a plugin contract this connector does not read. The plugin cannot see what the connector writes for it until one of the two is updated.
Could not tellFleet has no readable status. Health is unknown, not bad.
Grey is not red

Could not tell is the console declining to guess, and is never painted as a failure. A plugin whose last status says stopping lands there too: that document was written by a process on its way out, so it says nothing about what is running now, and a legitimate restart must not flash red.

What turns grey into red is somebody else's observation: the reconciler records what systemd said about the unit and how often it has had to restart it, shown with the age of that observation, and an absent count is never rendered as zero restarts. A plugin that is not alive also carries a journal excerpt: its last 20 lines, and never more than 4 KiB of them.

On the host, journalctl -u fleet-plugin@<service> covers one plugin and sudo fleet-connector status prints what current points at for each. See Connector logs for the reconciler and confirm units, which log every install and rollback of a plugin. Hosts differ mid-rollout on purpose: see connector lifecycle.