Repository navigation
Open
Feature
0 / 60 of 6 issues completed
Copy link
Description
Problem
Access to piqueld's private routes is decided by tailnet membership alone. Every member reaches every private route, so a QA tester can't be limited to staging. The tailnet also runs on Tailscale's hosted service, behind a Google login, and people's tailnet access is managed outside piqueld.
This is piqueld's part of sourceorigin-dev/infra#7, which moves the infrastructure to a self-hosted Headscale control server, with piqueld as the single place that manages everyone's access.
Model
- Tailnet: one tailnet on a self-hosted control server, on the operator's Pi. piqueld's private ingress keeps working as today: an apps node on the tailnet,
whoisto identify callers, private routes on its address. - Network policy: written in the infra repo. It lets every member reach the apps node (
tag:apps) and nothing else; it can't tell environments apart, since they share one address. - Account link: a tailnet user maps to one piqueld account.
- Private route: reachable by a tailnet node whose linked account holds
apps:accesson the route's environment. - Control plane: the dashboard and API stay behind a passkey session or token. A tailnet node proves network membership, never piqueld access.
- Enrollment: by hand over the operator's SSH until feat(auth): invitations enroll the invitee on the tailnet #344. piqueld never holds a Headscale API key: those have no scopes, so any key is full admin of the network.
Phases
sourceorigin-dev/infra#7 runs in phases. piqueld is involved in two:
- Phase 3, the cutover: feat(tailnet): tailnet nodes on a self-hosted control server #343 alone. It is gated on a Headscale release that ships Serve, which the apps node depends on; Headscale 0.29.7 still lists it as unsupported. Its test against Headscale can be written before that.
- Phase 4, other people: everything else, only once a second person needs access. Until then the operator enrolls people by hand, and nobody but the operator is invited before feat(ingress): private routes need an environment grant #346 is enforced.
Decisions
- Keep the Tailscale integration rather than building a WireGuard access network; Headscale makes it self-hosted.
- The network policy stays in Nix. piqueld never writes it: every member gets the same network reach, and per-environment decisions are piqueld's.
- The first version of environment scopes covers
apps:accessonly; reads and mutations stay application-wide (feat(auth): environment-scoped grants #342). - No Headscale API key in piqueld (feat(auth): invitations enroll the invitee on the tailnet #344).
- Open: environment selectors by name or by label (feat(auth): environment-scoped grants #342); forward-auth or Caddy allowlists for the check (feat(ingress): private routes need an environment grant #346); how feat(auth): invitations enroll the invitee on the tailnet #344 enrolls without an API key.
Roadmap
| Phase | Issue | Depends on |
|---|---|---|
| 3 | #343 tailnet nodes on a self-hosted control server | a Headscale release with Serve |
| 4 | #342 environment-scoped grants | none |
| 4 | #345 link tailnet users to piqueld accounts | #343 |
| 4 | #346 private routes need an environment grant | #342, #345 |
| 4 | #344 invitations enroll the invitee on the tailnet | #343, #345 |
| 4 | #193 pass the account's identity to private routes | #345, #346 |
Docs and NixOS module changes ship with each issue's pull request.
Related
- feat(auth): reusable roles #160: roles would bundle environment-scoped grants.
- feat: multi-node piqueld #322: multi-node piqueld will run over the same tailnet.
Written with Claude Opus 5.5 in Claude Code (via T3 Code).
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Type
Projects
- StatusShow more project fieldsNo status
Activity
[-]feat(access): a WireGuard access network and environment access[/-][+]feat(access): per-environment access on a self-hosted tailnet[/+]