Metadata-Version: 2.4
Name: netbox-checkmk
Version: 0.4.2
Summary: NetBox plugin showing live Checkmk monitoring status on devices, VMs and clusters
Author-email: Christian Wirtz <doc@snowheaven.de>
License-Expression: Apache-2.0
Project-URL: Homepage, https://gitlab.com/checkmk1/netbox_checkmk_plugin
Project-URL: Source, https://gitlab.com/checkmk1/netbox_checkmk_plugin
Project-URL: Changelog, https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/blob/main/CHANGELOG.md
Keywords: netbox,netbox-plugin,checkmk,monitoring
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: System Administrators
Classifier: Natural Language :: English
Classifier: Natural Language :: German
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Networking :: Monitoring
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: requests
Requires-Dist: cryptography
Dynamic: license-file

# netbox-checkmk

NetBox plugin that shows live **Checkmk monitoring state** on devices, virtual machines and
clusters, in a dedicated **"Checkmk" tab** on each object's detail page.

![Checkmk tab of a device](https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/raw/main/docs/screenshots/device-tab.png)

- **Tab label badges:** the "Checkmk" tab shows the number of OK / WARN / CRIT / UNKNOWN
  services as number-only badges in the state's color (zero counts hidden), plus a DOWN /
  UNREACH badge when the host is down. They are visible from every tab of the object.
- **Device / Virtual machine tab:** host state (UP/DOWN/UNREACH/PEND), acknowledgement and
  downtime flags, plugin output, service state counts, last check time, and the full service
  list (worst state first) with each service's output. The host and every service link straight
  to their views in Checkmk.
- **Cluster tab:** overall (worst) state across all member devices and VMs, the cluster's own
  Checkmk host (if one exists with that name), and a member table sorted worst-first. The tab
  label badges show the service counts summed over all members.
- **List column:** an optional "Checkmk" column for the device, virtual machine and cluster
  lists (host state + service counts), enabled per user via *Configure Table*.
- **English and German:** the UI follows the NetBox user's language setting.
- **Checkmk sites in the NetBox UI or the configuration file, or both:** sites can be managed
  under *Plugins → Checkmk sites* (secret stored encrypted, connection test button) and/or in
  `PLUGINS_CONFIG`. They are queried in order; the first site that knows a host wins.
- **Non-blocking:** the tab loads its content asynchronously (htmx), so a slow or unreachable
  Checkmk site never delays the NetBox page. State is fetched from Checkmk at most every
  `check_interval` seconds (default 300 = 5 min), and an open tab reloads itself at that
  interval. The tab's *Refresh* button bypasses the cache.
- **Read-only:** the plugin only reads monitoring status from the Checkmk REST API.
- **Standard plugin APIs only:** an extra tab via `register_model_view`/`ViewTab`, the list
  column via `register_table_column`, and the plugin's own model, views, REST API and menu
  entry. NetBox's code and templates are not touched.

![Checkmk tab of a cluster](https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/raw/main/docs/screenshots/cluster-tab.png)

![Checkmk column in the device list](https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/raw/main/docs/screenshots/device-list.png)

![Checkmk sites managed in the UI](https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/raw/main/docs/screenshots/site-list.png)

![A Checkmk site with its connection test](https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/raw/main/docs/screenshots/site-detail.png)

The screenshots are generated by the integration test against a throwaway Checkmk site with
demo services: `SCREENSHOT_DIR=docs/screenshots NETBOX_VERSIONS=v4.7.2 scripts/integration_test.sh`.
That mode needs `python3` on the machine running the script.

## How it works

The plugin reads monitoring state from the **Checkmk REST API** (`/check_mk/api/1.0`), always
authenticated as a Checkmk user. It never talks to Livestatus directly. Only the NetBox server
contacts Checkmk; the browser only talks to NetBox and gets HTML back.

```mermaid
flowchart TD
    A([User opens a device, VM or cluster in NetBox]) --> B[NetBox renders the page<br/>with the Checkmk tab and an empty placeholder]
    B --> C[Browser requests the tab content<br/>from the plugin via htmx]
    C --> D{Logged in and allowed<br/>to view this object?}
    D -- no --> D1([403/404 or login page])
    D -- yes --> E[Determine Checkmk host name:<br/>custom field checkmk_hostname,<br/>otherwise the object name]
    E --> F{Result in the cache<br/>and younger than check_interval?}
    F -- yes --> R
    F -- no --> G[Load sites: configuration file first,<br/>then enabled UI sites by weight]
    G --> H{Any site<br/>left to ask?}
    H -- no --> N[Host not found:<br/>cache result if no site failed]
    H -- yes --> I[HTTPS request to the site's REST API<br/>Authorization: Bearer user secret]
    I --> J{Response}
    J -- "401 / timeout / error" --> K[Remember the error for the tab] --> H
    J -- "200, host not in the result" --> H
    J -- "200, host found" --> L[Cache host state and service counts<br/>for check_interval]
    L --> M[Device/VM: fetch the service list<br/>of the host via the REST API, cached]
    M --> R[Render state, badges and services<br/>with links into Checkmk]
    N --> R
    R --> S([Browser shows the tab;<br/>it reloads itself every check_interval])
```

*The diagram is not rendered on PyPI. See it in the
[README on GitLab](https://gitlab.com/checkmk1/netbox_checkmk_plugin#how-it-works).*

Cluster tabs work the same way, with one lookup for all member hosts (up to 50 names per
request). The badges on the tab label and the list column use the same cached host lookup.

## Host name mapping

A NetBox object is matched to the Checkmk host with the same name, **case-insensitively**
(`NB-VM1` in NetBox finds `nb-vm1` in Checkmk). If the names differ, set the custom field
**Checkmk host name** (`checkmk_hostname`) on the object; its value is then used instead of the
object name.

The plugin creates this text custom field for devices, virtual machines and clusters on
`manage.py migrate` if it doesn't exist yet. An existing field with that name is never changed.
To name it differently, set `hostname_custom_field`; to create it yourself, set
`create_custom_field: False`.

## Requirements

- NetBox 4.3 or newer
- Checkmk 2.5 or 3.0 (REST API), with a user per site for NetBox (see below).

## Dependencies

| Package | Why | Notes |
| --- | --- | --- |
| [`requests`](https://pypi.org/project/requests/) | HTTP calls to the Checkmk REST API | Already part of every NetBox installation |
| [`cryptography`](https://pypi.org/project/cryptography/) | Encrypting the secrets of sites managed in the UI | Already part of every NetBox installation |

So installing the plugin adds no further packages. It brings one database table (the Checkmk
sites managed in the UI), no static files and no JavaScript of its own: the asynchronous loading
uses the htmx library that NetBox already ships.

## Checkmk user for NetBox

The plugin only reads, so a dedicated **automation user with the built-in "Guest user" role** is
all it needs. That role may see all hosts and services, and can't change anything. Don't use a
personal account: its password ends up in NetBox's configuration.

In each Checkmk site:

1. *Setup → Users → Users → Add user*
2. **Username** e.g. `netbox`, **Full name** e.g. `NetBox`
3. **Authentication:** *Automation secret for machine accounts*, then generate a secret and copy it
4. **Roles:** only *Guest user*; no contact groups, no notifications needed
5. Save and *activate changes*

Or with the REST API, as an admin. This is the call the integration test uses:

```sh
curl -u 'cmkadmin:<password>' -X POST -H 'Content-Type: application/json' \
  https://cmk.example.com/mysite/check_mk/api/1.0/domain-types/user_config/collections/all \
  -d '{"username": "netbox", "fullname": "NetBox", "roles": ["guest"],
       "auth_option": {"auth_type": "automation", "secret": "<long random secret>"}}'
```

Then enter `username` and `secret` for the site, either in the UI under *Plugins → Checkmk sites*
or in the configuration file (see *Configuration* below).
Check it from the NetBox host:

```sh
curl -s -H 'Authorization: Bearer netbox <secret>' \
  https://cmk.example.com/mysite/check_mk/api/1.0/version
```

## Installation

The plugin is on PyPI as [`netbox-checkmk`](https://pypi.org/project/netbox-checkmk/). Each
GitLab release also carries the wheel. The plugin has one database table (the Checkmk sites
managed in the UI) and no static files, so `manage.py migrate` is required after installing and
after every update. That run also creates the custom field.

### Classic installation (`/opt/netbox`, systemd)

These steps were used on a real NetBox 4.7.1 installation (Debian 13, `/opt/netbox`, services
`netbox` and `netbox-rq`):

```sh
# 1. Back up the configuration
cp -a /opt/netbox/netbox/netbox/configuration.py /root/configuration.py.bak-$(date +%F)

# 2. Install it into NetBox's venv
/opt/netbox/venv/bin/pip install netbox-checkmk

# 3. So that NetBox's own upgrade.sh reinstalls the plugin on NetBox upgrades
echo netbox-checkmk >> /opt/netbox/local_requirements.txt
```

4. In `/opt/netbox/netbox/netbox/configuration.py`, add `"netbox_checkmk"` to `PLUGINS`.
   Checkmk sites can go into `PLUGINS_CONFIG` here as well (see *Configuration* below), or be
   added in the UI after step 6. Without any site the plugin still loads; its tab then says
   that no site is configured.

```sh
# 5. Validate, create the plugin's table and custom field, restart, check
cd /opt/netbox/netbox && runuser -u netbox -- /opt/netbox/venv/bin/python manage.py check
runuser -u netbox -- /opt/netbox/venv/bin/python manage.py migrate
systemctl restart netbox netbox-rq
systemctl is-active netbox netbox-rq
```

6. Open a device or VM: the tab bar now has a **Checkmk** tab. If no site is configured in the
   file yet, add one under *Plugins → Checkmk sites → Add* and click *Test connection* on it.

To update: `/opt/netbox/venv/bin/pip install -U netbox-checkmk`, then run `migrate` and restart
both services as in step 5.

To uninstall: delete the plugin's table first, while the plugin is still installed and enabled
(this also deletes all sites managed in the UI), then remove it:

```sh
cd /opt/netbox/netbox && runuser -u netbox -- /opt/netbox/venv/bin/python manage.py migrate netbox_checkmk zero
/opt/netbox/venv/bin/pip uninstall netbox-checkmk
```

Then remove `"netbox_checkmk"` from `PLUGINS` (and its `PLUGINS_CONFIG` entry and the
`local_requirements.txt` line), and restart both services. The custom field `checkmk_hostname`
stays; delete it under *Customization → Custom Fields* if you don't need it anymore.

Without internet access on the NetBox host, copy the wheel there (e.g. to
`/opt/netbox/local_plugins/`), `pip install` that file, and put its path into
`local_requirements.txt` instead of the package name.

### netbox-docker

Build a custom image with the plugin. `plugin_requirements.txt`:

```text
netbox-checkmk
```

`Dockerfile-Plugins`:

```dockerfile
FROM netboxcommunity/netbox:v4.7.2
COPY ./plugin_requirements.txt /opt/netbox/
RUN /usr/local/bin/uv pip install --python /opt/netbox/venv/bin/python \
    -r /opt/netbox/plugin_requirements.txt
```

`docker-compose.override.yml`:

```yaml
services:
  netbox:
    image: netbox:latest-plugins
    pull_policy: never
    build:
      context: .
      dockerfile: Dockerfile-Plugins
  netbox-worker:
    image: netbox:latest-plugins
    pull_policy: never
  netbox-housekeeping:
    image: netbox:latest-plugins
    pull_policy: never
```

Put the configuration into `configuration/plugins.py`, then run
`docker compose build --no-cache && docker compose up -d`.

The `Dockerfile-Plugins` build was verified (the plugin installs into the image), and the
integration test runs the plugin in the same image with its config in `plugins.py`. The full
`docker compose` stack itself has not been run.

## Configuration

Checkmk sites can be configured in two places, and both can be used together:

| | NetBox UI (*Plugins → Checkmk sites*) | Configuration file (`PLUGINS_CONFIG`) |
| --- | --- | --- |
| Managed by | users with the `netbox_checkmk` permissions | whoever edits `configuration.py` (or automation) |
| Secret | stored encrypted in the database, never shown again | in the file (see below for keeping it out) |
| Takes effect | immediately | after restarting NetBox |
| Query order | after the file sites, by *weight* | first, in list order |

Sites from the file are shown read-only under *Plugins → Checkmk sites*. A UI site can't take the
name of a file site. Each site has a *Test connection* button.

### Sites in the NetBox UI

*Plugins → Checkmk sites → Add*: name, URL (site URL without `/check_mk`, e.g.
`https://cmk.example.com/mysite`), username and secret of the Checkmk user, plus optional TLS
verification, timeout, weight and an *enabled* switch. When editing, leave the secret empty to
keep the stored one.

How the secret is protected:

- It is encrypted (Fernet: AES-128 + HMAC) with a key derived from NetBox's `SECRET_KEY`.
- It is never displayed, exported, written to the change log, or returned by the REST API
  (`/api/plugins/checkmk/sites/`; the field `secret` can be written there, but never read).
- If NetBox's `SECRET_KEY` changes, stored secrets can't be decrypted anymore. The tab then names
  the affected site, and the secret has to be entered again.
- Only users with the matching object permissions (`netbox_checkmk.*_checkmksite`, typically
  admins) can see or change the sites.

### Settings in the configuration file

All settings, including sites; every key is optional:

```python
PLUGINS = ["netbox_checkmk"]

PLUGINS_CONFIG = {
    "netbox_checkmk": {
        "sites": [                                          # sites in addition to the UI ones
            {
                "name": "main",                             # label shown in NetBox
                "url": "https://cmk.example.com/mysite",    # site URL, without /check_mk
                "username": "netbox",                       # automation user
                "secret": "…",                              # its automation secret
                "verify_ssl": True,                         # optional, default True
                "timeout": 5,                               # optional, seconds
            },
            # further sites …
        ],
        "check_interval": 300,       # seconds between Checkmk queries (cache + tab auto-reload)
        "hostname_custom_field": "checkmk_hostname",
        "create_custom_field": True,  # create it on `manage.py migrate` if missing
        "max_services": 500,         # services listed on a device/VM tab
        "max_cluster_members": 100,  # members evaluated on a cluster tab
    },
}
```

To keep the secret out of `configuration.py`, see *Keeping the secret out of the configuration*
below.

Before restarting, check that the NetBox host can reach each Checkmk site:

```sh
curl -s -u '<user>:<secret>' https://cmk.example.com/mysite/check_mk/api/1.0/version
```

Permissions follow NetBox: users only see the tab, and its data, for objects they are allowed
to view.

## Keeping the secret out of the configuration

The simplest way is to add the site in the NetBox UI instead: its secret is then stored
encrypted in the database (see *Sites in the NetBox UI*). For sites in the configuration file:

`configuration.py` is plain Python, so the Checkmk secret doesn't have to be written into it. On
many installations that file is readable by every local user, and it often ends up in backups or
version control. Pick one of these:

**Separate secret file** (simplest). Only root and the `netbox` user, which NetBox runs as, can
read it:

```sh
install -m 640 -o root -g netbox /dev/null /etc/netbox/checkmk_secret
printf '%s' '<secret>' > /etc/netbox/checkmk_secret
```

```python
from pathlib import Path

PLUGINS_CONFIG = {
    "netbox_checkmk": {
        "sites": [
            {
                "name": "main",
                "url": "https://cmk.example.com/mysite",
                "username": "netbox",
                "secret": Path("/etc/netbox/checkmk_secret").read_text().strip(),
            },
        ],
    },
}
```

**Environment variable via systemd.** Put `CHECKMK_SECRET=<secret>` into
`/etc/netbox/checkmk.env` (`chmod 600`), then add a drop-in for **both** `netbox` and `netbox-rq`
(`systemctl edit netbox`, then `systemctl edit netbox-rq`):

```ini
[Service]
EnvironmentFile=/etc/netbox/checkmk.env
```

In the configuration: `"secret": os.environ["CHECKMK_SECRET"]` (with `import os`). Commands
you run by hand, such as `manage.py`, don't get that variable, so the configuration fails to
load there unless you export it first.

**systemd credentials** (systemd 247 or newer). The secret is only handed to the running service.
Drop-in for both services:

```ini
[Service]
LoadCredential=checkmk_secret:/etc/netbox/checkmk_secret
```

```python
import os
from pathlib import Path

"secret": Path(os.environ["CREDENTIALS_DIRECTORY"], "checkmk_secret").read_text().strip(),
```

The same caveat applies to `manage.py` as with the environment variable.

**netbox-docker: Docker secret.** Define a secret `checkmk_secret` in
`docker-compose.override.yml` for the `netbox` and `netbox-worker` services. Docker mounts it
at `/run/secrets/checkmk_secret`, so in `configuration/plugins.py`:

```python
from pathlib import Path

"secret": Path("/run/secrets/checkmk_secret").read_text().strip(),
```

Independent of where the secret lives:

- **Use a read-only Checkmk user** (see *Checkmk user for NetBox*). Even if the secret leaks, it
  can't change anything in Checkmk.
- **Restrict `configuration.py` itself:** it also holds NetBox's database password and
  `SECRET_KEY`. `chown root:netbox` and `chmod 640` keep it readable for NetBox but not for other
  local users.

## Authentication and security

**How NetBox authenticates against Checkmk**

- Only the Checkmk **REST API** is used, over HTTP(S) to the configured site URL, with the header
  `Authorization: Bearer <username> <secret>` on every request. Checkmk checks the user, and only
  then queries its monitoring core, with that user's permissions. Wrong credentials give
  `HTTP 401` and no data.
- **No Livestatus.** Livestatus over TCP (port 6557) has no user authentication: whoever reaches
  the port can read everything and even send commands. The plugin doesn't need it, so it can
  stay disabled.
- **Read-only.** The plugin only calls these endpoints:
  - `POST …/domain-types/host/collections/all`: host state and service counts. POST is used
    because Checkmk 3.0 removed the GET variant; it doesn't change anything.
  - `POST …/domain-types/service/collections/all`: the service list of a host.
  - `GET …/version`: only for the *Test connection* button.

  It never acknowledges, schedules downtimes or changes configuration.

**Recommended setup**

- A dedicated automation user per site with only the built-in **Guest user** role (see *Checkmk
  user for NetBox*). It can see all hosts and services, and can't change anything, even if its
  secret leaks.
- Use `https://` site URLs and keep `verify_ssl` on (the default). Turn it off only for test
  sites with self-signed certificates.
- The NetBox server needs outbound access to each Checkmk site, nothing more. Checkmk never
  connects to NetBox.

**Where the secret is stored and who can see it**

- **UI sites:** encrypted in the NetBox database (Fernet, key derived from NetBox's
  `SECRET_KEY`). The secret is never shown again, exported, written to the change log, or
  returned by the REST API, where it can only be written.
- **File sites:** in `configuration.py`, unless you load it from a separate file, an environment
  variable or a Docker secret (see *Keeping the secret out of the configuration*).
- The secret never reaches the browser. Links into Checkmk (host, services) open the normal
  Checkmk UI, where users log in with their own Checkmk account.

**Who sees what in NetBox**

- The Checkmk tab, its badges and the list column only show data for objects the user is allowed
  to view in NetBox. For other objects the tab answers with 403 and the plugin's data views with
  404.
- Checkmk sites in the UI follow NetBox's object permissions (`netbox_checkmk.view_checkmksite`,
  `add_…`, `change_…`, `delete_…`). The connection test of file sites requires
  `view_checkmksite`.
- Monitoring data (states, plugin output) is cached in NetBox's cache (Redis) for
  `check_interval`. Plugin output can contain details about your systems; anyone allowed to view
  an object in NetBox sees its Checkmk output. Grant object permissions accordingly.

## Behaviour on errors

If a site cannot be queried (unreachable, wrong credentials, …), the tab shows a warning naming
the site. Hosts that were not found on any reachable site are then reported as "could not be
determined" rather than "not found", and that result is not cached.

## Development

```sh
pip install ruff pytest requests
ruff check . && ruff format --check . && pytest -q          # unit tests (no NetBox needed)

# After editing netbox_checkmk/locale/de/LC_MESSAGES/django.po (no GNU gettext needed):
python scripts/compile_translations.py

# Integration test: real Checkmk site + NetBox per version, all in Docker
CHECKMK_VERSION=2.5.0p15 NETBOX_VERSIONS="v4.3.7 v4.7.2" scripts/integration_test.sh   # default: 4.3 … 4.7
```

The integration test creates hosts in a throwaway Checkmk site and sets their states with
Livestatus commands inside that Checkmk container (test setup only; the plugin itself uses the
REST API). Then it checks the tabs and status content through NetBox's own Django stack:
automatic custom field creation, case-insensitive matching, the custom field override, cluster
aggregation, tab label badges, the list column, host and service links, the German translation,
Checkmk sites in the UI (encryption, change log, REST API, connection test), multi-site fallback
with a broken site, caching, and permissions. It uses `docker create`/`cp`/`exec` only (no bind mounts, no published ports), so it also works
against a remote Docker daemon (`DOCKER_CONTEXT`/`DOCKER_HOST`).

CI runs lint and unit tests on every push. The integration test, package build and GitLab
release run on tag pushes (`vX.Y.Z`, which must match `__version__`).

## Tested with

Plugin version 0.4.0, integration test (`scripts/integration_test.sh`), 2026-10-12:

| Checkmk | NetBox `v4.3.7` | `v4.4.10` | `v4.5.10` | `v4.6.10` | `v4.7.2` |
| --- | --- | --- | --- | --- | --- |
| `2.5.0p15` (check-mk-ultimate) | passed | passed | passed | passed | passed |
| `3.0.0-2026.10.12` (check-mk-ultimate daily) | passed | passed | passed | passed | passed |

Also running on a real installation: NetBox `4.7.1` (classic install, Debian 13) with Checkmk
`2.5.0p15`. Version 0.1.0 was installed by hand via the classic path above, and later versions
were rolled out automatically with the same steps.

## License

Copyright 2026 Christian Wirtz. Licensed under the [Apache License 2.0](https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/blob/main/LICENSE). Forks and
redistributions must keep the [NOTICE](https://gitlab.com/checkmk1/netbox_checkmk_plugin/-/blob/main/NOTICE) file (section 4(d) of the license).

*NetBox is a trademark of NetBox Labs, Inc. Checkmk is a trademark of Checkmk GmbH. This project
is an unofficial, independent community project and is not affiliated with or endorsed by
NetBox Labs or Checkmk GmbH.*
