Files
infrastructure/docs/09-TAILSCALE-VPN.md
T
Kaloyan DanchevandClaude Opus 5 da981a7b40 docs: Unraid exit node approved
Approved in the Tailscale admin console, so xtrm-unraid now shows as
"offers exit node" and appears in tailscale exit-node list. The tailnet has
two exit nodes: mikrotik-tailscale (primary) and xtrm-unraid (fallback).

Neither has been exercised from the Mac. Noted that without
--exit-node-allow-lan-access an exit node cuts the client's own LAN access,
which would break an SSH session routed over Tailscale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-11-03 21:51:49 +02:00

303 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 (`br0-shim` holds `192.168.31.250/32` with one host route to `192.168.31.4`) — 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 default tailnet ACL uses `action: "check"`, so the first connection in each
period opens a browser re-auth prompt. Fine interactively, but it will stall an
unattended tunnel; switching that rule to `accept` for `autogroup:self` in the
admin console removes the prompt.
### 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.