Dynamic routing of public HTTPS endpoints to internal ports on your Tailscale-connected development machines.
dialtun
maps server names in the form SERVICE-MACHINE.example.com
(where
example.com
is any domain you control) to internal machines on your Tailnet.
MACHINE
is the exact name of a machine in the Tailscale network. SERVICE
is
mapped to a port number by referencing the labels on a (US-based) telephone
dialpad:
┌─────────────────────────┐ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ │ │ ABC │ │ DEF │ │ │ │ 1 │ │ 2 │ │ 3 │ │ │ └─────┘ └─────┘ └─────┘ │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ GHI │ │ JKL │ │ MNO │ │ │ │ 4 │ │ 5 │ │ 6 │ │ │ └─────┘ └─────┘ └─────┘ │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │PQRS │ │ TUV │ │ WXYZ│ │ │ │ 7 │ │ 8 │ │ 9 │ │ │ └─────┘ └─────┘ └─────┘ │ │ ┌─────┐ │ │ │ │ │ │ │ 0 │ │ │ └─────┘ │ └─────────────────────────┘
A base port number -- 64000, by default -- is added to the first three numbers of the service name. The final result is that a domain name like "agendas-devbox1.dev.example.com" would map to port 64243 (64000 + 243) on the "devbox1" machine on your Tailnet.
See this blog post for more information on the genesis of
dialtun
.
dialtun
does not authenticate incoming connections. Any port in the
configured range (64000-64999, by default) is accessible on the public Internet.
Tailscale ACLs are still consulted, but if you open port 64000 on your machine,
then you should expect that random machines on the Internet will connect to
that port.
In other words, goal of dialtun
is not to create a secure way to talk to
your internal services -- use normal Tailscale sharing and ACLs for that -- the
goal is agility and flexibility. The assumption is that whatever you are
exposing has its own auth model, or is a simple website that is not secret.
Add an ACL tag to your Tailscale Access Controls:
-
Add a new tag to the
tagOwners
list, something like this:// ACL tags. "tagOwners": { "tag:dialtun": [], },
-
Add a section for
dialtun
to theacls
block (assuming you keep the default port range of 64000-64999):"acls": [ // ... // dialtun can access dev server ports on all machines. { "action": "accept", "src": ["tag:dialtun"], "dst": ["autogroup:members:64000-64999"], }, // ... ],
-
(Optionally) Add an ACL test to verify that
dialtun
can access the dev server ports, but not any other ports (such as SSH) on the dev machines (this assumes a dev machine of "devbox1"):"acls": [ // ... // dialtun can access the dev ports on all machines (but not SSH). { "src": "tag:dialtun", "accept": ["devbox1:64243", "devbox1:64999"], "deny": ["devbox1:22"], }, // ... ],
Create a Tailscale auth key. The key should be Reusable, Ephemeral, and include
the dialtun
tag that you defined earlier.
Create (but do not yet deploy, since we need to add secrets) the dialtun
app
on Fly.io:
$ flyctl launch --build-only --image ghcr.io/malyn/dialtun:latest
You can choose an app name, or go with the auto-generated default. The app name
will not be used, assuming that you create the CNAME
mapping in the next step.
Add an IP address to your app, then create a wildcard TLS certificate; both will require adding DNS entries to your DNS server. This is documented on the Fly.io Custom Domains and SSL Certificates page.
Add the Tailscale auth key secret to the app:
$ flyctl secrets import
TS_AUTHKEY=tskey-auth-...
<Ctrl-D>
Using flyctl secrets import
means that your secrets will not show up in your
shell's command history. Press Control-D
on a blank line to complete the
input. flyctl
will respond with "Secrets are staged for the first deployment"
Deploy the app:
$ flyctl deploy
That's it! You can then access internal machines using dialtun
URLs.
This project adheres to the Contributor Covenant Code of Conduct. This describes the minimum behavior expected from all contributors.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.