diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6872af0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +tailscale/ +build-multi.sh +tailscale.tar diff --git a/Dockerfile b/Dockerfile index 0552e1d..2f2a59a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,18 @@ WORKDIR /go/src/tailscale COPY tailscale/go.mod tailscale/go.sum ./ RUN go mod download +# Pre-build some stuff before the following COPY line invalidates the Docker cache. +RUN go install \ + github.com/aws/aws-sdk-go-v2/aws \ + github.com/aws/aws-sdk-go-v2/config \ + gvisor.dev/gvisor/pkg/tcpip/adapters/gonet \ + gvisor.dev/gvisor/pkg/tcpip/stack \ + golang.org/x/crypto/ssh \ + golang.org/x/crypto/acme \ + nhooyr.io/websocket \ + github.com/mdlayher/netlink \ + golang.zx2c4.com/wireguard/device + COPY tailscale/. . # see build.sh @@ -43,21 +55,17 @@ RUN GOARCH=$TARGETARCH go install -ldflags="\ -X tailscale.com/version.GitCommit=$VERSION_GIT_HASH" \ -v ./cmd/tailscale ./cmd/tailscaled -FROM ghcr.io/tailscale/alpine-base:3.16 +FROM alpine:3.16 -# Set password -ARG TAILSCALE_PASSWORD="Pm36g58CzaLK" -RUN echo "root:$TAILSCALE_PASSWORD" | chpasswd - -RUN apk add --no-cache ca-certificates iptables iproute2 bash sudo openssh +RUN apk add --no-cache ca-certificates iptables iproute2 bash openssh curl jq RUN ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa RUN ssh-keygen -f /etc/ssh/ssh_host_dsa_key -N '' -t dsa COPY --from=build-env /go/bin/* /usr/local/bin/ -ADD sshd_config /etc/ssh/ +COPY sshd_config /etc/ssh/ +COPY tailscale.sh /usr/local/bin EXPOSE 22 -ADD tailscale.sh /usr/local/bin CMD ["/usr/local/bin/tailscale.sh"] diff --git a/README.md b/README.md index ca65203..55d47ea 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Tailscale for Mikrotik Container -This project provides the build and configuration information to run [Tailscale](https://tailscale.com) in [Mikrotik Container](https://help.mikrotik.com/docs/display/ROS/Container). Container is MikroTik's own implementation of Docker(TM), allowing users to run containerized environments within RouterOS. +This project provides build and configuration information to run [Tailscale](https://tailscale.com) in [Mikrotik Container](https://help.mikrotik.com/docs/display/ROS/Container). Container is Mikrotik's own implementation of Docker(TM), allowing users to run containerized environments within RouterOS. -This project is recommended for research and testing purposes only. Running Container currently requires installing the development branch of RouterOS and is unsupported for production use. Testing indicates there are also significant performance impacts: running a unidirectional IPerf UDP test of 50 Mbps via the container on a Mikrotik hAP ac3 consumes ~75% of the router's CPU. +This project is only recommended for research and testing purposes. Testing indicates there are significant performance hurdles: running a unidirectional IPerf UDP test of 50 Mbps via the container on a Mikrotik hAP ac3 consumes ~75% of the router's CPU. ## Instructions @@ -15,36 +15,32 @@ A WAN interface is configured as per default configuration on **ether1** for con ### Build the Docker Image +Note this step is only required if you are uploading an image to your router. See Configuration Step 6. + The build script uses [Docker Buildx](https://docs.docker.com/buildx/working-with-buildx/). 1. In `build.sh` set the PLATFORM shell script variable as required for the target router CPU - see [https://mikrotik.com/products/matrix](https://mikrotik.com/products/matrix) -2. In `Dockerfile` set the following argument. -| Argument | Description | -| ------------------ | ---------------------- | -| TAILSCALE_PASSWORD | Password for root user | - -3. Run `./build.sh` to build the image. The build process will generate a container image file **`tailscale.tar`** +2. Run `./build.sh` to build the image. The build process will generate a container image archive file **`tailscale.tar`** ### Configure the Router -The router must be be running RouterOS v7.5 or later with the container package loaded; this section follows the Mikrotik Container documentation with additional steps to route the LAN subnet via the tailscale container. +The router must be be running RouterOS v7.6 or later with the container package loaded; this section follows the Mikrotik Container documentation with additional steps to route the LAN subnet via the tailscale container. -1. Upload the `tailscale.tar` file to your router. Below we will assume the image is located at `disk1/tailscale.tar` -2. Enable container mode, and reboot. +1. Enable container mode, and reboot. ``` /system/device-mode/update container=yes ``` -3. Create a veth interface for the container. +2. Create a veth interface for the container. ``` /interface/veth add name=veth1 address=172.17.0.2/16 gateway=172.17.0.1 ``` -4. Create a bridge for the container and add veth1 as a port. +3. Create a bridge for the container and add veth1 as a port. ``` /interface/bridge add name=dockers @@ -52,49 +48,60 @@ The router must be be running RouterOS v7.5 or later with the container package /interface/bridge/port add bridge=dockers interface=veth1 ``` -5. Enable routing from the LAN to the Tailscale Network +4. Enable routing from the LAN to the Tailscale Network ``` /ip/route/add dst-address=100.64.0.0/10 gateway=172.17.0.2 ``` -6. Create environment variables as per the list below. +5. Add environment variables as per the list below. | Variable | Description | Comment | | ----------------- | --------------------------------------------- | -------------------------------------------- | -| AUTH_KEY | Tailscale reusable key | Generate the key from the tailscale console. | +| PASSWORD | System root user password | | +| DOMAIN | Tailscale domain | | +| AUTH_KEY | Tailscale reusable key | Generate from the tailscale console | +| API_KEY | Tailscale API key | See Upgrading section below | | ADVERTISE_ROUTES | Comma-separated list of routes to advertise | | -| CONTAINER_GATEWAY | The Container bridge IP address on the router | | +| CONTAINER_GATEWAY | The container bridge IP address on the router | | +| TAILSCALE_ARGS | Additional arguments passed to tailscale | Optional | ``` /container/envs +add name="tailscale" key="PASSWORD" value="xxxxxxxxxxxxxx" +add name="tailscale" key="DOMAIN" value="example.com" add name="tailscale" key="AUTH_KEY" value="tskey-xxxxxxxxxxxxxxxxxxxxxxxx" +add name="tailscale" key="API_KEY" value="tskey-xxxxxxxxxxxxxxxxxxxxxxxx" add name="tailscale" key="ADVERTISE_ROUTES" value="192.168.88.0/24" add name="tailscale" key="CONTAINER_GATEWAY" value="172.17.0.1" +add name="tailscale" key="TAILSCALE_ARGS" value="--accept-routes" ``` -7. Create a container from the tailscale.tar image +6. Create the container + +The container can be created via a registry or using the `tailscale.tar` file. + +a. Container registry + +Configure the registry URL and add the container. ``` -/container add file=disk1/tailscale.tar interface=veth1 envlist=tailscale root-dir=disk1/containers/tailscale hostname=mikrotik dns=8.8.4.4,8.8.8.8 +/container/config +set registry-url=https://ghcr.io tmpdir=disk1/pull + +/container add remote-image=fluent-networks/tailscale-mikrotik:latest interface=veth1 envlist=tailscale root-dir=disk1/containers/tailscale start-on-boot=yes hostname=mikrotik dns=8.8.4.4,8.8.8.8 ``` -If you want to see the container output in the router log add `logging=yes` +b. Tar archive file -8. Optional - configure the container to startup on boot. +Upload the `tailscale.tar` file to your router. Below we will assume the image is located at `disk1/tailscale.tar` ``` -/system/script -add name="tailscale" source= { - :delay 60s - /container - start [find tag="tailscale:tailscale"] -} - -/system/schedule -add name=tailscale on-event=tailscale start-time=startup interval=0 +/container add file=disk1/tailscale.tar interface=veth1 envlist=tailscale root-dir=disk1/containers/tailscale start-on-boot=yes hostname=mikrotik dns=8.8.4.4,8.8.8.8 ``` +If you want to see the container output in the router log add `logging=yes` to the container add command. + ### Start the Container Ensure the container has been extracted and added by verifying `status=stopped` using `/container/print` @@ -105,10 +112,23 @@ Ensure the container has been extracted and added by verifying `status=stopped` ### Verify Connectivity -In the Tailscale console, verify the router is authenticated and enable the subnet routes. Your tailscale hosts should now be able to reach the router's LAN subnet. +In the Tailscale console, check the router is authenticated and enable the subnet routes. Your tailscale hosts should now be able to reach the router's LAN subnet. The container exposes a SSH server for management purposes using root credentials, and can be accessed via the router's tailscale address or the veth interface address. +## Upgrading + +To upgrade, first stop and remove the container. + +``` +/container/stop 0 +/container/remove 0 +``` + +Create a new container as per Step 6. The tailscale.sh script detects if the tailscale machine exists and removes it using the Tailscale API. A new machine is then created with the same hostname. + +In the Tailscale console, check the router is authenticated and enable the subnet routes. + ## Contributing We welcome suggestions and feedback from people interested in integrating Tailscale on the RouterOS platform. Please send a PR or create an issue if you're having any problems. diff --git a/build.sh b/build.sh index 36c6ac8..adffd92 100755 --- a/build.sh +++ b/build.sh @@ -25,21 +25,26 @@ # https://mikrotik.com/products/matrix # PLATFORM="linux/arm/v7" +TAILSCALE_VERSION=1.32.0 +VERSION=0.1.6 set -eu +rm -f tailscale.tar + if [ ! -d ./tailscale/.git ] then - git clone https://github.com/tailscale/tailscale.git + git -c advice.detachedHead=false clone https://github.com/tailscale/tailscale.git --branch v$TAILSCALE_VERSION fi cd tailscale && eval $(./build_dist.sh shellvars) && cd .. docker buildx build \ + --no-cache \ --build-arg VERSION_LONG=$VERSION_LONG \ --build-arg VERSION_SHORT=$VERSION_SHORT \ --build-arg VERSION_GIT_HASH=$VERSION_GIT_HASH \ --platform $PLATFORM \ - -t tailscale:tailscale . + --load -t ghcr.io/fluent-networks/tailscale-mikrotik:$VERSION . -docker save -o tailscale.tar tailscale:tailscale +docker save -o tailscale.tar ghcr.io/fluent-networks/tailscale-mikrotik:$VERSION diff --git a/tailscale.sh b/tailscale.sh index 8d4c16d..75fff53 100755 --- a/tailscale.sh +++ b/tailscale.sh @@ -1,11 +1,19 @@ #!/bin/bash +set -m + +# Enable IP forwarding +echo 'net.ipv4.ip_forward = 1' | tee -a /etc/sysctl.conf +echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.conf +sysctl -p /etc/sysctl.conf + # Prepare run dir if [ ! -d "/var/run/sshd" ]; then mkdir -p /var/run/sshd fi -set -m +# Set root password +echo "root:${PASSWORD}" | chpasswd # Install routes IFS=',' read -ra SUBNETS <<< "${ADVERTISE_ROUTES}" @@ -13,11 +21,28 @@ for s in "${SUBNETS[@]}"; do ip route add "$s" via "${CONTAINER_GATEWAY}" done +# Check if this is first-time container startup +if [ ! -f "/var/run/tailscale.sh.pid" ]; then + # Delete all the old devices with this hostname. + IDS=$(curl -sSL "https://api.tailscale.com/api/v2/domain/${DOMAIN}/devices" -u "${API_KEY}:" | jq -r '.[][] | select(.hostname == "'${HOSTNAME}'") | .id' || echo "") + while IFS= read -r id; do + if [[ ! -z "$id" ]]; then + echo "deleting tailscale device: $id"; + curl -sSL -XDELETE -u "${API_KEY}:" "https://api.tailscale.com/api/v2/device/$id"; + fi + done </var/run/tailscale.sh.pid + # Start tailscaled and bring tailscale up /usr/local/bin/tailscaled & until /usr/local/bin/tailscale up \ - --authkey=${AUTH_KEY} \ - --advertise-routes="${ADVERTISE_ROUTES}" + --reset --authkey=${AUTH_KEY} \ + --advertise-routes="${ADVERTISE_ROUTES}" \ + ${TAILSCALE_ARGS} do sleep 0.1 done