Setup and configuration

Installing Sensor and pairing

Install Sensor, pair it with Server Edition and audit from the LAN side. See what happens when only one side removes the pairing, and how a collector revokes a lost Sensor.

  • Level: Basics
  • About 45 min
  • 2026-11-12

#Install Sensor

RoamSwitch Sensor is a dedicated machine you place on the LAN. It audits paired endpoints from outside, from the same LAN. The point is to see what the LAN can reach, which a host cannot learn by looking at itself.

Run Sensor at a fixed IP address. After pairing, an endpoint keeps connecting straight to the Sensor address it was given. Sensor listens on TCP 50543 for control traffic.

It sends no discovery traffic right after installing

The package's postinst starts the service as soon as it is installed. By default the ARP sweep (inventory.active_sweep), the DHCP probe (dhcp.active_probe) and the IPv6 router solicitation (ipv6.active_probe) are all off. Only reading the ARP, neighbour and route tables, plus passive capture, runs. To use them for the inventory and rogue DHCP detection on a production LAN, the operator turns them on.

Install, then check that it runs

This installs a package and starts a service (it changes the machine). Try it in a practice environment. Registering the repository is the same as for Server Edition.

apt also installs nmap, a recommended package. The audit uses it for its "nmap NSE supplementary" stage.

The labs ran on one disposable VM. Inside it, a network namespace (ip netns) holds the Sensor's own network: Server at 10.77.0.3, Sensor at 10.77.0.4, joined by a veth pair. Sensor commands ran inside that namespace (sudo ip netns exec rs-sensor …). Below they are written as you would type them on a Sensor machine.

sudo apt install roamswitch-sensor
systemctl is-active roamswitch-sensor
sudo roamswitch-sensor status
Excerpt (public key, address and fingerprint lines left out)
RoamSwitch Sensor installed and started.
By default no discovery traffic is sent to the LAN: the ARP sweep (inventory.active_sweep),
the DHCP probe (dhcp.active_probe) and the IPv6 probe (ipv6.active_probe) stay off until you turn them on.
Run 'roamswitch-sensor status' to see what is on.
active
🔍 RoamSwitch Sensor Status
  Trusted: 0
  ARP events: 0
  Pending audit requests: 0
  What it can see:
    Passive capture is off (ROAMSWITCH_SENSOR_PASSIVE_CAPTURE_IFACE is not set): traffic between other hosts is not seen; only the ARP table, the IPv6 neighbour table and the active probes you turned on are used. …
  · Scheduled audits: off (schedule.enabled is false).
  · Audits of unmanaged devices: off (unmanaged_audit.enabled is false).
  · Notification destinations: none. Nothing is sent when something is detected (set notify.webhook_urls or notify.syslog.host).
  · Discovery traffic to the LAN: off. No ARP sweep, DHCP discover or IPv6 router solicitation is sent (only the ARP table and the passive capture are read). Turn them on with inventory.active_sweep / dhcp.active_probe / ipv6.active_probe.
  Control API TLS: required

The postinst's closing lines are in English only. It prints "installed and started" after checking that the service really runs, and says that discovery is off by default.

"What it can see" lists what Sensor is not watching. Scheduled audits are off by default (schedule.enabled), so every audit in this module is run by hand. The control API's TLS is required on a freshly installed Sensor. It stayed required after pairing and restarting the daemon.

Narrow the listening address?

On start the daemon logs a warning: "Control API is listening on 0.0.0.0:50543 (TLS: required) and is reachable from the whole LAN". Set control.bind_addr to the Sensor's LAN address and it listens on that address only. This key takes effect when the daemon restarts. If the Sensor sits on several networks, narrow it to the one the endpoints are on.

#Let Server accept audits

By default, an endpoint refuses to pair with a Sensor. Receiving audits from Sensor is turned on with sensor_pairing_enabled in /etc/roamswitch/server.conf. The default is false. The example config lists it among the features that are off by default, because it "contacts an operator-supplied address".

Allow pairing

This changes a setting (it changes the machine). Run it on the endpoint (Server).

sudo roamswitch sensor pair --addr 10.77.0.4 --code AAAAAAAA --fingerprint 00; echo rc=$?
sudo roamswitch server config set sensor_pairing_enabled true
sudo roamswitch sensor key
Excerpt
  ✗ Sensor pairing is turned off (off by default). To turn it on, run: sudo roamswitch server config set sensor_pairing_enabled true

rc=1
  ✓ Server configuration updated and saved. (sensor_pairing_enabled = true)
  To apply: sudo systemctl restart roamswitch-server or sudo roamswitch server restart

=====================================================
  🔑 This Endpoint's Public Key
=====================================================

  public key: LCtWn1Go++XnVTO2UBwwp2LN+CSDiudM4Y0pRiHU5WE=
  address: 192.168.64.31

While it is off, sensor pair stops before sending anything to the Sensor and exits with code 1. It also prints the command that turns pairing on.

sensor pair reads the saved setting right away. Just after saving, without a restart, it reached the Sensor. Run sudo roamswitch server restart as well, so the daemon reads the new value. To undo, set sensor_pairing_enabled false.

When the Sensor machine also runs Server Edition

Sensor does not defend itself. The operations manual recommends installing Server Edition on the same machine. Server Edition's firewall refuses inbound traffic by default, though, so it blocks port 50543 too.

sudo roamswitch server config set sensor_colocated true
sudo roamswitch server config get allowed_ports
sudo roamswitch server restart
Excerpt
  ✓ Server configuration updated and saved. (sensor_colocated = true)
  To apply: sudo systemctl restart roamswitch-server or sudo roamswitch server restart

80,443,50543

Setting sensor_colocated to true adds 50543 to allowed_ports. This changes a setting (it changes the machine). After the restart, the firewall had tcp dport { 22, 80, 443, 50543 } accept.

When an endpoint cannot reach port 50543, sensor pair fails with tls_unavailable. Setting it back to false removes only the 50543 this setting added (apply_sensor_colocation in the source).

#Pair with a pairing code

Pairing and revocation uses a single-use code the Sensor issues. The code has 8 characters and expires after 10 minutes. One pairing makes the endpoint trust the Sensor and the Sensor trust the endpoint.

The endpoint pins the Sensor's TLS certificate by its SHA-256 fingerprint. It never fetches the fingerprint over the network. A person reads it off the Sensor's screen and passes it on by voice or chat.

Issue a code, get it wrong, then pair

Issue a code on the Sensor.

sudo roamswitch-sensor issue-code
Output
🔑 Pairing code issued
  Code: X4NBBV5N
  Valid for: 10 minutes
Give this code to the endpoint's operator (verbally, chat, etc.).
  Control API TLS: required
  Certificate fingerprint (SHA-256) — verify/enter it on the endpoint:
    F0:B8:24:38:E7:4F:31:F6:14:5F:46:D4:2F:B1:AD:C3:1E:74:F9:2E:63:24:55:CB:56:4F:14:00:39:AF:F2:D8
  On the endpoint (Linux), run:
    sudo roamswitch sensor pair --addr <Sensor IP> --code X4NBBV5N --fingerprint F0:B8:24:38:E7:4F:31:F6:14:5F:46:D4:2F:B1:AD:C3:1E:74:F9:2E:63:24:55:CB:56:4F:14:00:39:AF:F2:D8
  If the endpoint cannot reach <Sensor IP>, use one of these for --addr instead: 10.77.0.4 (rs-sens)

The Sensor's namespace has no default route, so --addr stays <Sensor IP> and the last line lists the addresses and interfaces you can use. The endpoint uses 10.77.0.4, the address it can reach.

On the endpoint, try a fingerprint with its last character changed, a wrong code, the right pair, and then the used code again. The commands below shorten the fingerprint with …; type it in full.

sudo roamswitch sensor pair --addr 10.77.0.4 --code X4NBBV5N --fingerprint F0:B8:…:F2:D0
sudo roamswitch sensor pair --addr 10.77.0.4 --code 2222AAAA --fingerprint F0:B8:…:F2:D8
sudo roamswitch sensor pair --addr 10.77.0.4 --code X4NBBV5N --fingerprint F0:B8:…:F2:D8 --name lab-sensor
sudo roamswitch sensor pair --addr 10.77.0.4 --code X4NBBV5N --fingerprint F0:B8:…:F2:D8
Excerpt (heading boxes left out)
  ✗ Pairing was rejected: pin_mismatch
  ⚠ The Sensor's certificate does not match the pinned fingerprint. The Sensor may have rotated its certificate (`tls rotate`), the pin may be mistyped, or someone may be intercepting the connection. Verify the value on the Sensor itself and re-pin only if you expected the change.

  ✗ Pairing was rejected: invalid_code
  Reason: invalid_code

  public key: kATbyoSZq/xNvV+2OKJVW/EWwooQHhTmbyqb7ucs6wk=
  ✓ Paired. Audit probes from this Sensor will now be trusted.

  ✗ Pairing was rejected: invalid_code
  Reason: invalid_code

Read them like this:

  • pin_mismatch: the certificate differs from the value you typed. The endpoint does not fall back to plain text. The same code paired afterwards, so it was not used up
  • invalid_code: the code is wrong
  • A used code gives invalid_code the second time. Failures exit with code 1

Check both lists to see each side's record.

sudo roamswitch sensor list          # on the endpoint
sudo roamswitch-sensor list          # on the Sensor
Excerpt
  name: lab-sensor
    paired at: 2026-11-12T02:07:04.357923267+00:00
    public key: kATbyoSZq/xNvV+2OKJVW/EWwooQHhTmbyqb7ucs6wk=
    last address: 10.77.0.4
    TLS certificate pin: F0:B8:24:38:E7:4F:31:F6:14:5F:46:D4:2F:B1:AD:C3:1E:74:F9:2E:63:24:55:CB:56:4F:14:00:39:AF:F2:D8

🔐 Trusted Endpoints
  ubuntu — LCtWn1Go++XnVTO2UBwwp2LN+CSDiudM4Y0pRiHU5WE= (10.77.0.3)

The right pair finished in under a second, even with nmap on the Sensor. The endpoint's "paired at" is in UTC. Along with the Sensor's address, the endpoint also stored the MAC address it saw (last_mac in /var/lib/roamswitch/trusted_sensors.json).

Filling in the fingerprint with a value the Sensor sent you

The pin tells a real Sensor apart from anything in between. If you fill it in with a value that came over the same network, you trust whatever an attacker in the middle swapped in. Type the value from the Sensor's screen, or one you received another way.

#Audit from outside

Vulnerability audit from outside the LAN host only targets paired endpoints. scan on the Sensor tries every port from 1 to 65535, then runs read-only checks on the open ones. It does not write or delete data, and it does not stop services.

Compare the view from inside the host with the view from the LAN

On the endpoint, start a dummy that pretends to be Redis on port 6379. It only answers PING with +PONG and stores nothing. Server's allowed_ports stays at the default 80,443.

sudo ss -ltnH 'sport = :6379'                                # on the endpoint
printf 'PING\r\n' | timeout 3 nc -q1 10.77.0.3 6379; echo rc=$?   # on the Sensor
Output
LISTEN 0      5      0.0.0.0:6379 0.0.0.0:*
rc=124

Inside the endpoint, port 6379 listens on every address. The Sensor still cannot reach it (rc=124 is the 3-second timeout). Server's firewall drops ports that are not in allowed_ports.

Audit from the Sensor. A few leading characters of the public key are enough.

sudo roamswitch-sensor scan LCtW
Excerpt
Running audit... (also running nmap NSE supplementary checks, this may take a while)
Open ports: 22
No issues found.
  ℹ :22 SSH Version Banner Disclosed
nmap NSE supplementary findings:
  🔎 :22 ssh-hostkey — 256 c2:5e:d3:0f:b5:fd:e0:78:96:a3:42:41:18:07:40:f2 (ECDSA)
256 9b:60:0a:5d:5a:96:63:86:4d:8e:42:99:9b:36:cf:92 (ED25519)
  🔎 :22 banner — SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.20
  🔎 :22 fcrdns — FAIL (No PTR record)

The audit took about 45 seconds. Only port 22 is reachable from the LAN, and 6379 does not appear. The host's own list of listeners and the view from outside differ. Keep track of which one you are reading.

The SSH banner shows up as an ℹ line. It is not counted as a finding, so an endpoint with only sshd gets "No issues found."

"nmap NSE supplementary findings" are the results of nmap's safe scripts run against the open ports. Host keys, the banner, the cipher lists and so on appear as 🔎 lines. They are not counted as findings either.

Next, the endpoint's side. At the traffic level, a full-port audit is a port scan. Server Edition detects it as an incoming port scan. When the source matches the paired Sensor's address and MAC, it does not auto-block and sends a calmer notice.

sudo roamswitch notifications      # on the endpoint
Excerpt
  2026-11-12T02:08:00.564755835+00:00
  Detected an audit from a paired Sensor: 10.77.0.4
    🔵 Detected connection attempts to 4096 distinct ports from 10.77.0.4 in a short window, but the source matches a paired RoamSwitch Sensor's known address, so no auto-block was applied
    Host: ubuntu (IP: 192.168.64.31)
    Source: 10.77.0.4
    Timestamp: 2026-11-12 02:08:00

Start a Python test web server on the endpoint's port 80 and run scan again. The Sensor keeps a record you can read back. history lists audits, diff shows the change from the previous audit, and report writes a Markdown report.

sudo roamswitch-sensor diff LCtW
sudo roamswitch-sensor report LCtW --out /tmp/report.md
sudo grep -A4 'Web Vulnerability Scan — Confirmed' /tmp/report.md
Excerpt
🔀 Audit changes (vs the previous audit, newest first)
  2026-11-12 02:09:24  ubuntu  newly open ports 80
  2026-11-12 02:08:18  ubuntu  first audit
✓ Wrote /tmp/report.md
## Web Vulnerability Scan — Confirmed Safe

| Port | Process/target | Check |
|---|---|---|
| 80 | dev-server | CORS Misconfiguration (Cross-Origin Allowed with Credentials) |

In the audit after the test web server started on port 80, diff reported "newly open ports 80"; the first audit is "first audit". The report is created -rw------- (readable by root only), so read it with sudo. "Confirmed Safe" means the listed checks ran and found nothing. It says nothing about checks that are not listed.

#Remove a pairing

A pairing exists on both sides. Removing it on one side leaves the other side still trusting its peer.

Remove it on the endpoint only

Remove the pairing on the endpoint. Nothing is sent to the Sensor.

sudo roamswitch sensor unpair kATb        # on the endpoint
sudo roamswitch-sensor list               # on the Sensor
sudo roamswitch-sensor scan LCtW          # on the Sensor
Excerpt
  ✓ Unpaired.
🔐 Trusted Endpoints
  ubuntu — LCtWn1Go++XnVTO2UBwwp2LN+CSDiudM4Y0pRiHU5WE= (10.77.0.3)
Running audit... (also running nmap NSE supplementary checks, this may take a while)
Open ports: 22, 80
No issues found.
  ℹ :22 SSH Version Banner Disclosed
nmap NSE supplementary findings:
  🔎 :80 http-headers — Server: SimpleHTTP/0.6 Python/3.12.3
  🔎 :80 http-title — Directory listing for /

The Sensor still has the endpoint in its audit scope, and the audit ran as before. The endpoint no longer trusts the Sensor, so it reports a port scan and blocks the source.

Excerpt of the endpoint's notifications
  2026-11-12T02:13:53.969060837+00:00
  Scanning source IP blocked: 10.77.0.4
    Host: ubuntu (IP: 192.168.64.31)
    Blocked source: 10.77.0.4
    Block duration: 10 minutes (expires automatically)
    Timestamp: 2026-11-12 02:13:53

  2026-11-12T02:13:53.784277682+00:00
  Incoming port scan detected: 10.77.0.4
    🔍 Connection attempts to 4096 distinct ports from one source in a short window, likely reconnaissance (a port scan)
    Host: ubuntu (IP: 192.168.64.31)
    Source: 10.77.0.4
    Sample ports: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

The block lasts 10 minutes and lifts by itself (port_scan_block_duration_secs, 600 seconds by default).

Scans from the same source are normally notified once an hour (RENOTIFY_COOLDOWN_SECS in the source). When a source changes from a paired Sensor to an ordinary source, though, it is notified and blocked regardless of that interval. In this lab the scan ran about 6 minutes after the notice for the paired audit.

Remove it on the Sensor as well. The Sensor's unpair also takes the leading characters of the public key.

sudo roamswitch-sensor unpair LCtW
sudo roamswitch-sensor scan LCtW; echo rc=$?
Output
✓ Unpaired.
Error: No matching trusted endpoint found.
rc=1

After that, scan cannot find the target and exits with code 1. The operation log (roamswitch-sensor audit-log) records "unpair … The endpoint was removed from the audit scope". When you retire an endpoint, remove the pairing on both sides.

#Revoking a lost Sensor

A Sensor holds each endpoint's list of weaknesses and its signing key. If a stolen Sensor joins the same LAN, the endpoints treat it as the party they allowed to audit them. If the Sensor is enrolled at a collector, you can revoke it there. A Sensor that learns it was revoked stops itself (lockdown).

Revoke at the collector and watch the Sensor stop

Start the collector, enroll the Sensor, then revoke it (it changes the machine). The operations manual recommends running the collector on a different machine from the Sensor. Here it runs at 127.0.0.1:8443 in the Sensor's namespace, just to walk through the steps. The push interval is set to 1 minute to keep the wait short (the default is 15).

sudo systemctl enable --now roamswitch-sensor-collector
sudo roamswitch-sensor-collector enroll kATbyoSZq/xNvV+2OKJVW/EWwooQHhTmbyqb7ucs6wk= --name lab-sensor --site lab
sudo roamswitch-sensor config set collector.url http://127.0.0.1:8443 collector.enabled true collector.push_interval_minutes 1
sudo roamswitch-sensor-collector list
sudo roamswitch-sensor-collector revoke kATbyoSZq/xNvV+2OKJVW/EWwooQHhTmbyqb7ucs6wk= --reason "lab: sensor lost"
Excerpt of list and revoke
ok         lab-sensor               lab            last=2026-11-12T02:09:41.199578928+00:00  findings=0  kATbyoSZq/xNvV+2OKJVW/EWwooQHhTmbyqb7ucs6wk=
✓ revoked lab-sensor (kATbyoSZq/xN)
  Its next push is refused with `sensor_revoked`; a Sensor that reaches this collector locks itself down (security.on_revoked).
  1 endpoint was paired with it. The endpoint still trusts it as an approved scanner until it drops the pairing:
    - ubuntu  10.77.0.3
  On each: Linux `roamswitch sensor unpair kATbyoSZq/xNvV+2`  /  Mac: Settings > Sensor > Unpair
  Enrol a replacement Sensor with `enroll`, then pair the endpoints with it again.

ok in list means pushes from the Sensor are arriving. Until the first push arrives, list shows never_pushed. When it was revoked before the first push, revoke said it had no record of endpoints paired with this Sensor. revoke printed the endpoints paired with this Sensor and the command to run on each.

The Sensor stopped at its next push, about a minute after the revocation.

sudo roamswitch-sensor lockdown status
sudo roamswitch-sensor issue-code
Output
  🔒 This Sensor is LOCKED DOWN: no pairing, audits, uploads or notifications
     cause: revoked at the collector   since: 2026-11-12 02:10:41
     reason: lab: sensor lost
     release: `roamswitch-sensor lockdown off` (only if you are sure it is back in safe hands)
Error: This Sensor is out of service (locked down), so this operation is refused. Release it with `roamswitch-sensor lockdown off` only if you are sure it is back in safe hands
  cause: revoked at the collector   since: 2026-11-12 02:10:41

After a lockdown off, a code was issued; once the next push had stopped the Sensor again, that code (still inside its 10 minutes) was tried on the endpoint.

Excerpt of sensor pair on the endpoint
  ✗ Pairing was rejected: sensor_locked_down
  Reason: sensor_locked_down

Even a code that has not expired is refused. While locked down, the Sensor accepts no new endpoints.

lockdown off releases it. While it is still revoked at the collector, though, it stops again.

sudo roamswitch-sensor lockdown off
sudo roamswitch-sensor lockdown status    # about a minute later
Output
🔓 Lockdown released. If it is still revoked at the collector it locks itself again on its next upload.
  🔒 This Sensor is LOCKED DOWN: no pairing, audits, uploads or notifications
     cause: revoked at the collector   since: 2026-11-12 02:12:41

In this lab, pushes were stopped with collector.enabled false before lockdown off. To bring a real Sensor back, enroll it again at the collector (enroll) and pair the endpoints again.

Revocation does not reach the endpoints

Revoking at the collector stops the Sensor. Each endpoint keeps trusting it until that endpoint removes the pairing. On every endpoint revoke lists, run the command it shows with sudo. On the lab endpoint, sudo roamswitch sensor unpair kATbyoSZq/xNvV+2 answered "✓ Unpaired." The list comes from the last state the Sensor pushed to the collector (store.rs in the source). An endpoint paired after that push is not on it.

#What it cannot see

What was checked and what was not

On one disposable VM (a Server, and a Sensor in its own network namespace), this is what we found:

  • An audit only sees ports it can reach. A port the endpoint's firewall drops does not appear, even if something listens on it. A port that does not appear may still be in use inside the endpoint
  • nmap NSE runs safe scripts only. The report heading says so too: "nmap NSE Supplementary Findings (safe scripts only)". Scheduled audits and audits of unmanaged devices stayed off, as by default, and did not run. Even with nmap installed, the Sensor sends an ARP sweep only when inventory.active_sweep is on (it is off by default)
  • A full-port audit trips the endpoint's detection. Only when the pairing exists on both sides and the address and MAC match does it get the calmer notice. Removing it on the endpoint only makes it a port-scan notice and a 10-minute block of the source. Even a source just notified as a paired Sensor was notified and blocked at once, because its standing changed
  • A Sensor carried off while powered down never learns it was revoked. Revocation takes effect when the Sensor next pushes. The operations manual (§14.3) names disk encryption (LUKS and the like) as the real defence
  • This module does not cover request-audit, where the endpoint asks for an audit. Every audit here was started on the Sensor
Check yourself

You ran only sudo roamswitch sensor unpair on the endpoint. What happens when the Sensor operator then runs scan?

Show the explanation

Removing the pairing on the endpoint is not sent to the Sensor. The Sensor keeps auditing, and the endpoint no longer trusts it, so it reports a port scan and blocks the source for 10 minutes. To retire an endpoint, run unpair on the Sensor too.

Check yourself

sensor pair was rejected with pin_mismatch. What do you do first?

Show the explanation

pin_mismatch means the certificate differs from the value you typed. Either the value was mistyped, the certificate was regenerated, or something sits in between. A value fetched over the network proves nothing. Compare with the value on the Sensor's screen.

#What to learn next

Features covered
What was verified

Verified on 2026-11-12. Target: Sensor 1.0.7. On one disposable Ubuntu 24.04 VM with the published Linux 1.17.39, Sensor 1.0.7 was installed from the public apt repository with its recommended package (nmap), its network split off in a network namespace, and pairing, TLS after a restart, the full-port audit (with nmap NSE), an audit right after unpairing on the endpoint only, and revocation and lockdown at a collector were run in Japanese and English.

Command output varies with the product version and the environment. If yours differs, check the version first.