The map response reader now caps a single message at 256 MiB on the wire and 1 GiB after zstd decompression. The server-chosen uint32 size prefix previously let a malicious control server make us allocate up to 4 GiB before reading any body bytes, and the decoded size was unbounded, so a small zstd frame could expand into gigabytes of JSON. A 16 MB cap has been hit by real production traffic before, so both limits sit far above plausible legitimate sizes. The size-prefixed read moved into a readMapResponseMessage helper, and the newer control/tsp path already enforced both kinds of bounds; this brings the long-poll path it replaces in line, with more generous limits for large tailnets. ts2021.Client.Do additionally caps every noise response body with httpbody.LimitSize, so a malicious or buggy control server can't make us buffer an unbounded response. Client.Do shadows the embedded http.Client's Do method, so register, set-dns, set-device-attr, audit-log, all DoNoiseRequest consumers (webclient, tailnet lock, SSH actions, id-token, feature queries), and the debug CLI get the cap without per-call-site changes, and future noise endpoints get it for free. The cap lives in the new util/httpbody package so other HTTP clients can adopt the same convention: LimitSize looks the size limit up from res.Request's context (a Response knows the Request that produced it), falling back to DefaultMaxSize, 1 MiB, when the context carries no override. It is like io.LimitReader except that reads past the limit fail with an error wrapping httpbody.ErrTooLarge instead of silently truncating, and a body of at most the limit, including one of exactly the limit, reads back without error: the wrapper probes for EOF once the limit is exhausted to tell an exactly-at-limit body from an oversize one. The per-request override, httpbody.WithMaxSize, is a context key, so transports pick it up with no API changes; LimitSizeTo applies an explicit limit ignoring any override. Repeated LimitSize or LimitSizeTo calls replace the previous limit rather than compounding it, so a later call can raise or remove the limit an earlier one set. Responses that stream an unbounded number of individually bounded messages disable the cap with httpbody.WithMaxSize(ctx, 0): the /machine/map long-poll and control/tsp's map session, whose messages are already capped per-message (by readMapResponseMessage and decodeMsg, and by tsp's framedReader and boundedReader). Their non-200 error bodies are not message streams, so those are capped with LimitSizeTo instead. The tailnet lock /tka/init/begin, /tka/sync/offer and /tka/affected-sigs responses can carry per-node key signatures or missing AUMs, which at 100,000 peers reach tens of MB, so they raise the cap to 512 MiB. The per-response io.LimitedReader decoders that silently truncated those responses at 1 or 10 MiB are removed: the transport cap is now the single enforcement point, and it reports oversize bodies instead of truncating them. The /key fetch over plain TLS switches from io.LimitReader to httpbody.LimitSizeTo, so an oversized response reports the problem instead of producing a confusing truncated-JSON error. Thanks to Ben Carman for the report! Updates tailscale/corp#48187 Reported-by: Ben Carman Change-Id: Ibf95e1ab9e4f0d7ef8866e8c26e62ed2a514455a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Caution
Development of tsidp has been moved to https://github.com/tailscale/tsidp and it is no longer maintained here. Please visit the new repository to see the latest updates, file an issue, or contribute.
tsidp - Tailscale OpenID Connect (OIDC) Identity Provider
tsidp is an OIDC Identity Provider (IdP) server that integrates with your Tailscale network. It allows you to use Tailscale identities for authentication in applications that support OpenID Connect, enabling single sign-on (SSO) capabilities within your tailnet.
Prerequisites
- A Tailscale network (tailnet) with magicDNS and HTTPS enabled
- A Tailscale authentication key from your tailnet
- Docker installed on your system
Installation using Docker
Pre-built image
A pre-built tsidp image exists at tailscale/tsidp:unstable.
Building from Source
# Clone the Tailscale repository
git clone https://github.com/tailscale/tailscale.git
cd tailscale
# Build and publish to your own registry
make publishdevtsidp REPO=ghcr.io/yourusername/tsidp TAGS=v0.0.1 PUSH=true
Running the Container
Replace YOUR_TAILSCALE_AUTHKEY with your Tailscale authentication key:
docker run -d \
--name tsidp \
-p 443:443 \
-e TS_AUTHKEY=YOUR_TAILSCALE_AUTHKEY \
-e TAILSCALE_USE_WIP_CODE=1 \
-v tsidp-data:/var/lib/tsidp \
ghcr.io/yourusername/tsidp:v0.0.1 \
tsidp --hostname=idp --dir=/var/lib/tsidp
Verify Installation
docker logs tsidp
Visit https://idp.tailnet.ts.net to confirm the service is running.
Usage Example: Proxmox Integration
Here's how to configure Proxmox to use tsidp for authentication:
-
In Proxmox, navigate to Datacenter > Realms > Add OpenID Connect Server
-
Configure the following settings:
- Issuer URL:
https://idp.velociraptor.ts.net - Realm:
tailscale(or your preferred name) - Client ID:
unused - Client Key:
unused - Default:
true - Autocreate users:
true - Username claim:
email
- Issuer URL:
-
Set up user permissions:
- Go to Datacenter > Permissions > Groups
- Create a new group (e.g., "tsadmins")
- Click Permissions in the sidebar
- Add Group Permission
- Set Path to
/for full admin access or scope as needed - Set the group and role
- Add Tailscale-authenticated users to the group
Configuration Options
The tsidp server supports several command-line flags:
--verbose: Enable verbose logging--port: Port to listen on (default: 443)--local-port: Allow requests from localhost--use-local-tailscaled: Use local tailscaled instead of tsnet--hostname: tsnet hostname--dir: tsnet state directory
Environment Variables
TS_AUTHKEY: Your Tailscale authentication key (required)TS_HOSTNAME: Hostname for thetsidpserver (default: "idp", Docker only)TS_STATE_DIR: State directory (default: "/var/lib/tsidp", Docker only)TAILSCALE_USE_WIP_CODE: Enable work-in-progress code (default: "1")
Support
This is an experimental, work in progress, community project. For issues or questions, file issues on the GitHub repository.
License
BSD-3-Clause License. See LICENSE for details.