Tailscale Access Grants
Control who can read, manage, and open shell access through yeet.
Use Tailscale grants to decide who can connect to catch and what they can do after they reach it.
For first setup, keep the policy simple. Let Tailscale admins reach catch on
its RPC port and give those admins every yeet permission. Tailscale's built-in
admin autogroup is autogroup:admin.
{
"tagOwners": {
"tag:yeet": ["autogroup:admin"],
"tag:catch": ["tag:yeet"],
"tag:app": ["tag:yeet"]
},
"grants": [
{
"src": ["autogroup:admin"],
"dst": ["tag:catch"],
"ip": ["tcp:41548"],
"app": {
"yeetrun.com/app/yeet": [
{ "allow": ["read", "manage", "ssh"] }
]
}
},
{
"src": ["tag:catch"],
"dst": ["tag:catch"],
"ip": ["tcp:41548"]
}
]
}
Run yeet init after this policy is active. First setup requires read,
manage, and ssh so one admin grant covers enrollment, validation, and shell
checks.
Use autogroup:admin directly in the grant src. Do not create a
group:yeet-admins entry that contains autogroup:admin; Tailscale policy
groups are for actual user identities when you define them in the policy file.
The second grant lets catch nodes talk to other catch nodes on the same port. Keep it if you run more than one catch host.
read allows observation commands. Use it for status, service information,
logs, events, VM defaults, artifact hashes, and other commands that inspect
catch state without changing it.
manage allows high-trust changes. Use it for deploys, updates, service
removal, yeet rm --clean, config changes, snapshot restore or deletion, VM
lifecycle commands, catch upgrades, Tailscale service changes, and registry
pushes over the tailnet.
ssh allows catch-mediated shell access. Use it for yeet ssh into the catch
host or into a service namespace.
manage does not imply read, and ssh does not imply either one. Include
every permission a role needs.
yeet ssh <vm> only needs read at the yeet permission layer because the VM
guest still enforces normal SSH authentication with its own key. This is
different from yeet ssh into the catch host or a service namespace, which is
authorized by catch and requires ssh.
The tailnet registry endpoint requires manage. The registry is mainly useful
for pushing deploy artifacts, so yeet does not expose a separate read-only
registry permission over the tailnet. Internal loopback reads used by catch stay
local to the host.
Catch embeds tsnet, but the local yeet CLI does not. Tailscale grants decide
what a caller may do after it reaches catch, but your workstation must still be
able to reach the catch tsnet address. In the normal setup, install Tailscale on
the workstation and connect it to the same tailnet.
After the first host is working, split access if you need tighter roles. Keep
the same app capability key and change the src groups and allow lists.
When you define policy-file groups, list the users in those groups directly.
{
"groups": {
"group:yeet-readers": ["reader@example.com"],
"group:yeet-deployers": ["deployer@example.com"],
"group:yeet-shell-admins": ["shell-admin@example.com"]
},
"grants": [
{
"src": ["group:yeet-readers"],
"dst": ["tag:catch"],
"ip": ["tcp:41548"],
"app": {
"yeetrun.com/app/yeet": [
{ "allow": ["read"] }
]
}
},
{
"src": ["group:yeet-deployers"],
"dst": ["tag:catch"],
"ip": ["tcp:41548"],
"app": {
"yeetrun.com/app/yeet": [
{ "allow": ["read", "manage"] }
]
}
},
{
"src": ["group:yeet-shell-admins"],
"dst": ["tag:catch"],
"ip": ["tcp:41548"],
"app": {
"yeetrun.com/app/yeet": [
{ "allow": ["read", "ssh"] }
]
}
}
]
}
If a user matches multiple grants, catch treats the allowed yeet permissions as one combined set.
When catch denies a command, yeet prints the missing permission and links back to this page:
missing yeet permission "manage"; update your Tailscale grant for yeetrun.com/app/yeet:
https://yeetrun.com/docs/security/tailscale-access-grants
Update the matching Tailscale grant, wait for the policy to apply, then rerun the command from a workstation that can reach the catch hostname.