0.22.0 Metrics a scraper can actually read, Mod-Gearman without an open port, and a much smaller install¶
0.22.0 rebuilds the metrics side of the agent. /api/v2/openmetrics served the
JSON keys verbatim โ names with dots, spaces and colons in them, no types, no
help text, six significant digits โ which a strict parser rejected outright.
It now serves a conformant, self-describing OpenMetrics document: every family
named to the grammar, typed, described, carrying its unit, and per-instance
readings collapsed into one family with a core, nic or drive label
instead of one family per core. Every metric name changes, so read the upgrade
note before you upgrade a scraped host.
The second theme is a new transport. GearmanClient makes the agent a
Mod-Gearman worker: a Naemon or Nagios Core keeps scheduling its checks, the
agent pulls them off a gearmand job server and answers them as native
queries, and the monitored host needs no inbound port at all. The same module
submits passive results back into the core’s result queue. It ships marked
experimental and has not been exercised at scale: it is verified end to end
against a real job server on both cores, but no one has yet run it across a
large estate, so put it on a slice of your hosts before you move a whole
Mod-Gearman installation onto it.
Then a long tail of security work โ the low-severity tier of the audit, in three parts, plus a second undefined-behaviour sweep and the ten worst threading bugs โ and a packaging release: common code moved out of every plugin into shared libraries, Windows ARM64 is back, and there is a Raspberry Pi package.
โจ Highlights¶
- ๐ A conformant OpenMetrics exposition, and every metric name changes.
Names follow the grammar (
system.cpu.core 0.idleโsystem_cpu_idle_percent{core="0"}), every family carries# TYPE,# HELPand a# UNITwhere the value is measured in something, counters are typed as counters, string readings come back as_infofamilies, values keep their full precision and the body ends with# EOF. Per-core, per-NIC, per-drive and per-process readings become one family with a label, sosum by (core)has something to group on. The JSON endpoints, the dashboard, Graphite, collectd and Pythonsubmit_metricsreport the same keys and values as before.openmetrics format = legacyreproduces the old body as a migration window. (#1533, #1535, #1538, #1539) - ๐ฅ New module:
GearmanClient, a Mod-Gearman worker and result channel. Optional, off by default, and it speaks both flavours โ ConSol’s Mod-Gearman for Naemon and Nagios Enterprises’ Nagios-Mod-Gearman for Nagios Core 4.5+.mode = agentanswers for the host it runs on;mode = proxyruns a whole hostgroup’s checks from one box. Windows and Linux. Experimental and not yet tested at scale โ try it on a few hosts first. (#1542) - ๐ A settings source, include or attachment that is not
https://is refused. The remote store is the agent’s whole configuration, re-read at boot and on every housekeeping pass, and over plainhttp://nothing authenticates the server. Opt back in per host withallow plaintext = trueinboot.ini; every plaintext fetch is then logged asINSECURE. (#1529) - ๐ก๏ธ The audit’s low-severity tier lands, in three parts. A request may no
longer weaken a credentialed target’s transport security or re-address
submit_smtpmail; script arguments,check_docker’s endpoint and the remote-connection checks are confined; and the web server sends browser hardening headers, negotiates TLS 1.3 on Linux and stops minting a session token per request. (#1550, #1551, #1552) - ๐
check_tcpandcheck_sshverify the server certificate by default.verifydefaulted tonone, so a TLS check handshook against any certificate at all and reportedok. It now defaults topeerwithca=falling back to the agent’s own trust bundle, and the checks gained certificate identity, SAN and STARTTLS-service keywords. (#1547) - ๐ Agent-to-agent checking works, over the REST API, and the raw protobuf
web API is gone.
POST /query.pblet a caller write the header the core reads its identity from; its only consumer wasNSCPClient, which had never actually worked.check_remote_nscpand friends now go through/api/v2/queries, authenticated. (#1552) - ๐งช New modules and check commands are marked experimental. A statement
about stability, not about breakage:
nscp test, the web UI, the REST API and the reference docs all say which checks may still change their options, keywords or output. (#1543) - ๐จ The prompt and the web UI paint filter expressions. Both now read the
check’s own vocabulary and colour a
filter=,warning=ordetail-syntax=as you type it โ a keyword the check does not offer is red before the check is ever run. A newGET /api/v2/queries/{query}/helpis what the browser reads. (#1540, #1541) - โฑ๏ธ Reloads wait for the checks that are running. A settings reload holds new checks off a module while it applies the new configuration, a module can no longer unload or restart itself from inside a request it is serving, and connect and TLS handshake now count against a client’s configured timeout. (#1531)
- ๐ฆ A fifth off the Linux install, and much more off Windows. Code every plugin compiled privately moved into shared libraries, and OpenSSL ships as DLLs instead of being linked into each of NRPE, NSCA, check_mk, the web server and the HTTP clients. (#1544, #1556)
- ๐ช Windows ARM64 is packaged again, and there is a Raspberry Pi package.
NSCP-<version>-ARM64.msi/.zipare back (withoutPythonScript, and the ARM64 MSI does not bundle the VC runtime), andNSCP-<version>-debian-trixie-arm64.debcovers Raspberry Pi OS 64-bit and Debian 13 arm64. (#1546)
๐ Detailed changes¶
๐ OpenMetrics โ named, typed, described and labelled¶
The endpoint used to paste the JSON keys into the exposition verbatim. Names
carried ., %, spaces and colons (system_mem_commited.avail,
system_cpu_core 0.idle, disk_free_C:.total); there was no # TYPE and no
# EOF; values were truncated to six significant digits, so 16 GB of memory
scraped as 1.6554e+10; monotonic counts were typed as gauges, so rate()
was unsafe on them; and string readings โ uptime, boot time, MAC address,
power source โ were dropped entirely. A strict parser rejected the body, and
the scenario page told you to repair the names with metric_relabel_configs.
Three things changed, and each of them renames families.
Names follow the grammar. % becomes the word percent, everything else
outside [a-zA-Z0-9_] becomes _, runs collapse, and a name that would not
start with a letter borrows a metric_ prefix.
| JSON key | Metric name |
|---|---|
system.mem.physical.% |
system_mem_physical_percent |
system.cpu.core 0.idle |
system_cpu_core_0_idle |
disk.free.C:.total |
disk_free_C_total |
Every metric carries a description, a type and a unit. # HELP on every
built-in family, # UNIT wherever the value is measured in something โ which
renames the family again, since a family that declares a unit has to end in it
(system_mem_physical_total โ system_mem_physical_total_bytes). Counters
are typed as counters and carry the reserved _total suffix on their sample;
strings come back as an _info family in the node_uname_info shape.
Per-instance metrics are one family with a label.
# TYPE system_cpu_idle_percent gauge
system_cpu_idle_percent{core="0"} 93
system_cpu_idle_percent{core="1"} 91
system_cpu_idle_percent{core="total"} 95
The label is core, cpu, nic, zone, battery, exe, disk, drive
or pdh_instance depending on the bundle โ pdh_instance rather than
instance, because Prometheus attaches its own instance label to every
sample. Windows and Linux spell a CPU core differently in the JSON key
(core 0 and core_0); neither spelling reaches the label, which is the bare
0 on both, so one query works across a mixed fleet.
Around that:
/api/v2/metrics?meta=1serves the same keys and values with their help text, unit, type and labels under ametadataobject. Withoutmetathe endpoint is byte for byte what it was.- GraphiteClient can send the labels as carbon tags (
metric tags = true, off by default โ a carbon older than 1.1 storespath;core=0as the name). - CollectdClient mappings can read the labels and the types. A variable set
to
label:coreexpands to every value of that label instead of a regular expression over flat keys, andauto:sends whatever the producing module declared a counter as a DERIVE and everything else as a GAUGE. - Predefined PDH counters can describe themselves with
helpandunitkeys, and Pythonfetch_metricsaccepts a dict per value ({"value": 42, "help": "โฆ", "unit": "bytes", "type": "counter"}, plus"labels"). - Windows only:
system.mem.page.%andsystem.mem.physical.%report different numbers, because they were reporting the wrong thing โ both divided the commit charge by the commit limit. Alert thresholds tuned against the old reading need re-checking.
๐ฅ GearmanClient โ Mod-Gearman, either flavour¶
A Naemon or Nagios Core installation running Mod-Gearman keeps its scheduler,
its check definitions and its escalations; the agent registers for the queues
you name, pulls jobs off gearmand and answers them as native NSClient++
queries. Nothing listens on the monitored host.
| Setting | Effect |
|---|---|
mode = agent |
Answers only for the host it runs on; a job for another host_name is answered UNKNOWN |
mode = proxy |
Answers every check on the queues it registered โ one Windows box running a whole hostgroup through check_nrpe, check_wmi and the rest |
The same module submits passive results into the core’s result queue
(/settings/gearman/client, channel GEARMAN, command submit_gearman),
which is what lets a Mod-Gearman installation drop NSCA. The two halves are
independent.
Two things to unlearn when writing the check_command on the core, since
neither fails in a way that names the cause: write host=$HOSTADDRESS$, not
-H $HOSTADDRESS$ (a two-character first token puts the argument parser into
key-value mode and the check answers with a help screen), and
warning=load gt 80, not warning=load>80 (> is a metacharacter, and
allow nasty characters is false by default). See
Mod-Gearman for the full
setup on either core.
The module is marked experimental, and that mark is doing real work here:
its settings, queue handling and output may still change, and while it has been
run end to end against a real gearmand on both cores, it has not been tested
at scale โ not against hundreds of hosts, a deep job backlog, or a proxy
answering for a large hostgroup. Roll it out to a slice of the estate first and
keep the existing transport until you are satisfied. Reports of how it behaves
on a real workload are exactly what the experimental mark is asking for.
(#1542)
๐ก๏ธ Security¶
The low-severity tier of the audit landed in three parts, alongside a second undefined-behaviour sweep and the settings/web/build hardening from #1529.
Outbound clients. The request-override guard now covers the keys that
decide how a connection is protected โ verify, insecure, no-psk,
ssl, tls-version, ca, certificate, allowed-ciphers, dh โ and, for
SMTP, recipient and sender (behind their own allow recipient override).
proxy= and no-proxy= count as moving the request, since a caller-chosen
proxy receives it whole with the configured NRDP token in it.
payload-length is clamped to what each protocol accepts, 65536 for NSCA and
1 MiB for NRPE. (#1526, #1550)
Scripts and check targets. A NUL in a script argument is refused โ
CreateProcessW reads the command line as a C string, so it truncated there
and silently dropped every operator-fixed argument after the substitution
point. ext-scr add --import reads only from the script folders. A script now
receives only its own stdin and its own stdout/stderr pipe ends instead of
every inheritable handle of the service. host= on a docker check must match
the configured endpoint. And on Linux, a script section with user,
domain or password set is refused rather than run as the service account:
those keys are implemented by the Windows launcher only, so a script sandboxed
with user = nobody was not sandboxed at all. (#1523, #1551)
The web server. Every response carries X-Frame-Options: DENY,
X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer and a content
security policy, plus Strict-Transport-Security over TLS โ so a page that
embeds the agent’s UI in a frame will stop working, which is the point.
[/settings/WEB/server] gains tls version (default 1.2+) and
allowed ciphers. Logging out calls DELETE /api/v2/login, so the bearer
token stops working immediately; the in-memory log buffer is capped at 1000
entries, where an unauthenticated peer could previously add one per rejected
request for the life of the process; and nscp web install-ui no longer stages
its download in ${temp}. (#1552)
Elsewhere. The shared /settings/default/password is registered sensitive
by the core rather than by whichever server module happens to be loaded, so
every agent redacts it. Elastic and Op5 submissions log when credentials go
over an unverified https link, as the Icinga, NRDP and NRPE clients already
did โ and the Icinga warning, which repeated on every submission, is logged
once per target now. allowed hosts finally understands the * ranges it has
always advertised (192.168.1.*, 10.*, a bare *); such an entry used to
throw out of the address parser and take the whole listener with it. The
Windows release build pins every third-party action to a commit and verifies
every download against .github/dependency-checksums.txt. (#1529, #1532,
1534, #1537)¶
Fleet bundle signatures changed shape, and this one needs action. An
Ed25519 signature used to cover the bare SHA-256 digest of a bundle’s bytes,
which bound nothing about which bundle those bytes were: an old signed blob
could be re-advertised under a new id, name or version and still verify. It now
covers a canonical descriptor of the bundle’s identity, which means the fleet
server must be upgraded before โ or at the same time as โ the agents. A
mismatch is safe but inert: the agent logs signature verification failed,
keeps the configuration it last applied, and stops picking up changes. (#1536)
๐ Agent-to-agent checks over REST¶
POST /query.pb and POST /settings/query.pb are removed. The first handed
the core a message whose header the caller wrote, and the core reads the
calling module and user out of that header. The second had been unreachable for
several releases. Nothing that talks to an agent over HTTP used them: Icinga’s
check_nscp_api asks for GET /query/{name}, untouched, and the web UI uses
/api/v2.
Their only consumer was NSClient++ itself, through NSCPClient โ and that
never worked: it passed the serialized message as the HTTP request target, so
every request was malformed, and the configured password was never sent.
check_remote_nscp, remote_nscp_query and remote_nscpforward now run the
command through GET /api/v2/queries/{command}/commands/execute. If you have
an NSCP target configured:
| Setting | Was | Now |
|---|---|---|
path |
/query.pb |
/api/v2/queries |
| TLS | off unless ssl = true |
on unless no ssl = true |
password |
read, never sent | sent; the remote’s admin user needs queries.execute |
exec_remote_nscp and submit_remote_nscp are removed rather than ported:
both posted an NRPE-style string to the protobuf route, which the remote parsed
as an empty message and answered with nothing โ and the submit path reported
success regardless. Use NSCA, NRDP or another submit client for passive
results. (#1552)
๐งช Experimental modules and commands¶
A module or check command that is new enough that its options, filter keywords
and output may still change now says so: nscp test appends (experimental)
in queries, aliases, list and plugins and shows a Status: line in
desc, the web UI shows a chip, the REST API reports an experimental field,
and the reference documentation renders a marker in the command tables and a
note on the command. The recently added check_* commands of CheckDisk,
CheckDocker, CheckNet, CheckNSCP and Scheduler are marked, as are the
CheckMSSQL, CheckMySQL, CheckSecurity, CheckWindowsApps,
GearmanClient and NSCANgClient modules in full. Out-of-tree modules declare
it in module.json. (#1543)
๐จ Filter expressions, painted¶
The prompt already knew a command name from a typo. It did not know anything
about what came after the =, which is where the mistakes actually are:
filter=fre < 10% is a check that runs, matches nothing and reports OK. The
value of every option that takes a filter expression or a syntax template is
now read as what it is, following the where grammar and the placeholder rules
the engine itself uses โ a keyword the check offers is painted, one it does not
is red, before enter is pressed. Filter functions, operators and number+unit
literals each get their own colour.
The web UI’s arguments field does the same, from the same registry data:
GET /api/v2/queries/{query}/help returns one check’s whole vocabulary โ every
option with its default, flags and description, and every filter keyword it
offers. An alias declares no keywords of its own, so the endpoint follows it to
the command it stands for and says which one in keyword_source. (#1540,
1541)¶
โฑ๏ธ Threading, reloads and timeouts¶
The ten highest-severity findings of the concurrency audit are fixed, and several of them are visible from outside:
- A settings reload holds new checks off a module while its
loadModuleExapplies the new configuration, and waits up to five seconds for checks already inside it to return. A reload is no longer instantaneous on a busy agent; if a check holds a module longer than that, the reload proceeds anyway and logs which module it was. - Unloading the module that is serving the request is refused instead of taking
the agent down with it โ in practice
POST /api/v2/modules/WEBServer/commands/unload. Reloading a listener from inside a check that the same listener is serving is refused too, instead of leaving that listener dead. - Configuration downloaded over HTTP gives up on a read or write that stalls for 30 seconds, instead of waiting indefinitely, and no longer holds the settings instance lock while it downloads.
- Submissions over NRPE, NSCA, NSCP and check_mk apply the configured timeout to connecting and to the TLS handshake, not just to the exchange.
Underneath: the Lua script manager and the collector pointers are published
atomically so a reload cannot free them under a running check, the scheduler
watchdog no longer re-arms the pool during shutdown, a socket server refuses to
be stopped from one of its own threads, the Python function registry is read
under the GIL, and a CheckEventLog filter object no longer closes a handle it
does not own. (#1531, #1534)
๐ Bug fixes¶
check_ntFILEAGEchecks one file instead of a directory of them. It mapped ontocheck_files, which walks a whole tree, so a directory argument reported whichever file the walk happened to emit first โ not the oldest, not the newest, just arbitrary. It maps ontocheck_single_filenow; a directory fails with an error.- The Windows installer no longer rewrites NRPE transport security it did not
configure. The MSI recognised only
insecure = trueandverify mode = peer-cert, so a listener running TLS without client certificates, or one with a hand-written cipher string, fell through and got a preset applied over it. (#1562) nscp nrpe installwritesuse ssl, notssl = trueโ a key the server never reads. On a host whereuse ssl = falsehad been set previously, the command claimed encryption and client-certificate authentication while the listener stayed in plaintext. The legacy cipher default also drops its!ADH, which never excluded the anonymous elliptic-curve suites.- The PDH counter browser narrows on every filter and ignores case.
--list,--filterand--countershared one variable, so whichever came last won and the others were silently discarded;--filteris repeatable now. Matching is case insensitive, so--list diskfinds what--list Diskfinds. - A round-robin counter with a zero or unparseable buffer size is refused and named in the log, instead of being loaded with a buffer that holds nothing.
- A collectd value list naming a metric the snapshot does not carry is no longer sent as a zero โ it reported a measurement nobody took. Such a value list is skipped in whole, since a collectd value list is positional.
check_pingkeeps listening for its own echo reply instead of giving up on the first reply that arrives, an HTTP status line with a reason phrase parses, every line of a remote check result is kept, and a string Python cannot encode no longer crashes the agent.check_dns’shost=description said the wrong thing. (#1545)
๐ฆ Packaging¶
- Shared runtime libraries.
nscp_net.dll(sockets and TLS),nscp_client.dll(the sender modules’ shared command line),nscp_json.dlland OpenSSL aslibcrypto-3-x64.dll/libssl-3-x64.dllnow ship next tonscp.exe, one copy for the whole service, where before NRPE, NSCA, check_mk, the web server, the HTTP clients and the checksum checks each carried their own.plugin_api.dllabsorbed the settings and program-options helpers. About a fifth off the install on Debian and RedHat, and more than that on Windows. The legacy XP build still links everything statically. (#1544, #1556) - Windows ARM64 is back as
NSCP-<version>-ARM64.msiand.zip. Two differences from x64: noPythonScript(the package is cross-compiled and there is no ARM64 CPython to embed), and the MSI does not bundle the Visual C++ runtime, because Microsoft ships no ARM64 merge module for this toolset โ install vc_redist.arm64.exe first on a fresh machine. (#1546) - A Raspberry Pi OS package,
NSCP-<version>-debian-trixie-arm64.deb, built on Debian 13 for Raspberry Pi 3 and newer and for Debian 13 arm64 in general. 64-bit only, and without the managed (C#) plugin API, since Debian does not package the .NET SDK. (#1546) - The web bundle is built with
npm ciand gated onnpm audit, so the bytes in the web zip and in the MSI’sweb/distmatchpackage-lock.json. Pull requests build only the newest RedHat, and a failed dependency download fails at the download instead of somewhere later.
โ ๏ธ Upgrade notes¶
- Every OpenMetrics family name changes. Drop any
metric_relabel_configsblock that rewrote dots to underscores โ the agent does that itself now. Update dashboards, recording rules and alerts:system_cpu_core 0.idleissystem_cpu_idle_percent{core="0"},disk_free_C:.totalisdisk_free_total_bytes{drive="C:"},workers_jobsisworkers_jobs_total. Excludecore="total"from anything that aggregates over cores. If dashboards cannot be updated first, setopenmetrics format = legacyunder[/settings/WEB/server]as a migration window โ it is deprecated and will be removed. - Upgrade the fleet server before or with the agents. Bundle signatures now cover the bundle’s identity, not only its bytes. A version mismatch is inert rather than damaging: the agent keeps the configuration it last applied and stops picking up changes.
- A settings source, include or attachment over plain
http://is refused. Move it tohttps://, or setallow plaintext = trueunder[tls]inboot.iniper host. check_tcp/check_sshagainst an internal or self-signed service now fail withtls_handshake_failed. Pointca=at the issuing CA or addverify=none.sni=on a non-TLS connection is now rejected rather than ignored.POST /query.pbis gone. If you have anNSCPclient target, remove an explicitpathor point it at/api/v2/queries, expect TLS unless you setno ssl = true, and give the remote’sadminuser thequeries.executegrant.exec_remote_nscpandsubmit_remote_nscpare removed.- Requests may no longer weaken a credentialed target’s transport security
(
verify,insecure,ssl,ca,tls-version,proxy, โฆ) or re-addresssubmit_smtpmail. Supply the credentials with the request, configure the variant as its own target, or setallow host override = true/allow recipient override = true. - External scripts with
user,domainorpasswordset are refused on Linux โ those keys were never implemented there. Put the identity change in the command itself withsudo. - The web UI cannot be framed any more, and
check_nt FILEAGEnaming a directory now fails instead of reporting an arbitrary file’s age. - A hand-rolled deployment must copy the new shared libraries from the
installation root alongside
modules\*.dll, the OpenSSL DLLs included. Installing from the MSI or the Debian/RedHat packages needs nothing. - On Windows ARM64, install the VC++ ARM64 redistributable before the agent
on a fresh machine, and use the x64 package under emulation if you need
PythonScript.
Security notices for this release are on the security notices page, and the full list of behaviour changes is on the upgrading page.
Download¶
You can download the new version from GitHub
// Michael Medin