Scanners
Scanners are stateless Go binaries on customer networks that execute port scans. This page provisions new scanners, manages their lifecycle (enable/disable/delete), configures IP binding, mints one-time provisioning and mTLS enrollment tokens, and governs the fleet-wide mTLS policy. A new scanner gets its identity from install.sh, which runs `mipo-scanner provision` on the host as the mipo-scanner service user: the host generates its own key pair and API key, sends a certificate request plus the API-key hash with the one-time provisioning token, and persists the issued certificate, key, API key and runtime trust in /var/lib/mipo-scanner. No secret is written to /etc/mipo-scanner.env.
Inputs
| Name | Description | Allowed values | Default |
|---|---|---|---|
Name | Unique label identifying this scanner | Letters, numbers, hyphens and underscores; 1–100 characters; unique across scanners AND resolvers (one identity table) | — |
Description | Optional free-text note about the scanner | Any text, max 10,000 characters | Empty |
Binding Mode | Restricts which source IP the scanner can connect from | ip, subnet, asn, or none | none |
Rate Limit | Maximum probes per second this scanner can fire, reflecting its hardware capacity | Integer 0–100,000 | 0 (unlimited) |
Fields & columns
| Name | Description |
|---|---|
Name | Editable inline — the scanner label |
Description | Editable inline — optional note |
Status | Pending (record created, host not yet provisioned), Active (provisioned and heartbeating — provisioning itself flips Pending to Active), Offline (no heartbeat for 2+ min), or Disabled (manually stopped) |
Binding | IP/subnet/ASN restriction for this scanner. Yellow badge shows IP mismatch count. |
Created By | User who provisioned this scanner |
Rate Limit | Editable inline — max probes per second, or "—" if unlimited |
Created | Date the scanner was provisioned |
mTLS | Certificate-based mutual TLS state for this scanner — Not Enrolled, Legacy Allowed (API-key only), Enrolling (an enrollment token is minted and unused), or Required (holds an mTLS identity; the legacy API-key path is refused for it). Shows the active certificate's expiry date when one exists. |
How to
Provision a New Scanner
- Click "Add Scanner", fill in the name, and open the Scanner Provisioning modal for the new record.
- On the target host: stage ./mipo-scanner next to where you will run the installer and verify its SHA-256 and Ed25519 signature (the modal's binary steps).
- Download install.sh from /scanner/install.sh, compare its SHA-256 with the value the modal shows, then run: sudo MIPO_MANAGER_URL=<manager-url> MIPO_PROVISIONING_TOKEN=<token> MIPO_BINARY_PATH=./mipo-scanner bash install.sh — the token is read only from the environment (there is no flag for it) and install.sh pipes it to `mipo-scanner provision` over stdin. This sudo form shows the token in sudo's argument list for the instant sudo starts, on the host that will hold the identity.
- install.sh creates the mipo-scanner system user, installs the binary, and runs `mipo-scanner provision` as that user. The host generates its own key pair and API key, sends a certificate request plus the API-key hash with the one-time token to /scanner/register, verifies the issued certificate, and persists key, certificate, API key and runtime trust under /var/lib/mipo-scanner (0700). /etc/mipo-scanner.env receives only MIPO_MANAGER_URL.
- If the manager serves a self-signed or private-CA certificate, also pass MIPO_SERVER_CA_FILE=<ca.pem> — MIPO_INSECURE is refused for provisioning.
- Provisioning itself flips the record from Pending to Active; the first heartbeat keeps it Active and the mTLS column shows Required with the certificate expiry. A host that never heartbeats afterwards goes Offline.
Configure IP Binding
- Open the row actions menu and click "Configure Binding".
- Choose a binding mode (IP, Subnet, or ASN).
- Enter the allowed value and save.
- Connections from non-matching IPs will be flagged with a yellow mismatch badge.
Troubleshoot an Offline Scanner
- Check that the scanner service is running on the target host (systemctl status mipo-scanner).
- Verify outbound reachability from the scanner host to the manager on TCP 443 (bootstrap: provisioning, enrollment, the install script; and the runtime path for legacy API-key scanners while the fleet policy is migration) AND on TCP 8443 (the mandatory-mTLS runtime ingress, SCANNER_MTLS_PORT) once the cutover is published.
- Read the host logs: journalctl -u mipo-scanner -f — look for TLS handshake failures, a certificate past expiry, or 401/403 responses.
- Check the mTLS column: Not Enrolled or Legacy Allowed means the scanner still uses the legacy API-key path on :443 and will be refused once the fleet policy is required; Required means every request must present its certificate on :8443.
- If the scanner was recently re-provisioned (Regenerate Token + install.sh), confirm the host completed the new provisioning — the old identity stopped authenticating the moment the token was regenerated.
Rotate an API key without interrupting in-flight scans
- Open the row actions menu for the active scanner.
- Click "Rotate API Key".
- In the modal, click "Rotate Key" to confirm.
- Copy the new key from the success screen — it is shown only once.
- Legacy (API-key-only) scanner: update MIPO_API_KEY in /etc/mipo-scanner.env on the host and restart the service within 24 hours. The previous key keeps working until then.
- Scanner with an mTLS identity: its persisted API key lives inside the active generation under /var/lib/mipo-scanner and there is no environment override once an identity exists. Within the 24-hour grace, run Enroll mTLS again (Re-enroll) passing the NEW key as MIPO_API_KEY — enroll accepts the current key or the previous key still inside its grace and writes a fresh immutable generation holding the new key. Never modify files under /var/lib/mipo-scanner/gen-<N>/ by hand; generations are immutable by contract.
- After 24 hours the previous key stops authenticating; a host still presenting it starts logging auth failures and shows as Offline.
Enroll an existing scanner into mTLS
- Upgrade the host's binary first with upgrade.sh — a build from before the mTLS release has no `enroll` subcommand.
- Open the row actions menu for the scanner and click "Enroll mTLS" (or "Re-enroll mTLS" if it already has a required identity).
- Confirm in the modal — this mints a one-time enrollment token (valid 60 minutes, single-use). Copy the command and the token from their separate blocks.
- On the host, make sure the state directory exists and belongs to the service user (sudo install -d -o mipo-scanner -g mipo-scanner -m 0700 /var/lib/mipo-scanner), then run enroll AS that user, exactly like install.sh runs provision: sudo runuser -u mipo-scanner -- env -i MIPO_MANAGER_URL=<manager-url> MIPO_STATE_DIR=/var/lib/mipo-scanner MIPO_API_KEY=<the key from /etc/mipo-scanner.env> /usr/local/bin/mipo-scanner enroll
- Paste the token at the stdin prompt, or export it as MIPO_ENROLLMENT_TOKEN in that environment — never pass it on the command line.
- Make sure the unit file has StateDirectory=mipo-scanner and Environment=MIPO_STATE_DIR=/var/lib/mipo-scanner (an install from before mTLS lacks both — see the Linux service guide's "Enroll an existing installed scanner" section), then sudo systemctl restart mipo-scanner.
- A successful enroll sets the scanner to Required immediately (Enrolling only means a minted token is still unused). The host journal shows "Loaded enrolled identity from state (generation N)" — N is whatever number the host reserved, not necessarily 1.
Require mTLS fleet-wide
- Open the Fleet mTLS Policy panel above the scanners table and check the readiness list — it lists every blocker (unenrolled scanner, missing certificate, untrusted issuer, missing enforcement, missing CA/trust bundle, unresolvable Public URL) with a remediation hint.
- Enroll or re-enroll each listed scanner (or fix the CA/trust-bundle/Public URL issue) until the blocker list is empty. The "Require mTLS for all scanners" button stays disabled while any blocker remains.
- Click "Require mTLS for all scanners" and, in the modal, type the literal confirmation string require-mtls exactly (the API body is { policy: "required", confirm: "require-mtls" }).
- Confirm — this is step-up gated, so re-prove your identity if prompted.
- If the fleet state changed between the panel's preview and the submit (a scanner going offline, losing its cert, etc.), the modal shows the fresh blockers in place instead of closing — resolve them and retry.
- The flip is ONE request: the server live-probes, takes the lock, recomputes readiness, persists the zero-blocker snapshot and flips. Three things return 409 and persist nothing: readiness blockers (the response lists them), a revalidation change under the lock (the served identity changed — re-check and retry), or a CA rollover still in progress (ROLLOVER_IN_PROGRESS). Repeating the request once the policy is already required is a harmless 200 with changed: false.
Gotchas
- Scanners and resolvers share one identity table with a fixed kind: this page lists kind=scanner only. Reverse-DNS workers live under Config › Resolvers and can never receive port-scan work.
- Regenerating a provisioning token invalidates the previous one — the host must be re-provisioned with install.sh and the new token, which mints a fresh identity and API key.
- After an install from the mTLS release, /etc/mipo-scanner.env contains only MIPO_MANAGER_URL. The private key, certificate, API key and runtime trust live in /var/lib/mipo-scanner (0700, owned by the service user); uninstall.sh wipes that directory.
- Two ingress ports: :443 for bootstrap (provisioning, enrollment, the install script) and for legacy API-key runtime traffic while the policy is migration; :8443 (SCANNER_MTLS_PORT) for the mandatory-mTLS runtime once the cutover is published. Allow both from every scanner host.
- Enroll must run as the mipo-scanner user (exactly as install.sh runs provision). Run as root, it leaves root-owned generation files the service cannot read — chown -R mipo-scanner:mipo-scanner /var/lib/mipo-scanner repairs that.
- Disabling a scanner prevents it from receiving new scan assignments but does not kill in-progress scans.
- A scanner marked Offline has simply missed heartbeats; it may still be running but unable to reach mipo.
- Rate limits interact with subnet and port list limits — the most restrictive (lowest non-zero) value wins. When scanners share a subnet via a scanner group, the subnet rate is divided equally among them.
- Scans use a 0.5 s TCP connect timeout by default, which is fast and accurate on a LAN. On lossy or high-latency links a lost packet can make an open port look closed (a false "closed" change in a delta scan): set MIPO_SCAN_TIMEOUT in /etc/mipo-scanner.env on that scanner host as a Go duration, e.g. MIPO_SCAN_TIMEOUT=2s (1.5–2 s for WAN, 3.5–5 s for intercontinental links), and restart the mipo-scanner service. An unparseable value such as "1.5 s" silently falls back to 0.5 s. See the Linux service install guide.
- Rotate Key (24h grace) is different from Regenerate Token (immediate kill). Use Rotate Key for proactive rotation; use Regenerate Token only when you suspect a compromise and need the old key to stop working right now.
- The new key from a rotation is shown exactly once. If you lose it before deploying it to the scanner, you must rotate again — there is no read-back path by design.
- An enrollment token is valid for 60 minutes and single-use. If it expires or is lost before the scanner uses it, mint a fresh one — there is no read-back path.
- Enrolling in mTLS does not retire the API key — the API key continues to authenticate the scanner alongside its certificate; it stays mandatory even after the scanner reaches the Required mTLS state.
- Re-enrolling a scanner that already has a Required mTLS identity issues a brand-new certificate and revokes the current one as soon as the new one is issued — treat it like a compromise-response action, not routine maintenance.
- Requiring mTLS fleet-wide is a one-way change — there is no button or API call to flip it back to migration mode. Recovery from a mistaken flip goes through certificate/CA operations (re-enrolling scanners, rotating the trust bundle), not a policy downgrade.
- The required-policy flip persists a readiness snapshot as evidence (shown as "snapshot #N" in the Fleet mTLS Policy panel once flipped) — it records exactly which scanners were judged ready and why, for audit purposes.
- A scanner disabled at the time of the flip is excluded from readiness entirely. If you re-enable it afterward, it has no legacy API-key-only runtime to fall back on — it must be (re-)enrolled with mTLS before it can rejoin the fleet.
API calls (25)
| Method | Path | Description |
|---|---|---|
| GET | /api/config/scanners | List all scanners |
| POST | /api/config/scanners | Create a new scanner |
| PUT | /api/config/scanners/:id | Update scanner name or description |
| PATCH | /api/config/scanners/:id | Update scanner binding |
| DELETE | /api/config/scanners/:id | Delete a scanner — its certificate records go with it (cascade) and its API key stops authenticating immediately |
| GET | /api/config/scanners/:id/binding | Get scanner binding details |
| POST | /api/config/scanners/:id/release-binding | Release scanner IP binding |
| POST | /api/config/scanners/:id/disable | Disable a scanner |
| POST | /api/config/scanners/:id/enable | Enable a disabled scanner |
| POST | /api/config/scanners/:id/regenerate-token | Regenerate the provisioning token (kills the current API key immediately; the host must be re-provisioned with install.sh) |
| POST | /api/config/scanners/:id/rotate-key | Rotate the API key with a 24h grace window. The previous key continues to authenticate for 24 hours so in-flight scans do not fail. Returns the new key once. A legacy scanner takes it via /etc/mipo-scanner.env; a scanner with an mTLS identity takes it by re-enrolling with the new key. 409 if the scanner has no API key yet (use regenerate-token). |
| POST | /api/config/scanners/:id/enrollment-token | Mint a one-time mTLS enrollment token (60-minute TTL, single-use) for an already-provisioned scanner. Returns { enrollmentToken, expiresAt, expiresInMinutes } once — the token is stored only hashed and cannot be redisplayed. |
| GET | /api/config/scanner-mtls/policy | Get the fleet-wide mTLS enforcement policy singleton (migration allows legacy API-key-only scanners; required rejects them). |
| PUT | /api/config/scanner-mtls/policy | Flip the fleet-wide mTLS policy from migration to required in one request (live probe, lock, readiness recompute, snapshot, flip). One-way, step-up gated; body { policy: "required", confirm: "require-mtls" }. 409 with the blocker list, 409 on a revalidation change, 409 ROLLOVER_IN_PROGRESS, 422 POLICY_DOWNGRADE_FORBIDDEN; already required replays as 200 { changed: false }. |
| GET | /api/config/scanner-mtls/readiness | Advisory fleet-readiness preview for the mTLS cutover — lists every blocker (scanner-not-enrolled, required-scanner-without-active-cert, scanner-cert-issuer-untrusted, scanner-enforcement-missing, no-issuing-ca, no-active-trust-bundle, issuing-ca-not-trusted, public-url-unresolvable) before flipping the policy to required. |
| GET | /api/config/scanners/binary-hash | Get the current scanner binary hash |
| GET | /api/config/scanners/install-script-preview | Preview the scanner install script |
| GET | /api/config/scanners/source-code | Get scanner source code for review |
| GET | /api/config/scanners/:id | Get single scanner configuration |
| GET | /api/config/scanners/filter-values | Get available filter values for the scanners table |
| POST | /scanner/register | Manager-served (:443) provisioning endpoint used by `mipo-scanner provision`: body is a ProvisionRequest (one-time provisioningToken, a certificate request, the SHA-256 hash of the host-generated API key, hostname/osInfo). Returns the issued certificate chain plus runtime trust once and flips the record to Active. The raw API key never leaves the host. |
| POST | /scanner/enroll | Manager-served (:443): exchange a one-time enrollment token plus the scanner's current API key (X-API-Key header; the previous key is accepted while inside its 24h rotation grace) and a certificate request for an mTLS identity — used by `mipo-scanner enroll` on an already-installed scanner. Sets the scanner to Required. |
| POST | /scanner/cert/renew | Runtime (:8443, mTLS): the scanner requests a renewed leaf before expiry; it stages the new leaf and activates it after verification. |
| POST | /scanner/cert/activate | Runtime (:8443, mTLS): the scanner confirms activation of a renewed leaf; the previous leaf enters its retiring window. |
| GET | /scanner/install.sh | Ingest API — serves the static shell install script (no auth required to fetch; integrity via the X-Mipo-Sha256 header). Download it, then run sudo MIPO_MANAGER_URL=… MIPO_PROVISIONING_TOKEN=… MIPO_BINARY_PATH=./mipo-scanner bash install.sh; the script runs `mipo-scanner provision` as the mipo-scanner user, stores the identity in /var/lib/mipo-scanner, writes /etc/mipo-scanner.env with the manager URL only, and installs a hardened systemd unit. |
Related
- Resolvers — The same identity lifecycle, for the reverse-DNS role
- Scanner Groups — Organize scanners into groups for distributed scanning
- Subnets — Scanners are assigned to subnets to define which networks they scan
- Recent Scans — View scan progress and results from scanner executions
- Scanner Health — Real-time health and heartbeat status for registered scanners
- Settings — Public URL setting affects scanner provisioning commands
- Scanner ACL — Exports firewall rules based on registered scanner IPs; both scanner ingress ports (:443 and :8443) must be allowed from scanner hosts
- Discovery — Discovery lists are assigned to scanners for execution
- Scanner Binary — Build and deploy the scanner binary before provisioning
- Scanner Heartbeat — Heartbeat and result submission protocol used by scanners
- Scanner Job Dispatch — Pull-based job protocol by which scanners receive scan assignments
- Resource Limits — MAX_SCANNERS cap applies to the total number of registered scanners
- Rate Limit Hierarchy — Scanner rate limits participate in the three-level slowest-wins calculation
- Job Errors — Active job error states per scanner — Active Errors column links here
- Install: Linux service — Full host-install procedure, including enrolling an existing installed scanner into mTLS
- Scanner CA Rollover — Rotating the issuing CA that signs scanner certificates without fleet downtime