Skip to content
tsrelay

Access control

The relay is a tailnet node tagged tag:tsrelay. Access to it comes from grants: the ip part lets a device reach the relay at all, and the app part, the tsrelay.mazetti.me/cap/relay app capability, says what it may do there. A device without a value for the capability has no access; the web UI tells such users exactly that, and shows a grant that would fix it.

A minimal policy

"tagOwners": { "tag:tsrelay": ["autogroup:admin"] },
"grants": [
  // Who may receive callbacks
  { "src": ["autogroup:member"], "dst": ["tag:tsrelay"], "ip": ["tcp:443"],
    "app": { "tsrelay.mazetti.me/cap/relay": [{ "roles": ["receiver"] }] } },
  // Who may send callbacks
  { "src": ["tag:prod-worker"], "dst": ["tag:tsrelay"], "ip": ["tcp:443"],
    "app": { "tsrelay.mazetti.me/cap/relay": [{ "roles": ["sender"] }] } }
]

Senders have to be tailnet devices themselves; How senders reach the relay lists the ways, and what identity the relay sees for each.

Capability values

Each capability value is an object with these fields. Keys are parsed leniently: a key the relay doesn't know is ignored, and the relay logs a warning naming it. Check your grants for typos, because a misspelled key is the same as a missing one: "channel": ["staging"] leaves channels out, which grants every channel.

Field
roles
Any of receiver, sender, admin (see Roles)
channels
Channel patterns the roles apply to; * matches anything, dots included. Absent means ["*"]; an empty list [] matches nothing (see Channels)
maxHooks
Receivers: hooks owned at once (relay default 10)
maxTunnels
Receivers: tunnels attached at once (relay default 5)
maxHookTtl
Receivers: the longest a persistent hook may live, as a Go duration like 720h. Takes effect only when the relay sets a limit with --user-max-hook-ttl (default: unlimited, and then this field does nothing); the grant then raises that limit
public
Receivers: true lets them make their hooks reachable from the internet; see Serving public hooks
requires
Names of grant features a relay must know to apply this value (see Combining grants). No features exist at 1.0

Roles

RoleMay
receiver
Create and manage their own hooks in channels their patterns match, attach tunnels, inspect and replay their own calls
sender
Call any hook whose channel their patterns match. Tagged machines can only ever be senders
admin
List and view the metadata of other users' hooks, and their tunnels, in channels their patterns match, and delete those hooks, which detaches their tunnels. Admins can't edit those hooks, see their public URLs or their calls' queries, headers or bodies, or replay or test them. Only users can be admins: the role does nothing for a tagged machine

Channels

Channels scope senders to receivers:

{ "roles": ["receiver"], "channels": ["staging", "prod.*"] }   // may create hooks in these channels
{ "roles": ["sender"],   "channels": ["staging"] }             // may only call staging hooks

Scope sender grants first, because they are the broad ones.

Leaving channels out grants every channel, the same as ["*"]. An explicit empty list, "channels": [], matches no channel at all: the value grants nothing for its roles.

Combining grants

When several grants match a device, the relay combines them: roles and channels are unioned (channels per role, so a sender grant never widens what the same device may do as a receiver), and each limit is the highest any grant sets, falling back to the relay default. Limits count per user, and only from values that grant the receiver role: a limit on a sender-only or admin-only value is ignored. A grant can therefore only raise a limit, and maxHookTtl can't even do that while the relay has no default lifetime; to restrict most users, lower the relay default and raise it for specific groups.

Each value is checked on its own, and the relay logs why it skips or trims one:

  • A value that is invalid, such as one with a bad channel pattern or a negative limit, is skipped; the other values still apply.
  • A value that names a feature in requires that the relay doesn't know is skipped. This lets a newer policy use features an older relay lacks without that relay applying the value with a narrower meaning than you intended. Grant features are added only in new relay versions.
  • An unknown role is logged and dropped; the rest of the value applies.

Revoking a grant takes effect within one --revalidate-interval (60 s), also for attached tunnels. The details are in §5 of the design document.

Making someone an admin

Admin is a role like the others: grant it to a group in its own grant, or add it to an existing one. For example, to let group:platform receive callbacks and administer the relay:

"groups": { "group:platform": ["alice@example.com", "bob@example.com"] },
"grants": [
  { "src": ["group:platform"], "dst": ["tag:tsrelay"], "ip": ["tcp:443"],
    "app": { "tsrelay.mazetti.me/cap/relay": [{ "roles": ["receiver", "admin"] }] } }
]

Admins get an Admin page in the web UI listing the hooks and tunnels in their channels, as metadata only. An admin without the receiver role starts on the admin page and can't create hooks. The role takes effect on the admin's next request; no relay restart is needed.