Proxmox VE
The hypervisor is the one system that knows, for every guest it runs, which
virtual NIC it gave it and — with the guest agent — which addresses that NIC
carries right now. The proxmox connector reads a Proxmox VE cluster (or a
single node) through its REST API: the guest name becomes the device name
(name_source: hypervisor, via hypervisor, directly after NetBox), the
MAC↔IP binding of every virtual NIC reaches the identity model as anchors, and
the device detail shows VMID, node, type, status, tags, pool and operating
system with a link into the Proxmox interface.
It is a Free connector. Nothing is written to the cluster, ever: every call
is a documented read, the token is granted the read-only PVEAuditor role and
nothing else, and the test connection proves it can read — it never needs to
write. It answers the question a homelab asks most — which VM is this
address? — from the system that actually runs the VM, which a controller or a
DHCP server can only guess at.
What it reads
Section titled “What it reads”All calls are reads of /api2/json/... with an API token
(Authorization: PVEAPIToken=<id>=<secret>):
| Call | Purpose |
|---|---|
/version |
Reachability and authentication probe, for the test connection |
/cluster/resources?type=vm |
Every guest: vmid, name, node, type (qemu or lxc), status, tags, pool, template |
/nodes/{node}/{qemu|lxc}/{vmid}/config |
Per guest: only the netN lines, the agent switch and ostype are kept |
/nodes/{node}/qemu/{vmid}/agent/network-get-interfaces and agent/get-osinfo |
A running VM whose configuration enables the guest agent: the live addresses and the OS name |
/nodes/{node}/lxc/{vmid}/interfaces |
A running container: the live addresses of its network namespace |
Disks, CPU, a cloud-init password hash and everything else in a guest
configuration are never decoded or stored. A NIC line is parsed in both
spellings — virtio=<mac>,bridge=vmbr0,tag=100 for a VM and
name=eth0,hwaddr=<mac>,ip=<cidr>,ip6=<cidr>,bridge=,tag= for a container,
where dhcp, auto and manual are not addresses.
An address counts only when its interface’s MAC is one of the guest’s configured NICs. A docker bridge or a nested container inside the guest has a MAC of its own that the hypervisor never handed out, and is ignored. Loopback, link-local, multicast and unspecified addresses never count. Templates are skipped; a stopped guest is inventoried without addresses.
The optional per-guest calls are tolerant by design. An agent that is not
running or not installed (500), an endpoint an older node does not serve
(404/501), a token without VM.GuestAgent.Audit on the agent path (403) and a
guest that answers too slowly all mean no live addresses, not failure. A node
pveproxy cannot reach (595/596 on the configuration) fails that guest only. A
rejected token (401) or a missing VM.Audit on a configuration (403) fails the
collection, because it is the same token for every guest.
Like NetBox, the cluster is a snapshot, not an event stream. An unchanged inventory produces an empty poll, which is still a successful sync; once a day the inventory is re-emitted in full so the identity anchors stay inside the identity job’s window.
Set it up
Section titled “Set it up”1. Create a read-only token with privilege separation
Section titled “1. Create a read-only token with privilege separation”On any cluster node, as root, create a dedicated user, a token that does not
inherit the user’s rights, and one ACL that grants the built-in PVEAuditor
role on / to the token itself:
pveum user add nyxtrace@pve --comment "Nyxtrace inventory (read-only)"pveum user token add nyxtrace@pve inventory --privsep 1 --comment "Nyxtrace inventory"pveum acl modify / --users nyxtrace@pve --roles PVEAuditorpveum acl modify / --tokens 'nyxtrace@pve!inventory' --roles PVEAuditorBoth ACL lines are required. With --privsep 1 Proxmox VE computes a token’s
effective permissions as the intersection of the user’s and the token’s
permissions, so a token-only ACL yields an empty inventory (/cluster/resources
returns no guests) even though authentication succeeds. Verified on PVE 9.2.
The second command prints the token secret exactly once; copy it into the
credential file of step 2. With --privsep 1 the token can never exceed the
user’s rights, which is why the user carries the same read-only role.
PVEAuditor on / is the minimal ACL: VM.Audit for the guests and their
configurations, Sys.Audit for /version, VM.GuestAgent.Audit for the
agent’s address list. On a release older than 8.2 that last privilege does not
exist; the connector then tolerates the agent’s 403 and inventories the guest
without live addresses. Do not grant PVEVMAdmin or Administrator.
2. Read the node certificate’s fingerprint
Section titled “2. Read the node certificate’s fingerprint”A stock Proxmox node serves a self-signed certificate, which the connector
refuses without a pin. Read the fingerprint once, on the node named in url:
openssl x509 -in /etc/pve/local/pve-ssl.pem -noout -fingerprint -sha256In a cluster every node has its own certificate — pin the one of the node you
point url at. A node with an ACME certificate from a public CA needs no pin;
the host trust store accepts it. Plain http is refused: the API listens on
8006 with TLS only.
3. Credential file
Section titled “3. Credential file”The token secret goes into a file the service can read, in one of the two
accepted shapes of UniFi, step 3 (0640 root:nyxtrace
or 0600 nyxtrace):
install -o root -g nyxtrace -m 0640 /dev/null /etc/ndr/secrets/proxmox-token-secretprintf '%s\n' '<the secret printed by pveum>' >/etc/ndr/secrets/proxmox-token-secret4. Declare it in connectors.yaml
Section titled “4. Declare it in connectors.yaml”connectors: - id: proxmox instance: pve tenant: example enabled: true config: url: https://pve.example.invalid:8006 # any cluster node token_id: nyxtrace@pve!inventory # user@realm!tokenname tls_fingerprint_sha256: "<sha256 from step 2, colons optional>" poll_interval: 5m # floor 1m # nodes: pve-01, pve-02 # optional: only guests on these nodes secrets: token_secret: file:/etc/ndr/secrets/proxmox-token-secretNDR_CONNECTORS_ENABLED=true in /etc/ndr/ndr.env is required, as for every
file-declared connector — see connectors.yaml.
Two clusters are two instances of the same connector, each with its own cursor,
backoff and health line.
The poll interval defaults to five minutes and has a floor of one: every poll reads the guest list, every NIC configuration and every running guest’s agent, so a large cluster pays a few requests per guest each time.
5. Prove it before the service polls it
Section titled “5. Prove it before the service polls it”sudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true ndr connectors validatesudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true ndr connectors test pve --livesudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true ndr connectors collect pve --once --livetest --live reports the Proxmox VE version, the guest count (qemu, lxc,
running, templates), the node count and the one configuration it read — the
version, the guest list and one configuration are exactly the rights the token
needs, and the test fails closed on a missing one. collect --once --live
prints how many addresses became items and how many anchors of which class
(mac, mac_oui, authoritative_map) arrived, plus failed for guests on a
node pveproxy could not reach. It stores nothing.
A failure names its class:
| Class | Meaning |
|---|---|
auth |
Token rejected — check the id spelling user@realm!name and the secret file |
forbidden |
No VM.Audit: the ACL of step 1 is missing or on the wrong path |
tls |
Wrong pin, or a self-signed certificate without one |
unreachable, timeout |
The node did not answer — 8 s per request, 5 min per collection |
unexpected_shape |
Not the API: a reverse proxy in front of it, or the wrong port |
too_large |
An answer over 4 MiB |
Guests without addresses are not an error. A stopped guest, a VM without
the guest agent (qemu-guest-agent not installed, or agent: 0 in its
options) and a container on DHCP have no address the hypervisor can prove.
Install and enable the agent in the VMs that matter, and the next poll picks
the addresses up.
6. Switch on and verify
Section titled “6. Switch on and verify”NDR_CONNECTORS_ENABLED=true in /etc/ndr/ndr.env and systemctl restart ndr-api. The system view then shows connector:pve
with its state, the time of the last sync and the counters guests,
running, ips, agent_ok, templates and nodes, which move on every
poll, empty batches included. The instance turns stale after three missed
polls (15 minutes at the default interval). Without the interface:
journalctl -u ndr-api -g 'instance=pve'The hourly naming job logs hypervisor=N on its next run.
What the poll writes
Section titled “What the poll writes”One item per (guest, address). Every address inside the tenant’s own
configured VLANs gets an inventory record in the state store and its identity
anchors in the anchor table under source = connector:proxmox: mac (or
mac_random) plus mac_oui for the virtual NIC, and authoritative_map with
the guest name reduced to its host label — the same class the UniFi controller
map and NetBox use — each with the poll as its window. Addresses outside the
configured networks are dropped and counted in the dropped= field of the
poll’s log line.
A guest name that is not a usable label yields no name: one that contains
a control character or an undecodable byte, or one longer than 200 bytes,
produces neither an authoritative_map anchor nor a naming candidate (0.27).
The mac and mac_oui anchors of the guest’s NICs are recorded as before —
the hypervisor is trusted for what it proves about a NIC, not for whatever an
operator typed into a name field.
The inventory record carries the guest name, its kind (qemu or lxc),
status, the link to the guest in the Proxmox interface, the MAC, and the
hypervisor fields: vmid, node, pool, tags, os (the agent’s
pretty-name, empty without an agent), bridge and vlan. The payload is a
reduced guest record, never the raw configuration.
Nothing is deleted when a guest disappears from the cluster. Its record does expire: one the connector has not re-emitted for three full snapshots (72 h) stops being shown and stops naming the device, and the name stays listed as a former name. The next emission that carries the address makes it live again.
Bounds: at most 2,000 guests per poll — more is marked truncated, never
silently dropped — 8 s per request, 5 minutes per collection, 4 MiB per
answer.
Where it shows up
Section titled “Where it shows up”Device name. The naming job ranks the hypervisor third, directly after
NetBox and above the UniFi controller map, DHCP, mDNS, the resolver, reverse
DNS and the address. The guest name is a host label, not a typed name, so it
becomes a DNS label (lower case, no spaces): Web-01 in Proxmox and a DHCP
host name web-01 are one name. A name with a control character in it, or one
over 200 bytes, is no label and names nothing. The interface labels such a
name via hypervisor (name_source: hypervisor). The full order is on
device identity and naming.
Device detail. A Proxmox block in the identity panel — virtual machine
or container, the guest name, VMID, node, status, pool, tags, operating
system and the connector instance it was read through — with a link Open in
Proxmox VE when the connector has an https origin to point at.
API. GET /api/v1/devices/{id} returns a proxmox object — name,
vmid, node, type, status, tags, pool, os, url, via, source
— or null when no hypervisor knows any of the device’s addresses. See
endpoints.
Known limits
Section titled “Known limits”- Only the guest agent (VMs) or the network namespace (containers) can prove a
live address; a VM without
qemu-guest-agentis inventoried by name, MAC, bridge and VLAN tag, with no address, and never gains one by guessing. agent/get-osinfocosts one request per running VM with an agent on every poll; on a very large cluster, lengthenpoll_intervalor narrownodes.- The device list (
GET /api/v1/devices) does not carry theproxmoxobject and does not search by VMID or node; both are in the detail only. - The connector was shaped after the public Proxmox VE API documentation and
its synthetic fixtures — a container’s interface list is read in both the
hwaddrand thehardware-addressspelling — and has not yet been exercised against a live cluster by the project itself.test --liveis the first thing to run on yours.