Skip to content

UniFi client block

The unifi-response connector blocks the wireless client that currently holds the target address on a UniFi Network controller (block-sta) and unblocks it on release (unblock-sta). It is the second target of the same executor as the firewall quarantine: the executor sees one node named unifi, adds and removes memberships and reads them back, and the Response page shows the same statuses. It is Pro (licence feature response).

It is meant for a tenant that has no firewall pair — a wireless-only site behind a controller. One tenant can declare one response target, so it is this or the firewall, not both.

  1. Checks the address against the policy: inside allowed_targets, outside every protected scope (plus the controller host and the appliance’s own addresses, added implicitly).
  2. Resolves the client’s MAC from the controller’s live active-client list — never from a cache, because the address may belong to another client by now, and blocking the wrong MAC is worse than refusing. No active client holding the address, or a wired client, is refused (address_outside_scope): blocking a switch port through the client manager is a different blast radius — the port, not the device.
  3. Posts block-sta for that MAC, reads the blocked flag back from the known clients, and only then reports the membership as present.
  4. Records the MAC, the address and the action id in the ownership ledger in the appliance’s state store (response_target_members, keyed by instance and MAC).

On release it posts unblock-sta only for a MAC in its ledger and reads back that the client is no longer blocked. A block an operator placed by hand on the controller is never lifted by Nyxtrace, and a restart neither turns own blocks into foreign ones nor lifts a block it did not make. list_members reports a foreign blocked client as foreign and an owned block the controller no longer shows as absent, so the executor sees a block somebody lifted.

The command body is a fixed verb and a normalised MAC — nothing else of the request reaches the controller.

Settings → Admins → Add Admin, Local Access Only, restricted to the site. This account needs the right to block and unblock clients, which the read-only account of the source connector does not have — so it is a second account, not the dashboard admin and not a UI Cloud account. Give it the least role the controller offers that can block a client, and nothing above.

2. The certificate pin and the credentials

Section titled “2. The certificate pin and the credentials”

The same pin as the source connector (tls_fingerprint_sha256, read with the openssl s_client line on the UniFi page), and two secret files under /etc/ndr/secrets, following the secret rules:

Terminal window
umask 077
printf '%s' 'nyxtrace-response' > /etc/ndr/secrets/unifi-response-username
printf '%s' '<password>' > /etc/ndr/secrets/unifi-response-password
chgrp nyxtrace /etc/ndr/secrets/unifi-response-*
chmod 0640 /etc/ndr/secrets/unifi-response-*
connectors:
- id: unifi-response
instance: unifi-block
tenant: example
enabled: true
config:
url: https://controller.example.invalid:11443
site: default
tls_fingerprint_sha256: "…"
policy_revision: 1
allowed_targets: "198.51.100.0/24"
protected_scopes: "198.51.100.0/28"
ttl: 900s
secrets:
username: file:/etc/ndr/secrets/unifi-response-username
password: file:/etc/ndr/secrets/unifi-response-password

url, site and tls_fingerprint_sha256 are the source connector’s. The policy fields are the same set as on the firewall target — policy_revision, allowed_targets and protected_scopes required, ttl (default 900s), kill_switch, auto_enabled, the thresholds, the limits and the calibration fields. There is no kill_states and no alias: the controller drops the client’s association itself. The automatic mode applies to this target exactly as to the firewall pair — an identity proof of dhcp_lease_and_mac or better is what the controller’s own client table provides, through the unifi source connector.

Terminal window
sudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true NDR_CONNECTORS_PATH=/etc/ndr/connectors.yaml \
ndr connectors validate
sudo -u nyxtrace env NDR_CONNECTORS_ENABLED=true NDR_CONNECTORS_PATH=/etc/ndr/connectors.yaml \
ndr connectors test unifi-block --live

test --live logs in and lists the active clients; it blocks nothing. Restart ndr-api and look for operation=response_worker … nodes=1 … started=true. Without the licence feature the worker starts in mode=release_only.

The same order as on the firewall: once with kill_switch: true to walk a proposal through approve without touching the controller, then one operator-owned wireless test client for ten minutes, Release, and a check on the controller that the client is unblocked. Skip the “expiry with Nyxtrace stopped” step — there is nothing on the controller that would expire it, which is exactly the point of the caution above.

Release on the Response page or POST /api/v1/response-actions/{id}/release unblocks the MAC from the ledger and confirms by reading the blocked flag back; the action becomes rolled_back when the controller no longer shows the client as blocked, rolling_back with the reason otherwise, and the worker retries. At the deadline the appliance releases on its own and ends the action as expired. Release is never licence-gated.

If the appliance is down at the deadline the block stays until it is back or a person lifts it on the controller (Clients → the client → Unblock). If you lift it by hand, press Release afterwards so the ledger agrees.

The instance is listed as connector:unifi-block on the System page. Twenty requests a minute with a burst of six, one command per request, health once an hour. Attempts on GET /api/v1/response-actions/{id} carry the node label unifi, the operation and the readback; the MAC is the membership id on the server side and never leaves it.

  • Wireless clients only. A wired client is refused, on purpose.
  • No fail-open. The controller has no expiry; the appliance’s release is the only automatic way back.
  • The address must be held by an active client at the moment of approval; a client that has already roamed off is refused rather than guessed.
  • Not exercised against a real controller by the project: tested against recorded controller exchanges only.
  • Everything on the overview still applies: this is not endpoint access and not an IPS.