|
| 1 | +--- |
| 2 | +title: Tailscale |
| 3 | +description: Learn how to use Tailscale in your Coder workspace. |
| 4 | +--- |
| 5 | + |
| 6 | +This guide walks you through configuring [Tailscale networking] for use inside |
| 7 | +[Coder workspaces]. With Tailscale networking, you can access services running |
| 8 | +inside Coder and services running on your [tailnet (Tailscale private network)]. |
| 9 | + |
| 10 | +[tailscale networking]: https://tailscale.com/ |
| 11 | +[tailnet (tailscale private network)]: https://tailscale.com/kb/1136/tailnet/ |
| 12 | +[coder workspaces]: ../../workspaces/index.md |
| 13 | + |
| 14 | +## Creating the image |
| 15 | + |
| 16 | +As part of this tutorial, you'll create an image with the following that you'll |
| 17 | +use to create new Coder workspaces. The container image will include: |
| 18 | + |
| 19 | +- The Tailscale daemon (`tailscaled`) |
| 20 | +- A transparent proxy tool (`proxychains4`) |
| 21 | +- Environment variables to control proxy behavior for outbound connections |
| 22 | +- A [SOCKS5 proxy] included in `tailscaled` to facilitate connections to the |
| 23 | + tailnet, listening on `localhost:1080` |
| 24 | +- An [HTTP proxy] included in `tailscaled` to facilitate connections to the |
| 25 | + tailnet, listening on `localhost:3128` |
| 26 | + |
| 27 | +[socks5 proxy]: https://en.wikipedia.org/wiki/SOCKS |
| 28 | +[http proxy]: https://en.wikipedia.org/wiki/Proxy_server#Web_proxy_servers |
| 29 | + |
| 30 | +## Limitations |
| 31 | + |
| 32 | +This guide describes how to install Tailscale in a Ubuntu base image using the |
| 33 | +package manager and running it in userspace networking mode. As such: |
| 34 | + |
| 35 | +- The image (which you will create as part of this tutorial) requires |
| 36 | + [container-based virtual machine] workspaces, so that `systemd` can start the |
| 37 | + Tailscale daemon (`tailscaled`) |
| 38 | +- Users will require `sudo` access to configure the Tailscale tunnel |
| 39 | +- Inbound connections from other devices on the tailnet to your workspace will |
| 40 | + appear to originate from `localhost` |
| 41 | +- Outbound connections to other devices on the tailnet will require support for |
| 42 | + HTTP proxies; otherwise, you'll need to use a wrapper such as `proxychains4` |
| 43 | +- The example in this article applies to Ubuntu 20.04 LTS (Focal Fossa), so you |
| 44 | + may need to adapt it for compatibility with your preferred base image |
| 45 | + |
| 46 | +Tailscale does not require root access to operate in userspace networking mode, |
| 47 | +and the requirement to use [container-based virtual machine] workspaces applies |
| 48 | +only to the instructions in this guide. [Contact our support team] if you are |
| 49 | +interested in using Tailscale in your Coder workspace without root access. |
| 50 | + |
| 51 | +[contact our support team]: ../../feedback.md |
| 52 | +[container-based virtual machine]: ../../workspaces/cvms.md |
| 53 | + |
| 54 | +## Step 1: Create the Dockerfile |
| 55 | + |
| 56 | +In Coder, developer workspaces are defined by a Dockerfile that contains the |
| 57 | +apps, tools, and dependencies that you need to work on the project. |
| 58 | + |
| 59 | +> See our [custom image docs] and Docker’s [guide to writing Dockerfiles] for |
| 60 | +> more information. |
| 61 | +
|
| 62 | +[custom image docs]: ../../images/writing |
| 63 | +[guide to writing dockerfiles]: |
| 64 | + https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ |
| 65 | + |
| 66 | +To simplify creating and maintaining the image, we recommend structuring your |
| 67 | +source code so that files added or modified in the image match the hierarchy of |
| 68 | +the target files, in a subdirectory called `files`: |
| 69 | + |
| 70 | +```text |
| 71 | +. |
| 72 | +├── Dockerfile |
| 73 | +└── files |
| 74 | + ├── etc |
| 75 | + │ ├── apt |
| 76 | + │ │ ├── preferences.d |
| 77 | + │ │ │ └── tailscale |
| 78 | + │ │ └── sources.list.d |
| 79 | + │ │ └── tailscale.list |
| 80 | + │ └── systemd |
| 81 | + │ └── system |
| 82 | + │ └── tailscaled.service.d |
| 83 | + │ └── tailscale.conf |
| 84 | + └── usr |
| 85 | + └── share |
| 86 | + └── keyrings |
| 87 | + └── tailscale.gpg |
| 88 | +``` |
| 89 | + |
| 90 | +While it is possible to configure everything directly in a single Dockerfile, we |
| 91 | +recommend using a folder hierarchy, since this makes it easier to create a |
| 92 | +reproducible image and examine the change history for individual files. |
| 93 | + |
| 94 | +Create the folder hierarchy: |
| 95 | + |
| 96 | +```console |
| 97 | +mkdir --parents \ |
| 98 | + files/etc/apt/preferences.d \ |
| 99 | + files/etc/apt/sources.list.d \ |
| 100 | + files/etc/systemd/system/tailscaled.service.d \ |
| 101 | + files/usr/share/keyrings |
| 102 | +``` |
| 103 | + |
| 104 | +Then, create the following files (we'll walk you through the contents of each in |
| 105 | +the following steps): |
| 106 | + |
| 107 | +```console |
| 108 | +touch files/etc/apt/preferences.d/tailscale \ |
| 109 | + files/etc/apt/sources.list.d/tailscale.list \ |
| 110 | + files/etc/systemd/system/tailscaled.service.d/tailscale.conf |
| 111 | +``` |
| 112 | + |
| 113 | +### Step 1a: Add image repository |
| 114 | + |
| 115 | +Add the Tailscale package repository to `tailscale.list` in the local path |
| 116 | +`files/etc/apt/sources.list.d`. This will appear in `/etc/apt/sources.list.d` in |
| 117 | +the resulting image, with the following contents: |
| 118 | + |
| 119 | +```text |
| 120 | +deb [signed-by=/usr/share/keyrings/tailscale.gpg] https://pkgs.tailscale.com/stable/ubuntu focal main |
| 121 | +``` |
| 122 | + |
| 123 | +This configures `apt` and `apt-get` to install packages from the Tailscale |
| 124 | +repository and verify package signatures with the specified public key. |
| 125 | + |
| 126 | +### Step 1b: (Optional) Configure package pinning |
| 127 | + |
| 128 | +For improved security, you can configure `apt` to deny package installation from |
| 129 | +a given repository by default and allow specific packages by name. To do this, |
| 130 | +create `files/etc/apt/preferences.d/tailscale` with the following contents: |
| 131 | + |
| 132 | +```text |
| 133 | +# Ignore all packages from this repository by default |
| 134 | +Package: * |
| 135 | +Pin: origin pkgs.tailscale.com |
| 136 | +Pin-Priority: 1 |
| 137 | +
|
| 138 | +Package: tailscale |
| 139 | +Pin: origin pkgs.tailscale.com |
| 140 | +Pin-Priority: 500 |
| 141 | +``` |
| 142 | + |
| 143 | +### Step 1c: Add the signing key for Tailscale package repository |
| 144 | + |
| 145 | +Retrieve the signing key from Tailscale, and store the binary (dearmored) key |
| 146 | +file in `files/usr/share/keyrings/tailscale.gpg`: |
| 147 | + |
| 148 | +```console |
| 149 | +curl --silent --show-error --location "https://pkgs.tailscale.com/stable/ubuntu/focal.gpg" | \ |
| 150 | + gpg --dearmor --yes --output=files/usr/share/keyrings/tailscale.gpg |
| 151 | +``` |
| 152 | + |
| 153 | +### Step 1d: Override default `tailscaled` service settings |
| 154 | + |
| 155 | +By default, `tailscaled` will store its internal state in a **state file** |
| 156 | +located at `/var/lib/tailscale/tailscaled.state` (this is is ephemeral in |
| 157 | +Coder). We will need to modify the service settings to: |
| 158 | + |
| 159 | +- Store the state file in the persistent home volume (`/home/coder`) |
| 160 | +- Enable userspace networking |
| 161 | +- Enable the SOCKS5 proxy (optional) |
| 162 | +- Enable the HTTP proxy (optional) |
| 163 | + |
| 164 | +If you do not require outbound connections from the workspace to other services |
| 165 | +running in the tailnet, you may skip the steps where you configure the proxies. |
| 166 | + |
| 167 | +Override the `ExecStart` setting for the `tailscaled` service by saving the |
| 168 | +following to `files/etc/systemd/system/tailscaled.service.d/tailscale.conf`: |
| 169 | + |
| 170 | +```text |
| 171 | +[Service] |
| 172 | +ExecStart= |
| 173 | +ExecStart=-/usr/sbin/tailscaled --state=/home/coder/.config/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock --port $PORT --tun=userspace-networking --socks5-server=localhost:1080 --outbound-http-proxy-listen=localhost:3128 $FLAGS |
| 174 | +``` |
| 175 | + |
| 176 | +### Step 1e: Create the Dockerfile |
| 177 | + |
| 178 | +Create a `Dockerfile`, build it, and push to an external repository, such as |
| 179 | +Docker Hub: |
| 180 | + |
| 181 | +```Dockerfile |
| 182 | +FROM codercom/enterprise-base:ubuntu |
| 183 | + |
| 184 | +USER root |
| 185 | + |
| 186 | +# Copy configuration files to appropriate locations |
| 187 | +COPY files / |
| 188 | + |
| 189 | +ARG DEBIAN_FRONTEND="noninteractive" |
| 190 | + |
| 191 | +RUN apt-get update && \ |
| 192 | + apt-get install --no-install-recommends --yes --quiet \ |
| 193 | + netcat-openbsd \ |
| 194 | + proxychains4 \ |
| 195 | + python3 && \ |
| 196 | + apt-get install --no-install-recommends --yes --quiet \ |
| 197 | + tailscale && \ |
| 198 | + # Delete package cache to avoid consuming space in layer |
| 199 | + apt-get clean && \ |
| 200 | + rm -rf /var/lib/apt/lists/* |
| 201 | + |
| 202 | +USER coder |
| 203 | + |
| 204 | +ENV ALL_PROXY=socks5://localhost:1080 |
| 205 | +ENV http_proxy=http://localhost:3128 |
| 206 | +``` |
| 207 | + |
| 208 | +## Step 2: Authenticate to Tailscale |
| 209 | + |
| 210 | +[Create a workspace] using the container image. Initially, `tailscaled` should |
| 211 | +be running, but it will indicate that it requires authentication: |
| 212 | + |
| 213 | +```console |
| 214 | +systemctl status tailscaled |
| 215 | +``` |
| 216 | + |
| 217 | +Authenticate using `sudo tailscale up`, then verify that other network devices |
| 218 | +are visible: |
| 219 | + |
| 220 | +```console |
| 221 | +tailscale status |
| 222 | +``` |
| 223 | + |
| 224 | +`tailscale` should maintain connectivity across workspace rebuilds, since we |
| 225 | +chose to store the state file in a persistent volume. |
| 226 | + |
| 227 | +[create a workspace]: ../../workspaces/getting-started.md |
| 228 | + |
| 229 | +## Step 3: (Optional) Test Tailscale services |
| 230 | + |
| 231 | +By creating two workspaces from the same image, both authenticated to Tailscale, |
| 232 | +we can verify connectivity works as expected. In one workspace, run the Python |
| 233 | +web server: |
| 234 | + |
| 235 | +```console |
| 236 | +python3 -m http.server 3000 |
| 237 | +``` |
| 238 | + |
| 239 | +In another workspace, verify that `tailscaled` is listening for connections on |
| 240 | +the configured proxy ports: |
| 241 | + |
| 242 | +```console |
| 243 | +ss -nltp |
| 244 | +``` |
| 245 | + |
| 246 | +Check that the `http_proxy` environment variable is set to the address of the |
| 247 | +local `tailscaled` proxy: |
| 248 | + |
| 249 | +```console |
| 250 | +env | grep -i proxy |
| 251 | +``` |
| 252 | + |
| 253 | +Run `curl` (which respects the `http_proxy` command) to connect to the webserver |
| 254 | +running in the other workspace. Since we proxy the connection through the local |
| 255 | +`tailscaled` instance, we can use the internal hostname: |
| 256 | + |
| 257 | +```console |
| 258 | +curl http://jawnsy-tailscale-1:3000 |
| 259 | +``` |
| 260 | + |
| 261 | +For applications that do not respect the `http_proxy` or `ALL_PROXY` environment |
| 262 | +variables, consider using a tool like `proxychains4` to intercept the socket |
| 263 | +system calls and transparently route traffic through the proxy. |
0 commit comments