Skip to content

Commit b5bd8b8

Browse files
jawnsyKatie Horne
and
Katie Horne
authored
feat: add guide for running Tailscale in Coder (coder#676)
Co-authored-by: Katie Horne <katie@coder.com>
1 parent 6e9ad03 commit b5bd8b8

File tree

2 files changed

+266
-0
lines changed

2 files changed

+266
-0
lines changed

guides/customization/tailscale.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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.

manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,9 @@
364364
{
365365
"path": "./guides/customization/node.md"
366366
},
367+
{
368+
"path": "./guides/customization/tailscale.md"
369+
},
367370
{
368371
"path": "./guides/customization/vnc.md"
369372
}

0 commit comments

Comments
 (0)