Tailscale’s website reads:
A secure network that just works
Zero config VPN. Installs on any device in minutes, manages firewall rules for you, and works from anywhere.
gokrazy-based devices are no exception! This guide shows you how to use Tailscale with gokrazy.
Tailscale’s networking will come in handy when accessing your gokrazy server remotely (no static DHCP leases, port-forwarding and DynDNS required!), or even to secure your communication when gokrazy is connected to an unencrypted WiFi network.
Tailscale currently uses Userspace networking mode on gokrazy, because
for tun
mode, Tailscale currently requires components that gokrazy does not
provide. For accessing the services on your gokrazy installation, the Userspace
networking mode works fine, though 🥳 .
tailscale.com
v1.22.1 or later (latest version used automatically unless you have the package already in go.mod)/perm/
needs to be initialized (instructions use github.com/gokrazy/mkfs
to initialize)
to persist authentication over reboots.Add the Tailscale daemon tailscaled
and CLI tailscale
Go packages to your
gokrazy instance:
gok add tailscale.com/cmd/tailscaled
gok add tailscale.com/cmd/tailscale
# Automatically initialize a file system on the /perm partition on first boot:
gok add github.com/gokrazy/mkfs
Then, open your instance’s config.json
in your editor:
gok edit
And configure Package config: Command-line flags for Option A or Option B:
Option A: interactive authentication
{
"Hostname": "ts",
"Packages": [
"github.com/gokrazy/fbstatus",
"github.com/gokrazy/hello",
"github.com/gokrazy/serial-busybox",
"github.com/gokrazy/breakglass",
"tailscale.com/cmd/tailscaled",
"tailscale.com/cmd/tailscale",
"github.com/gokrazy/mkfs"
],
"PackageConfig": {
"tailscale.com/cmd/tailscale": {
"CommandLineFlags": [
"up"
]
}
}
}
Option B: unattended authentication with auth key
Alternatively, navigate to Tailscale console and open Settings / Keys. Generate auth key.
Include the key to tailscale flags:
{
"Hostname": "ts",
"Packages": [
"github.com/gokrazy/fbstatus",
"github.com/gokrazy/hello",
"github.com/gokrazy/serial-busybox",
"github.com/gokrazy/breakglass",
"tailscale.com/cmd/tailscaled",
"tailscale.com/cmd/tailscale",
"github.com/gokrazy/mkfs"
],
"PackageConfig": {
"tailscale.com/cmd/tailscale": {
"CommandLineFlags": [
"up",
"--auth-key=tskey-AAAAAAAAAAAA-AAAAAAAAAAAAAAAAAAAAAA"
]
}
}
}
Then, deploy as usual:
gok update
Skip this step if you are using option B with auth key.
gok
CLI./user/tailscale
and find the login URL.You are now connected to Tailscale and you can access your gokrazy instance over Tailscale.
Tailscale requires re-authentication periodically. You can disable key expiry from Tailscale console for the gokrazy instance to not require login every 3 months.
(If you only want to connect to services on your gokrazy device, you don’t need this step.)
To make the github.com/stapelberg/dr
package able to connect to addresses on
the tailscale network, we need to enable tailscaled
’s HTTP
proxy
and set the proxy environment variables:
{
"Hostname": "ts",
"Packages": [
"github.com/gokrazy/fbstatus",
"github.com/gokrazy/hello",
"github.com/gokrazy/serial-busybox",
"github.com/gokrazy/breakglass",
"tailscale.com/cmd/tailscaled",
"tailscale.com/cmd/tailscale",
"github.com/gokrazy/mkfs",
"github.com/stapelberg/dr"
],
"PackageConfig": {
"tailscale.com/cmd/tailscale": {
"CommandLineFlags": [
"up"
]
},
"tailscale.com/cmd/tailscaled": {
"CommandLineFlags": [
"--outbound-http-proxy-listen=localhost:9080"
]
},
"github.com/stapelberg/dr": {
"Environment": [
"HTTPS_PROXY=localhost:9080",
"HTTP_PROXY=localhost:9080"
]
}
}
}
If you want to make a program listen on tailscale without listening on any other network interface, you can use the tsnet Tailscale as a library package in your application.
When using tailscale.com/tsnet
, you don’t need to run tailscale up
and
it’s enough to only include tailscale.com/cmd/tailscaled
and your appplication
with tsnet.
There is an example program at github.com/gokrazy/tsnetdemo:
package main
import (
"crypto/tls"
"flag"
"fmt"
"log"
"net/http"
"os"
"tailscale.com/client/tailscale"
"tailscale.com/tsnet"
)
func main() {
os.Setenv("TAILSCALE_USE_WIP_CODE", "true")
// TODO: comment out this line to avoid having to re-login each time you start this program
os.Setenv("TS_LOGIN", "1")
os.Setenv("HOME", "/perm/tsnetdemo")
hostname := flag.String("hostname", "tsnetdemo", "tailscale hostname")
allowedUser := flag.String("allowed_user", "", "the name of a tailscale user to allow")
flag.Parse()
s := &tsnet.Server{
Hostname: *hostname,
}
log.Printf("starting tailscale listener on hostname %s", *hostname)
ln, err := s.Listen("tcp", ":443")
if err != nil {
log.Fatal(err)
}
ln = tls.NewListener(ln, &tls.Config{
GetCertificate: tailscale.GetCertificate,
})
httpsrv := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
who, err := tailscale.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if who.UserProfile.LoginName != *allowedUser || *allowedUser == "" {
err := fmt.Sprintf("you are logged in as %q, but -allowed_user flag does not match!", who.UserProfile.LoginName)
log.Printf("forbidden: %v", err)
http.Error(w, err, http.StatusForbidden)
return
}
fmt.Fprintf(w, "hey there, %q! this message is served via the tsnet package from gokrazy!", who.UserProfile.LoginName)
}),
}
log.Fatal(httpsrv.Serve(ln))
}
--allowed_user
flag to verify that tailscale authentication works as expectedYou can also use TS_AUTHKEY
instead of TS_LOGIN=1
for non-interactive
auth. See Environment variables in Userguide to avoid setting secrets in
your application source code.