Repository navigation
Releases: Radiergummi/tsrelay
Release list
v1.1.1
What's Changed
- Apply the architecture review: one owner per responsibility, less homegrown code by @Radiergummi in #81
Full Changelog: v1.1.0...v1.1.1
v1.1.0
A small release with one fix to how the CLI follows hook changes. Upgrade both the relay and your CLIs to get it; any mix of 1.0 and 1.1 keeps working as before.
Fixed
- Hook changes made while a tunnel was down now reach the CLI when it reconnects. If a hook was renamed or made public while
tsrelay httportsrelay attachwas disconnected, the CLI kept showing the old URL until the hook's next change. On reconnect, the relay now sends each hook's current state, numbered so that it can't undo a newer change that arrives first. This needs a 1.1 relay and a 1.1 CLI. With a 1.0 relay, a 1.1 CLI behaves as 1.0 did. - Website: search results link to the page's URL rather than its
.htmlfile.
Upgrading
Nothing to change. The store format is unchanged, so 1.1.0 and 1.0.0 can be swapped either way. The manifests in deploy/ pin 1.1.0.
Verifying this release
Binaries and the image come with SLSA build provenance and SBOM attestations:
gh attestation verify tsrelay_linux_amd64 --owner Radiergummi
gh attestation verify oci://ghcr.io/radiergummi/tsrelay:1.1.0 --owner RadiergummiWhat's Changed
- Post-v1 polish: search URLs, code-tab labels, 405 pins, outage renames by @Radiergummi in #75
Full Changelog: v1.0.0...v1.1.0
v1.0.0
tsrelay delivers HTTP callbacks from your tailnet to apps running on your laptop. Production calls the relay, laptops dial out to it, and your tailnet policy decides who may send and receive.
v1.0.0 is the first stable release. From now on, changes within 1.x only add things to what you build against: the HTTP API, policy grants, the Tsrelay-* headers and 56x statuses, serve configuration, the CLI (commands, flags, environment variables, exit codes and --json output) and the tunnel protocol. Any v1.x CLI works with any v1.y relay. Compatibility has the full promise, and Security model and limitations says what the relay guarantees and what it doesn't.
Upgrading from 0.6
Hooks carry over: the store format is unchanged, and a relay upgraded from 0.6.0 keeps every hook and setting. Check these before you upgrade:
- The default capability is now
tsrelay.mazetti.me/cap/relay. Relays that set--cap-name(TSRELAY_CAP_NAME) are unaffected. - Grants:
maxHookTTLis nowmaxHookTtl(the old spelling still parses), and"channels": []now matches nothing; leavechannelsout to mean every channel. The relay now logs keys in a grant value it doesn't know, such as a misspelledchannels. serveflags and theirTSRELAY_*variables were renamed for one naming scheme; Configuration lists them all. An old variable left in the environment is ignored.- OAuth client secrets: append
?ephemeral=false&preauthorized=true, or the relay joins your tailnet as an ephemeral device that is removed after being offline. The client needs theauth_keysscope and must be allowed to assigntag:tsrelay. - Kubernetes: the manifests in
deploy/pin1.0.0, meet the Pod Securityrestrictedprofile, and no longer set a namespace (kubectl apply -n <namespace>). - The API is strict about what it doesn't understand: unknown body fields, and unknown query parameters on writes, are refused with
422 unsupported_field; field names match exactly.
New
- Restarts keep their promises. On SIGTERM, calls waiting for a tunnel get
503 shutting_downwithRetry-After: 5, calls already delivered get--shutdown-timeout(20s) to finish, and the CLI reconnects to the restarted relay on its own. - Bounded memory.
--capture-total-bytes(128 MiB) bounds what the inspector keeps of all calls together; Running a relay has a sizing rule. - Ready means ready. The relay gets its TLS certificate before
/readyzreports ready, and a slow disk no longer stalls calls. - Operator docs: sizing, backups, restarts, failover, suggested alerts and a runbook for the 56x statuses.
- Metrics:
tsrelay_build_info,tsrelay_inspector_bytes, and the standard Go and process metrics. The audit log is written whatever--log-levelsays. - CLI:
--jsonerrors carry the relay's problem document, the CLI's own error codes are documented, andhooks deleteandstopreport every failure. A command killed by a signal exits 128+N.
Security
- Call paths longer than 8 KiB are refused (
414 path_too_long). - Responses from your app are served with a
Content-Security-Policysandbox, so a page it serves through a hook can't use the relay's origin. - Header spellings with underscores, such as
X_Forwarded_For, are stripped like the dashed ones. - The CLI prints control characters a sender puts in a path escaped, so a call can't send your terminal escape sequences.
tsrelay updatedownloads the release and its checksum from GitHub itself; the relay only names the version.
Verifying this release
Binaries and the image come with SLSA build provenance and SBOM attestations:
gh attestation verify tsrelay_linux_amd64 --owner Radiergummi
gh attestation verify oci://ghcr.io/radiergummi/tsrelay:1.0.0 --owner RadiergummimacOS binaries are signed and notarized. tsrelay is an independent project, not affiliated with or endorsed by Tailscale Inc.
v0.6.0
tsrelay 0.6 gives the relay's URLs and the CLI's tunnels one owner each. Every route now lives in one place, and the tests go through the handler the relay actually serves. tsrelay http and tsrelay attach run on one shared tunnel session, which fixes several cases where a dropped or renamed hook was handled wrongly. Read Upgrading for the two stricter rules.
Upgrading from 0.5
- Channel names can't end in
.jsonor.html./channels/team.jsonmeans the channelteamas JSON, so a channel namedteam.jsonwould show up as a different channel in the web UI. Creating a hook in such a channel is now refused with 422. Hooks that already exist in one keep working, but their channel page won't load; move them to another channel. - Wrong methods get 405. A request with a method other than GET or HEAD, on a path that exists but doesn't take that method (
PUT /hooks,OPTIONS /hooks), now gets 405 with anAllowheader instead of 404. GET and HEAD still reach the web UI on any path. - No CLI update is needed. A 0.5 CLI works against a 0.6 relay.
Fixed
/channels/team.payments.jsonreturned 404 instead of the channelteam.paymentsas JSON. Any channel name with a dot was affected.tsrelay attachexited when one of its hooks was deleted while it was disconnected, though its other hooks were fine. It now drops that hook and keeps serving the rest.- A hook the relay had just taken away (replaced by another tunnel, revoked or deleted) could be attached again if the tunnel reconnected at that moment. The relay refused it, and
tsrelay httpcould even recreate a hook its owner had just deleted. - Hooks the relay took away stayed in a background tunnel's record, so
tsrelay psstill listed them andtsrelay stop <path>still matched them. tsrelay http --backgroundrecorded the old hook ID when it recreated its hook on the first connect.- The inspector measured time to first byte to when the response status was sent on to the caller. It now measures to when the app's response headers arrived, as the audit log and metrics already did.
- A
.jsonor.htmlon an install script or document (/install.sh.json,/openapi.html) served it under the wrong type. Now it's refused with 406.
Changed
tsrelay attachprints the hook and channel line again after a hook is renamed or its public URL changes, astsrelay httpalways did.- In the inspector, refused calls are marked as refused. When live updates stop, the web UI says why: the stream was refused, or too many are open. Keyboard shortcuts no longer fire while a dialog is open.
- A refused tunnel attach names the hook it was refused for, in the problem's new
hookfield.
What's Changed
- Add a run skill that starts and drives the dev relay by @Radiergummi in #68
- Give the web UI's query views and cache policy one owner each; drop the bundle budget by @Radiergummi in #67
- Web follow-ups: mark refused calls, explain stopped live updates, modal-aware shortcuts by @Radiergummi in #69
- Give a running tunnel one owner: client.Session for http and attach by @Radiergummi in #70
- Give the URL space one owner: api.NewRouter by @Radiergummi in #71
- Make the session the one owner of the hooks it dials by @Radiergummi in #72
Full Changelog: v0.5.0...v0.6.0
v0.5.0
tsrelay 0.5 tightens who can see and do what on a relay. Every access rule now lives in one place, and a test checks every route for every kind of identity. Several access bugs are fixed on the way, and some policies are deliberately stricter, so read Upgrading before you deploy.
Upgrading from 0.4
- Update the CLI. API writes without a body must now be sent as
application/jsontoo. A 0.4 CLI doesn't do that, sotsrelay hooks deleteandtsrelay hooks rotate-urlfail with 415 against a 0.5 relay. Everything else, tunnels included, keeps working. The CLI offers to update when the relay's version differs;tsrelay updatedoes it directly. - Only people are admins. A tagged node whose grant has the
adminrole no longer gets admin rights. Give the admin role to users. - Admin grants are scoped to their channels. An admin sees and deletes only hooks in channels their admin grant's
channelspatterns match. A grant withoutchannelscovers every channel, so only admin grants that list channels are affected. publicis scoped to its grant's channels. A grant that carries"public": trueallows public hooks only in the channels that grant names. A public hook in a channel the owner's public grant doesn't cover stops answering internet calls: it answers 404, like an unknown URL, and the owner still sees those calls in the inspector. A grant withoutchannelscovers every channel, so check only grants withpublicthat list channels, and that they include the channels your public hooks use.GET /now returnspublicas a list of channel patterns.- Event streams need a role. A node with no role on the relay can no longer open event streams, and each node may hold at most 8 at once.
- Building from source: the SBOM and license texts are now built rather than committed.
make buildbuilds them; a plaingo buildbinary works, but has no licenses page ortsrelay licenses. Release binaries and images are unaffected.
Fixed
- Tagged nodes with the admin role could list, read and delete everyone's hooks.
- Admin grants ignored their channel scope.
- Editing, rotating, replaying or testing a hook didn't re-check the owner's receiver role and channel. Owners can still always delete their hooks.
- After a channel change made from one node, a tunnel on another node could keep receiving a channel it isn't granted, for up to 60 seconds.
- A held call could follow a renamed hook and be forwarded with a stale path prefix. Held calls are now routed again from their original address once a tunnel attaches, their sender is identified again, and every delivery checks that the tunnel may still serve the hook.
- Replaying a public call skipped the check that the owner may still receive public calls.
- Event streams were unlimited, unfiltered once opened, and never re-checked who was watching.
- The audit log could record a hook update after that hook's deletion.
- A call whose sender hung up right after the last byte of the response was counted as
sender_canceled. It now counts asok, because the response went out in full.
Changed
- Calls refused because of their channel go into their own list of 10 per hook, marked
refused: true. They no longer push delivered calls out of the inspector or send events. - Status codes are consistent across the API: a hook you can't see is 404, one you can see but may not change is 403, and a setting your grant doesn't allow is a 422 on that field. API responses are
Cache-Control: no-store. - The container image is built on Go 1.27.2.
What's Changed
- Give the hook–tunnel lifecycle one owner by @Radiergummi in #56
- Route hook updates through the lifecycle by @Radiergummi in #57
- Announce hook changes in the order they were made by @Radiergummi in #58
- Put every authorization rule in one access package by @Radiergummi in #59
- Build the SBOM instead of committing it by @Radiergummi in #63
- Keep TestStreamCap from deadlocking on a refused stream's body by @Radiergummi in #64
- Bump golang from 1.27.1-bookworm to 1.27.2-bookworm in the images group across 1 directory by @dependabot[bot] in #60
- Bump golang.org/x/mod from 0.41.0 to 0.42.0 in the go group across 1 directory by @dependabot[bot] in #61
- Bump the web group across 1 directory with 7 updates by @dependabot[bot] in #65
- Give a call's outcome one owner by @Radiergummi in #66
Full Changelog: v0.4.1...v0.5.0
v0.4.1
What's Changed
- Fix the review findings parked after v0.4.0 by @Radiergummi in #48
- Keep showing a page's data when a background refetch fails by @Radiergummi in #49
- Fix the relay's review findings by @Radiergummi in #50
- Fix the CLI's review findings by @Radiergummi in #51
- Fix the flaky TestHeldCallsReleasedOnExpiry by @Radiergummi in #53
- Fix the web UI's review findings by @Radiergummi in #52
- Fix the defects deferred from the pre-release review by @Radiergummi in #54
- Stamp the image's commit, and release only what passes CI by @Radiergummi in #55
Full Changelog: v0.4.0...v0.4.1
v0.4.0
tsrelay 0.4 adds public hooks for third-party webhooks, a reworked web UI, and a CLI that runs tunnels in the background and updates itself. It also changes the API and the error statuses, so read Upgrading before you deploy.
Upgrading from 0.3
-
Reinstall the CLI once. The API moved off
/api/v1, so a 0.3 CLI can't talk to a 0.4 relay, andtsrelay updateis new in this release. Runcurl -fsSL https://<your relay>/install.sh | sh, orirm https://<your relay>/install.ps1 | iexon Windows. From then on,tsrelay updatekeeps the CLI on the relay's version. -
API URLs changed. Every resource now lives at a single URL. Its JSON, the web UI page and its live event stream all come from that URL, picked by
Accept:/(the entry document, formerly/api/v1/me),/hooks,/hooks/{id},/hooks/{id}/calls,/channels, and so on. Each resource has its own event stream, which replaces the separate/api/v1/events.GET /openapi.jsondescribes the API. -
Error statuses changed. Tunnel and transport failures now use their own statuses, so they can't be mistaken for your app's 502, 503 or 504:
Error Was Now tunnel_unavailable503 561 tunnel_overloaded503 562 tunnel_lost502 563 upstream_unreachable502 564 upstream_timeout504 565 body_stalled(new)— 408 Clients that don't know these statuses treat them as 500.
Tsrelay-Errorstill names the error. -
Bodies aren't captured by default. The inspector still records every call's metadata and headers, but keeps request and response bodies only if the hook or the relay opts in. Opt in with
--hook-capture-defaulton the relay, or withcapture: true(--capture) per hook. Replaying a call needs its request body captured.
New
- Public hooks: a hook can be made reachable from the internet through Tailscale Funnel, for webhooks from providers such as Stripe, GitHub or Twilio. Both sides have to opt in. The operator runs
tsrelay serve --funneland grants"public": true, and you runtsrelay http 3000 --public. The public URL is a secret;tsrelay hooks rotate-urlreplaces it. Your app seesTsrelay-Sender-Public: trueon these calls, including replays of them. The relay doesn't verify provider signatures, so your app has to; see SECURITY.md. - Background tunnels:
tsrelay http --backgroundreturns once the tunnel is attached;tsrelay ps,logsandstopmanage it. - Self-update:
tsrelay updatereplaces the CLI with the relay's version, checked against the release's checksum. The CLI offers to update when the versions differ, and admins are told when a newer release exists (--update-check=falseturns that off). - Channels in the web UI:
/channelsand/channels/{name}list hooks by channel, and channel names link there. - Reworked hook form: a URL-prefixed path, a channel picker, duration presets, expiry quick picks, a live preview of the hook, and a "Reachable from" choice for public hooks.
- Explained CLI errors: errors say what went wrong, why, and how to fix it, also as
--json. Shell completions now complete hooks, channels and targets. - Licenses:
tsrelay licenses,/licensesand a footer list the open-source components tsrelay ships, generated from an SBOM that CI checks against the dependencies.
Fixed
tsrelay updatefetches a Windows binary's checksum from the right URL.- A relay's write guard no longer treats public callbacks as API writes.
- Live timestamps in the web UI keep ticking when calls arrive faster than once a second.
tsrelay httpkeeps a reaped hook's public state when it recreates it.- Public tokens are compared in constant time, and every incoming
X-Forwarded-*header is stripped, not only on public calls. - Shell completions work even when a
TSRELAY_*variable is invalid. - A data race between the transport and the call log on aborted calls.
- go-git is updated to 5.19.2. It is only in the SBOM tooling, not the binary.
What's Changed
- Disable go cache by @Radiergummi in #16
- Guard the request body's counts against the transport still reading it by @Radiergummi in #42
- Serve public hooks to the internet through Tailscale Funnel by @Radiergummi in #36
- Keep TestTunnelLostBeforeHeaders from seeing the app's empty answer by @Radiergummi in #41
- Keep relative timestamps live and drop the header's role list by @Radiergummi in #31
- Split the README into "Using tsrelay" and "Running a relay" by @Radiergummi in #30
- Serve the API and web UI from shared, content-negotiated URLs by @Radiergummi in #32
- Make channels navigable in the web UI, backed by /channels resources by @Radiergummi in #34
- Use a 56x status range for tunnel failures, and capture bodies only on opt-in by @Radiergummi in #33
- Add a footer and an open-source licenses page by @Radiergummi in #35
- Explain CLI errors, and complete hooks, channels and targets by @Radiergummi in #37
- Fix the build: check tsrelay licenses' arguments with arity by @Radiergummi in #44
- Update go-git to 5.19.2 by @Radiergummi in #45
- Give async queries in web tests 5 s instead of 1 s by @Radiergummi in #46
- Rework the hook create and edit forms by @Radiergummi in #38
- Run tunnels in the background with --background by @Radiergummi in #39
- Update the CLI to the relay's version, and tell admins about new releases by @Radiergummi in #40
- Fetch a Windows binary's checksum without its .exe in tsrelay update by @Radiergummi in #47
Full Changelog: v0.3.1...v0.4.0
v0.3.1
What's Changed
- Use alternate variables to configure the rcodesign version by @Radiergummi in #15
Full Changelog: v0.3.0...v0.3.1
v0.2.2
What's Changed
- Add OpenTelemetry metrics and tracing, pprof, benchmarks and a performance baseline by @Radiergummi in #3
- Fix the forwarder's port exhaustion and pool copy buffers, and rebuild the CLI on cobra, fang and lipgloss by @Radiergummi in #4
- Replace the server-rendered pages with a React single-page UI by @Radiergummi in #5
- Bump node from 24-bookworm-slim to 25-bookworm-slim by @dependabot[bot] in #7
- Bump the web group in /web with 2 updates by @dependabot[bot] in #9
- Bump the go group across 1 directory with 5 updates by @dependabot[bot] in #8
- Bump react-router from 7.18.4 to 8.4.0 in /web by @dependabot[bot] in #11
- Fix the image build, and tag, attest and SBOM the release image by @Radiergummi in #12
New Contributors
- @dependabot[bot] made their first contribution in #7
Full Changelog: v0.1.0...v0.2.2
v0.1.0
What's Changed
- Label the container image with its source repository by @Radiergummi in #1
New Contributors
- @Radiergummi made their first contribution in #1
Full Changelog: https://github.com/Radiergummi/tsrelay/commits/v0.1.0