UniFi Network
The UniFi Network connector is a read-only source against a UniFi OS controller. It exists because WiFi is where a hypervisor mirror is blind: the controller knows which client is on which access point, on which SSID, in which VLAN, on which switch port, and — most valuable of all — the name a human gave that client.
It is a Pro connector. See Free vs Pro.
What it reads
Section titled “What it reads”| Call | Every | Gives |
|---|---|---|
| Session login | Once, renewed on a 401 |
A session |
| Active clients | 60 s by default, floor 30 s | Current clients with address, host name, access point, SSID, VLAN, switch port, signal |
| Known clients | 900 s by default, floor 300 s | Operator-assigned names, which change rarely |
The two polls have different intervals on purpose: login plus timeouts cost
real seconds on a normal controller, and the name list is nearly static. The
session is held and renewed exactly once on a 401; after that, backoff takes
over.
Client blocking and isolation are a separate response connector with their own approval path. This source cannot change anything on the controller.
Set it up
Section titled “Set it up”1. Create a read-only account on the controller
Section titled “1. Create a read-only account on the controller”Settings → Admins → Add Admin, Local Access Only, role View Only, restricted to the site the appliance should read. Do not use a UI Cloud account and do not reuse an existing admin: this credential ends up in a file on the appliance, so it has to be worth exactly the read access it grants.
Nyxtrace never needs to write, and a connector that cannot write cannot be turned into a way to write.
2. Read the controller’s certificate fingerprint
Section titled “2. Read the controller’s certificate fingerprint”The connector pins the controller’s certificate by SHA-256 fingerprint. There is deliberately no option to disable verification — not a flag, not an “insecure” mode.
Read the fingerprint once, from the controller itself:
openssl s_client -connect controller.example.invalid:11443 </dev/null 2>/dev/null \ | openssl x509 -noout -fingerprint -sha256Copy the hex digest — with or without colons — into tls_fingerprint_sha256.
3. Store the credentials as files
Section titled “3. Store the credentials as files”The installer creates /etc/ndr/secrets as 0750 root:nyxtrace, empty. A
credential file has exactly two accepted shapes, because the API does not
run as root:
umask 077printf '%s' '<controller username>' > /etc/ndr/secrets/unifi-usernameprintf '%s' '<controller password>' > /etc/ndr/secrets/unifi-passwordchgrp nyxtrace /etc/ndr/secrets/unifi-username /etc/ndr/secrets/unifi-passwordchmod 0640 /etc/ndr/secrets/unifi-username /etc/ndr/secrets/unifi-passwordThat is the group model: mode 0640, owner root, group the service group
— and the group of /etc/ndr/secrets itself is the authority, so the directory
is what a credential’s group is checked against. The alternative is the
owner-only model, mode 0600 owned by the service user.
Everything else is refused, each with the file name and the fix in the message:
readable by another account, group-writable, executable, a world-traversable
directory, a group the directory does not name, a group-readable file whose
group is root, and a root-owned 0600 file while the directory names a
non-root service group — that last one is the shape that looks correctly locked
down and is unreadable at the next restart. A trailing newline is trimmed, so a
printf without \n and an echo both work.
4. Declare the instance
Section titled “4. Declare the instance”Copy infra/appliance/examples/connectors.example.yaml to
/etc/ndr/connectors.yaml (0640 root:root) and keep one unifi-network
entry. Leave enabled: true — the framework flag in step 6 is the real switch.
connectors: - id: unifi-network instance: unifi-home tenant: example enabled: true config: url: https://controller.example.invalid:11443 site: default tls_fingerprint_sha256: "0000000000000000000000000000000000000000000000000000000000000000" poll_interval: 60s user_poll_interval: 900s secrets: username: file:/etc/ndr/secrets/unifi-username password: file:/etc/ndr/secrets/unifi-passwordpoll_interval has an enforced floor of 30 s; site is default unless the
controller says otherwise.
5. Prove it before the service polls it
Section titled “5. Prove it before the service polls it”Validate as the service user: a root-run validation proves the file modes
but not that nyxtrace can read them.
sudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true ndr connectors validatesudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true ndr connectors test unifi --livesudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true ndr connectors collect unifi --once --livetest --live proves reachability, authentication, authorisation and the
response shape, and writes nothing. collect --once --live pulls exactly one
batch and prints how many anchors of which class arrived; it stores nothing, so
running it never makes the service skip data. Anchor values are withheld unless
--verbose is given, because they are MAC addresses and client names.
A failure names its class: auth (wrong account), tls (wrong or missing pin),
unreachable (wrong host or port), forbidden (the account lacks the read
right), rate_limited, timeout.
6. Switch the framework on and restart
Section titled “6. Switch the framework on and restart”printf 'NDR_CONNECTORS_ENABLED=true\n' >> /etc/ndr/ndr.envsystemctl restart ndr-apiThe restart runs ndr migrate as ExecStartPre, and that is required: the
anchor insert writes provenance columns a newer migration added, so a binary
that runs before the migration fails the insert — and that insert is shared with
the identity job. If the ndr identity timer could fire between deploying the
binary and restarting ndr-api, run ndr migrate by hand first.
Expect /api/v1/health to read degraded for up to one poll interval after the
restart: a declared instance is unavailable until its first successful
collection, the same rule the sensors follow.
7. Verify in the interface
Section titled “7. Verify in the interface”The system view shows one component row per declared
instance, connector:<id>, with its state and lag. The same lines are
visible without the interface:
sudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true ndr connectors listjournalctl -u ndr-api -g 'operation=connector'The service then polls on the configured interval, persists the cursor and the
health line in state.sqlite3 (connector_cursors, connector_health), and
writes identity anchors with source = connector:unifi-network. The hourly
ndr identity --tenant <id> job reads them like every other source.
To take the connector out again, set enabled: false on the instance and
restart: a switched-off instance keeps its cursor, deletes no data, and stays
visible with its reason.
What becomes an identity anchor
Section titled “What becomes an identity anchor”Not everything the controller says is an identity claim. The connector maps its fields deliberately:
| Controller field | Becomes | Weight |
|---|---|---|
| MAC address | A mac anchor, or mac_random if the locally administered bit is set |
Establishes |
| MAC vendor prefix | A mac_oui anchor |
Confirms only |
| Client host name | A dhcp_hostname anchor |
Establishes |
| Operator-assigned name | An authoritative_map anchor |
Establishes |
| RADIUS identity | A dot1x anchor |
Establishes |
| Access point, SSID, switch, port, VLAN, vendor string, signal, wired flag | Facts, not anchors | — |
The operator-assigned name is the one that matters most: it is what binds a rotating MAC address to the device you already know. See MAC rotation.
A randomised address is recognised from the locally administered bit and marked as such, rather than being treated as a stable identity.
When the controller is unavailable
Section titled “When the controller is unavailable”The instance reports unreachable and its facts go stale, never deleted. No
identity merge and no identity split is decided on stale controller data alone.
That is the general rule for connector data: a connector is a witness, not an authority. Its last statement stays on the record with its age attached.