Skip to content

Expose on the LAN

--expose publishes the agent on the router’s LAN address, for a client that cannot reach the veth /30: a Prometheus job, a browser, curl. It adds two tagged firewall rules and makes a token mandatory.

Terminal window
mikroscope install --expose --lan-address 192.168.88.1

Keep the token in MIKROSCOPE_TOKEN, which keeps it off the command line.

What install --expose adds

  • two firewall rules, tagged
  • a token becomes mandatory
  • uninstall and status find the two rules through the manifest and the tag, with or without --expose

Every object carries the comment mikroscope:<name> (managed by mikroscope)

mikroscope plan prints every command before anything is written.

The agent binds to the container’s address, the .2 of --subnet (172.30.10.2 by default), on --port (9123). Without --expose it is reachable only from hosts the router routes to that veth /30, through the two list memberships every install adds (Network access).

install --expose adds, after the list memberships and before the container, a dst-nat from the router’s LAN address on the agent port to the veth:

/ip/firewall/nat/add chain=dstnat dst-address=<router LAN IP> protocol=tcp dst-port=9123 action=dst-nat to-addresses=172.30.10.2 to-ports=9123 comment="mikroscope:mikroscope (managed by mikroscope)"

and a forward accept for that flow, placed before the first chain=forward action=drop rule, or appended when the forward chain has no drop:

/ip/firewall/filter/add chain=forward dst-address=172.30.10.2 protocol=tcp dst-port=9123 connection-nat-state=dstnat action=accept comment="mikroscope:mikroscope (managed by mikroscope)" place-before=<first forward drop>
  • The addresses and the port are the defaults. <first forward drop> stands for the lookup the real command does on the router before it adds the rule. plan prints both commands exactly as they run, with your values.
  • Both carry the tag. The pair lets the LAN reach the agent through the router’s own address (verified).
  • --lan-address must be an IPv4 address; install refuses --expose without one (--expose needs the router's IPv4 LAN address). doctor checks that an interface of the router holds it and that it is not on the uplink.
  • An existing rule with the same chain, destination address, port and protocol that does not carry the tag stops install, as any foreign object does (Installer safeguards).

Every LAN host can reach the agent at <router LAN IP>:9123. Neither rule restricts the source: the dst-nat has no in-interface and no src-address, and the accept matches only the destination, the port and connection-nat-state=dstnat. Which hosts get through is decided by which hosts can send a packet to the router’s LAN address, and by whatever your other rules do before these. Whether anything outside the LAN reaches that address depends on the rest of your firewall, which mikroscope neither reads nor changes (not tested).

Because the agent is no longer reachable only through the veth, install and upgrade refuse --expose without a token (--expose makes the agent reachable from the LAN: a token is mandatory).

With a token set, every endpoint the agent serves except /healthz returns 401 token required, with WWW-Authenticate: Bearer, unless the request carries Authorization: Bearer <token>: /capabilities, /sampler, /snapshot, /stream, /captures, /captures/{id} (including DELETE) and POST /capture. A path the agent does not serve gets 404, and a wrong method 405, token or not. The agent strips an optional "Bearer " prefix before comparing, so a header holding the bare token is accepted too.

/healthz stays open. It returns the agent’s version, rate, sequence numbers, uptime, slip count, capabilities hash, its wall and monotonic clocks, and the board’s device-tree model. The model is there on purpose: it is what an operator is asked to send when their board has no kernel-to-RouterOS port map yet.

What the token is, and is not:

  • It may contain letters, digits, _, . and -, up to 128 characters; anything else is refused before the first command.
  • It is stored in the envlist as TOKEN. A read,api user can list every container’s envlist property (verified); whether that user reads the entries’ values was not checked, and the design assumes it can. Treat the token as guarding the agent’s HTTP paths from the LAN, not from the router’s own read users.
  • doctor, status and upgrade count the TOKEN entries and never read their value; the install manifest says only token=yes or token=no.
  • A token set without --expose is still written and still required.
  • The agent compares it as a plain string, over plain HTTP: the dst-nat carries no TLS, so the header crosses the LAN unencrypted.

The rules serve a client that addresses <router LAN IP>:<port>: a Prometheus job, a browser, a curl with the header. mikroscope’s own commands do not use that address:

  • record and forward build the agent’s URL from --subnet and --port, so they always dial the container’s address. No flag points them at the LAN address.
  • install, upgrade and status probe /healthz at the container’s address too, and doctor reads /healthz and then the agent’s ring there, presenting the token if one is given.
  • The relay transport cannot carry the token: /tool fetch on the router sends no Authorization header. Against an agent with a token, the relay’s /healthz answers and every sample request is refused. A token needs the direct transport, or a deployment without a token.

upgrade keeps both rules. It reads from the router that the install is exposed, removes the container step (the container, the envlist <name>-env and the image) and creates it again, with the envlist written from the flags given to upgrade.

  • It refuses an install whose envlist holds a TOKEN when no token is given: the install named mikroscope asks for a token and upgrade would write its envlist without one: pass --token (or MIKROSCOPE_TOKEN).
  • A tuning flag left out comes back at its default. Pass the --rate, --buffer, --mem-limit-mb, --capture-mb, --triggers, --floor-hz, --memory-max, --privileged, --restart-max-count and --restart-interval you installed with.

uninstall removes both rules with the rest of the install. It reads the install’s shape from the router, so it needs neither --expose nor --lan-address, and no token:

Terminal window
mikroscope uninstall # lists what it would remove, the two rules included
mikroscope uninstall --yes # removes it
  • Each rule is selected by the tag together with the chain, destination address, port and protocol. Any other object that carries the install’s tag in /ip/firewall/nat or /ip/firewall/filter is removed by the tag.
  • It then counts what is left, per step and per menu, and fails naming anything that remains.
  • Those find selectors quote every non-numeric value except the chain and action enums. Unquoted, an address or a port is parsed as a typed value and matches nothing (verified), and a bare word such as tcp is read as a variable name, whose unset value is empty.