# Expose on the LAN

Publish the agent on the router's LAN address with install --expose, the two tagged firewall rules it adds, who reaches the agent through them, the token they require and how they come off.

Source: https://docs.jmrp.io/mikroscope/security/expose/

`--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.

```sh
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.

## Default reachability

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](https://docs.jmrp.io/mikroscope/install/reaching-the-agent/)).

## Firewall rules

`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:

```routeros
/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:

```routeros
/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](https://docs.jmrp.io/mikroscope/about/status/#verified-expose-rules)).
- `--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](https://docs.jmrp.io/mikroscope/security/installer/)).

## Reachable hosts

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](https://docs.jmrp.io/mikroscope/about/status/#not-tested)).

## Token

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](https://docs.jmrp.io/mikroscope/about/status/#verified-read-user-envlist)); 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.

## Clients of the address

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

`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.

> **An exposed agent with no token**
>
> An install of the same `--name` that has a tagged dst-nat and no `TOKEN` entry is published on the
> LAN with no token. `doctor` warns about it, counting the entries and never reading a value:
> `WARN the installed agent published on the LAN asks for a token`. Its fix is
> `mikroscope upgrade --name <name> --token <secret>`, or `mikroscope uninstall --name <name> --yes`,
> which removes the agent and both rules.

## Remove the rules

`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:

```sh
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](https://docs.jmrp.io/mikroscope/about/status/#verified-find-quoting)), and a bare word such as `tcp` is read as a
  variable name, whose unset value is empty.

## See also

- [Network access](https://docs.jmrp.io/mikroscope/install/reaching-the-agent/): direct, relay and `--expose` compared.
- [Security model](https://docs.jmrp.io/mikroscope/security/): where the token sits among the other credentials.
- [Installer safeguards](https://docs.jmrp.io/mikroscope/security/installer/): the ownership checks every rule goes
  through.
- [HTTP endpoints](https://docs.jmrp.io/mikroscope/reference/http/): every path the token guards.
