Tailnet SSH policy changed from the default action:"check" to accept. The effective policy is now a single rule covering all tailnet nodes, with no expiry and port forwarding allowed, so the per-device ~12h grants and the browser re-auth prompt are gone. Verified unattended with BatchMode=yes: a plain command, a -L forward returning HTTP 302, and the Herdr fallback leg. Records the trade-off: check acted as a second factor, so a compromised tailnet device now reaches root on Unraid without a further step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
308 lines
11 KiB
Markdown
308 lines
11 KiB
Markdown
# MikroTik Tailscale Container Setup
|
|
|
|
**Last Updated:** 2026-02-06
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Tailscale VPN running as a container on MikroTik hAP ax³, providing remote access to the home network via the Tailscale mesh network.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ MikroTik hAP ax³ (192.168.10.1) │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────┐ │
|
|
│ │ Container Network (172.17.0.0/24) │ │
|
|
│ │ │ │
|
|
│ │ ┌─────────────────────┐ ┌─────────────────────────┐ │ │
|
|
│ │ │ AdGuard Home │ │ Tailscale │ │ │
|
|
│ │ │ 172.17.0.2 │ │ 172.17.0.3 │ │ │
|
|
│ │ │ veth-adguard │ │ veth-tailscale │ │ │
|
|
│ │ └─────────────────────┘ └─────────────────────────┘ │ │
|
|
│ │ │ │ │ │
|
|
│ │ └───────────┬───────────────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ Gateway: 172.17.0.1 │ │
|
|
│ └─────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ NAT Masquerade │
|
|
│ │ │
|
|
│ WAN (ether1) │
|
|
└──────────────────────────────┬──────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────┐
|
|
│ Tailscale Network │
|
|
│ 100.x.x.x mesh │
|
|
│ │
|
|
│ Home Router IP: │
|
|
│ 100.74.219.35 │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Container Details
|
|
|
|
| Setting | Value |
|
|
|---------|-------|
|
|
| Image | tailscale/tailscale:latest |
|
|
| Interface | veth-tailscale |
|
|
| Container IP | 172.17.0.3/24 |
|
|
| Gateway | 172.17.0.1 |
|
|
| Tailscale IP | 100.74.219.35 |
|
|
| Root dir | usb1/tailscale/root |
|
|
| State mount | usb1/tailscale → /var/lib/tailscale |
|
|
| DNS | 8.8.8.8 |
|
|
| Start on boot | Yes |
|
|
| Networking mode | Userspace (TS_USERSPACE=true) |
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Value | Purpose |
|
|
|----------|-------|---------|
|
|
| TS_AUTHKEY | tskey-auth-... | One-time auth key (used during setup) |
|
|
| TS_STATE_DIR | /var/lib/tailscale | Persistent state directory |
|
|
| TS_USERSPACE | true | Required for MikroTik containers (no /dev/net/tun) |
|
|
|
|
---
|
|
|
|
## Network Configuration
|
|
|
|
### veth Interface
|
|
|
|
```routeros
|
|
/interface veth add address=172.17.0.3/24 gateway=172.17.0.1 name=veth-tailscale
|
|
```
|
|
|
|
### Gateway IP on Interface
|
|
|
|
```routeros
|
|
/ip address add address=172.17.0.1/24 interface=veth-tailscale comment="Tailscale container gateway"
|
|
```
|
|
|
|
### NAT Masquerade for Internet Access
|
|
|
|
```routeros
|
|
/ip firewall nat add chain=srcnat action=masquerade src-address=172.17.0.0/24 out-interface-list=WAN comment="Container network NAT"
|
|
```
|
|
|
|
### Firewall Forward Rules
|
|
|
|
```routeros
|
|
/ip firewall filter add chain=forward action=accept dst-address=172.17.0.0/24 comment="[Container] Forward to container network"
|
|
/ip firewall filter add chain=forward action=accept src-address=172.17.0.0/24 comment="[Container] Forward from container network"
|
|
```
|
|
|
|
---
|
|
|
|
## Container Setup Commands
|
|
|
|
### Create Mounts
|
|
|
|
```routeros
|
|
/container mounts add list=ts-state src=usb1/tailscale dst=/var/lib/tailscale
|
|
```
|
|
|
|
### Create Environment Variables
|
|
|
|
```routeros
|
|
/container envs add list=ts-env key=TS_STATE_DIR value=/var/lib/tailscale
|
|
/container envs add list=ts-env key=TS_USERSPACE value=true
|
|
/container envs add list=ts-env key=TS_AUTHKEY value=<your-auth-key>
|
|
```
|
|
|
|
### Create Container
|
|
|
|
```routeros
|
|
/container add remote-image=tailscale/tailscale:latest interface=veth-tailscale \
|
|
root-dir=usb1/tailscale/root mountlists=ts-state envlists=ts-env \
|
|
dns=8.8.8.8 start-on-boot=yes logging=yes
|
|
```
|
|
|
|
---
|
|
|
|
## Management
|
|
|
|
### Check Container Status
|
|
|
|
```routeros
|
|
/container print
|
|
```
|
|
|
|
### View Logs
|
|
|
|
```routeros
|
|
/log print where topics~"container" and message~"tailscale"
|
|
```
|
|
|
|
### Start/Stop Container
|
|
|
|
```routeros
|
|
/container start [find name~"tailscale"]
|
|
/container stop [find name~"tailscale"]
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Container Won't Start (Exit Status 1)
|
|
|
|
**Cause:** Missing /dev/net/tun device (default for Tailscale)
|
|
|
|
**Solution:** Enable userspace networking mode:
|
|
```routeros
|
|
/container envs add list=ts-env key=TS_USERSPACE value=true
|
|
```
|
|
|
|
### Can't Reach Internet from Container
|
|
|
|
**Cause:** Missing NAT masquerade or gateway IP
|
|
|
|
**Solution:**
|
|
1. Verify gateway IP on veth interface:
|
|
```routeros
|
|
/ip address print where interface=veth-tailscale
|
|
```
|
|
|
|
2. Verify NAT masquerade rule:
|
|
```routeros
|
|
/ip firewall nat print where src-address=172.17.0.0/24
|
|
```
|
|
|
|
### Container Not Connecting to Tailscale
|
|
|
|
1. Check DNS resolution works (logs should show no timeout)
|
|
2. Verify auth key is valid and not expired
|
|
3. Check firewall isn't blocking outbound HTTPS
|
|
|
|
---
|
|
|
|
## Tailscale Network Devices
|
|
|
|
| Tailscale IP | Device | Status |
|
|
|--------------|--------|--------|
|
|
| 100.74.219.35 | MikroTik hAP ax³ (container) | Online |
|
|
| 100.100.208.69 | xtrm-unraid (Unraid plugin, offers exit node) | Online |
|
|
| 100.100.208.70 | xtrm-unraid (old node, deleted 2026-11-03) | Removed |
|
|
| 100.112.103.7 | hapax3 (old native install) | Offline |
|
|
| 100.75.93.123 | mikrotik-tailscale (previous container) | Offline |
|
|
|
|
---
|
|
|
|
## Important Notes
|
|
|
|
1. **Userspace Networking Required:** MikroTik containers don't have /dev/net/tun access, so TS_USERSPACE=true is mandatory
|
|
|
|
2. **Auth Key:** After initial authentication, the key is no longer needed - state is persisted in the mount
|
|
|
|
3. **Container Network:** Both AdGuard and Tailscale share the 172.17.0.0/24 network but have separate veth interfaces
|
|
|
|
4. **Accept Routes:** If subnet routing is needed, add TS_EXTRA_ARGS="--accept-routes" to environment
|
|
|
|
---
|
|
|
|
## Unraid Tailscale Plugin Node — 2026-11-03
|
|
|
|
The Unraid server runs its own Tailscale node via the **Tailscale plugin** (not a
|
|
container), separate from the MikroTik container documented above. It had been
|
|
logged out since roughly May 2026 and was re-enrolled on 2026-11-03.
|
|
|
|
| Item | Value |
|
|
|------|-------|
|
|
| Tailscale IP | `100.100.208.69` |
|
|
| MagicDNS name | `xtrm-unraid.tailf789e1.ts.net` |
|
|
| Daemon | `/usr/local/sbin/tailscaled` 1.96.2, `-tun tailscale1` |
|
|
| State dir | `/boot/config/plugins/tailscale/state` (survives reboot) |
|
|
| Previous IP | `100.100.208.70` — stale node deleted, so a new IP was issued |
|
|
|
|
### Prefs set
|
|
|
|
| Pref | Value | Reason |
|
|
|------|-------|--------|
|
|
| `--advertise-routes` | none | The old `192.168.31.0/24` advertisement was dropped. That subnet is only the macvlan shim created in `/boot/config/go` for a Pi-hole at `192.168.31.4`. Pi-hole was replaced by AdGuard: there is no such container any more and the address is silent, so nothing reachable lives there. |
|
|
| LAN subnet routing | not advertised | The MikroTik container is primary for `192.168.10.0/24`, `.20`, `.25`, `.30`. A second advertiser for a subnet Unraid itself sits on would create a routing conflict. |
|
|
| `--advertise-exit-node` | on | Redundant with the hAP today, kept as a fallback on the 24/7 box. **Approved in the admin console 2026-11-03** — shows as `offers exit node` and appears in `tailscale exit-node list`. |
|
|
| `--accept-routes` | **false** | Critical. Unraid is *on* `192.168.10.0/24`; accepting the hAP's route for its own subnet would loop. This is why `tailscale status` reports the health warning "Some peers are advertising routes but --accept-routes is false" — expected here, not a fault. |
|
|
| `--accept-dns` | false | Keeps local AdGuard resolution intact (Unraid AdGuard is config master). |
|
|
| `--ssh` | on | Tailscale SSH. Needed — see the sshd caveat below. |
|
|
|
|
### SSH over Tailscale — the sshd caveat
|
|
|
|
`/etc/ssh/sshd_config` on Unraid binds explicitly and has **no wildcard**:
|
|
|
|
```
|
|
Port 422
|
|
ListenAddress 192.168.10.20 # br0
|
|
ListenAddress 192.168.31.250 # br0-shim
|
|
```
|
|
|
|
So **sshd does not listen on `100.100.208.69`**. Port 422 on the Tailscale IP is
|
|
closed and stays closed unless a `ListenAddress` is added — which needs a go
|
|
script, because Unraid regenerates this file per interface at every boot.
|
|
|
|
Access on the Tailscale IP is served by **Tailscale SSH on port 22**, handled by
|
|
`tailscaled` itself and authenticated by tailnet identity rather than by an SSH
|
|
key:
|
|
|
|
```bash
|
|
# direct, independent of the MikroTik subnet router
|
|
ssh root@100.100.208.69
|
|
ssh root@xtrm-unraid.tailf789e1.ts.net
|
|
|
|
# port forward — verified 2026-11-03 (Unraid web UI returned HTTP 302)
|
|
ssh -N -L 18089:127.0.0.1:80 root@100.100.208.69
|
|
```
|
|
|
|
The tailnet SSH ACL was switched from the default `action: "check"` to
|
|
`accept` on 2026-11-03, so this needs no browser re-auth and works unattended.
|
|
Before the change the policy minted ~12 h grants per device and prompted when
|
|
they lapsed. The effective policy is now a single rule: accept for all tailnet
|
|
nodes, with local and remote port forwarding and agent forwarding allowed.
|
|
|
|
The trade-off: `check` acted as a second factor, so a compromised tailnet device
|
|
now reaches root on Unraid without a further step. Acceptable here because every
|
|
node in the tailnet is the owner's own hardware.
|
|
|
|
### The other path — via the MikroTik subnet router
|
|
|
|
Independent of the Unraid node and still working:
|
|
|
|
```bash
|
|
ssh -i ~/.ssh/id_ed25519_unraid -p 422 -N -L 18088:127.0.0.1:80 root@192.168.10.20
|
|
```
|
|
|
|
Forwarding works here only because Unraid's stock sshd config denies it globally
|
|
and re-enables it for root alone:
|
|
|
|
```
|
|
AllowTcpForwarding no # limetech - disallow forwarding but see below
|
|
...
|
|
Match Group root
|
|
AllowTcpForwarding yes # limetech - permit only root SSH tunneling
|
|
```
|
|
|
|
Any non-root account added later will not be able to open tunnels.
|
|
|
|
### Exit node
|
|
|
|
Approved 2026-11-03. The tailnet now has two exit nodes:
|
|
|
|
| IP | Node |
|
|
|----|------|
|
|
| `100.74.219.35` | `mikrotik-tailscale` (primary) |
|
|
| `100.100.208.69` | `xtrm-unraid` (fallback, 24/7 box) |
|
|
|
|
Select one with `tailscale set --exit-node=xtrm-unraid`. Not exercised in anger
|
|
yet — note that without `--exit-node-allow-lan-access` an exit node cuts the
|
|
client's own LAN access, which will break an SSH session that is itself routed
|
|
over Tailscale.
|