98 lines
3.1 KiB
Markdown
98 lines
3.1 KiB
Markdown
# tsproxy
|
|
|
|
A tiny TCP forwarder that joins your tailnet via [tsnet](https://pkg.go.dev/tailscale.com/tsnet)
|
|
and exposes one or more tailnet services as local ports — without running
|
|
`tailscaled` on the host.
|
|
|
|
Useful when you want to reach a single service on your tailnet from a machine
|
|
you'd rather not fully enroll (work laptop, ephemeral VM, etc). The proxy
|
|
itself becomes a tailnet node, so it can be tightly scoped with tags and ACLs.
|
|
|
|
## Install
|
|
|
|
```sh
|
|
go install github.com/smw/tsproxy@latest # or build locally:
|
|
go build -o /usr/local/bin/tsproxy .
|
|
```
|
|
|
|
## Quick start
|
|
|
|
1. (Optional) In your tailnet policy, add a tag for the proxy and an ACL that limits what it can reach. Example (adapt to your hosts):
|
|
|
|
```json
|
|
"tagOwners": {
|
|
"tag:tsproxy": ["you@example.com"]
|
|
},
|
|
"acls": [
|
|
{ "action": "accept", "src": ["autogroup:members"], "dst": ["*:*"] },
|
|
{ "action": "accept", "src": ["tag:tsproxy"], "dst": ["webhost:80", "dbhost:5432"] }
|
|
]
|
|
```
|
|
|
|
2. Mint a one-shot auth key at
|
|
<https://login.tailscale.com/admin/settings/keys>:
|
|
**not reusable**, **not ephemeral**, tagged `tag:tsproxy`. The key is only
|
|
needed for the first run — tsnet persists its own credentials after that.
|
|
|
|
3. First run:
|
|
|
|
```sh
|
|
TS_AUTHKEY=tskey-auth-... tsproxy \
|
|
--name tsproxy \
|
|
--forward 127.0.0.1:8080=webhost:80 \
|
|
--forward 127.0.0.1:5432=dbhost:5432
|
|
```
|
|
|
|
4. Subsequent runs don't need `TS_AUTHKEY`:
|
|
|
|
```sh
|
|
tsproxy -f 127.0.0.1:8080=webhost:80 -f 127.0.0.1:5432=dbhost:5432
|
|
```
|
|
|
|
Then `curl http://127.0.0.1:8080` hits `webhost:80` on your tailnet.
|
|
|
|
## Flags
|
|
|
|
| Flag | Short | Default | Description |
|
|
|---|---|---|---|
|
|
| `--forward` | `-f` | *(required, repeatable)* | Forward rule `LOCAL=TARGET`, e.g. `127.0.0.1:8080=myhost:80`. |
|
|
| `--name` | `-n` | `tsproxy` | Hostname advertised on the tailnet. |
|
|
| `--dir` | | `~/.config/tsproxy/<name>` | State directory (node identity, keys). |
|
|
| `--verbose` | `-v` | `false` | Verbose tsnet logging. |
|
|
|
|
Target can be any MagicDNS name, short hostname, or tailnet IP.
|
|
|
|
## Running under launchd (macOS)
|
|
|
|
An example LaunchAgent plist is included as
|
|
[dev.tsproxy.plist](dev.tsproxy.plist). Install it per-user:
|
|
|
|
```sh
|
|
cp dev.tsproxy.plist ~/Library/LaunchAgents/
|
|
launchctl load ~/Library/LaunchAgents/dev.tsproxy.plist
|
|
```
|
|
|
|
Manage it:
|
|
|
|
```sh
|
|
launchctl list | grep tsproxy
|
|
launchctl kickstart -k gui/$(id -u)/dev.tsproxy # restart after edits
|
|
launchctl unload ~/Library/LaunchAgents/dev.tsproxy.plist
|
|
tail -f /tmp/tsproxy.log
|
|
```
|
|
|
|
On first run, add the auth key via an `EnvironmentVariables` block in the
|
|
plist, then remove it once the state directory has been populated.
|
|
|
|
## Notes
|
|
|
|
- **One node, many forwards.** All `--forward` rules share a single tsnet
|
|
identity, so you get one device in the admin console and one ACL subject.
|
|
- **Startup is strict.** If any local listener fails to bind, the process
|
|
exits — partial success is confusing under a supervisor.
|
|
- **State directory matters.** Losing `~/.config/tsproxy/<name>/` means the
|
|
node re-registers on next launch and will need a fresh `TS_AUTHKEY`.
|
|
|
|
## License
|
|
|
|
[MIT](LICENSE)
|