Restores the kernel-level isolation the pre-cutover ttyd-session.sh had, but keeps the multi-session lobby UX: - ttyd.service gets `-H X-authentik-username` back. `tmux-attach.sh` reads $TTYD_USER, looks up the local part in /etc/ttyd-user-map, denies the connection (no fallback to wizard) if there's no mapping, otherwise `sudo -n -H -u <os_user> tmux …`. Each Authentik identity → its own Unix user → its own `/tmp/tmux-<uid>/default` socket. - tmux-api scopes every request to the same OS user via the same header. Adds /whoami so the lobby HTML can preflight access and render "logged in as <os_user> (<authentik>)" instead of leaving the user to discover the deny via a reconnect loop. - Commits /etc/ttyd-user-map and the matching /etc/sudoers.d/ttyd-users fragment under files/devvm/ so future operators see one canonical source of truth. Current mappings: vbarzin → wizard, emil.barzin → emo. Adding a user is now: append a line to ttyd-user-map + a NOPASSWD sudoers line + `useradd -m`. README walks through it. No Terraform changes — this is all DevVM-side + lobby JS. |
||
|---|---|---|
| .. | ||
| README.md | ||
| sudoers.d-ttyd-users | ||
| tmux-api.service | ||
| tmux-attach.sh | ||
| ttyd-user-map | ||
| ttyd.service | ||
DevVM terminal files
ttyd + tmux-api on the DevVM (10.0.10.10). ttyd serves the multi-session
lobby on port 7681 and attaches each Authentik identity into its own OS
user's tmux server. tmux-api (port 7684) backs the lobby's list/kill
actions, scoped to the same OS user.
terminal-ro.service (port 7682, single read-only session) and
clipboard-upload (port 7683) are unchanged by these files.
Per-user isolation
The Authentik forward-auth middleware injects X-authentik-username on
every authenticated request:
- ttyd is started with
-H X-authentik-username, so the header value lands as$TTYD_USERin each launchedtmux-attach.shinvocation. tmux-attach.shlooks up$TTYD_USERin/etc/ttyd-user-map, denies the connection if there is no mapping, and otherwisesudo -n -H -u <os_user> /usr/bin/tmux ….tmux-apireadsX-authentik-usernameon every request and runs tmux as the mapped OS user too — so the lobby's session list is the intersection of "your Authentik identity" and "what tmux on that OS user's socket reports".
Different Authentik identities map to different Unix users, which means
different /tmp/tmux-<uid>/default sockets — kernel-level isolation,
not "the API filtered the list".
Adding a new user:
- Append a line to
/etc/ttyd-user-map(canonical atfiles/devvm/ttyd-user-map). - Append
wizard ALL=(<os_user>) NOPASSWD: /usr/bin/tmuxto/etc/sudoers.d/ttyd-users(canonical atfiles/devvm/sudoers.d-ttyd-users). - Ensure the OS user exists (
useradd -m <os_user>).
Layout
| Source | Destination on DevVM | Mode |
|---|---|---|
tmux-attach.sh |
/usr/local/bin/tmux-attach.sh |
0755 |
ttyd.service |
/etc/systemd/system/ttyd.service |
0644 |
tmux-api.service |
/etc/systemd/system/tmux-api.service |
0644 |
ttyd-user-map |
/etc/ttyd-user-map |
0644 |
sudoers.d-ttyd-users |
/etc/sudoers.d/ttyd-users |
0440, root:root |
../index.html (one dir up) |
/usr/local/share/ttyd/index.html |
0644 |
../../tmux-api/ Go binary |
/usr/local/bin/tmux-api |
0755 |
Apply
From the workstation (infra/ repo root):
DEVVM=10.0.10.10 # SSH config provides the user
# 1. Build the tmux-api binary for linux/amd64
( cd infra/stacks/terminal/tmux-api && GOOS=linux GOARCH=amd64 go build -o /tmp/tmux-api . )
# 2. HTML + config files
scp infra/stacks/terminal/files/index.html $DEVVM:/tmp/index.html
scp infra/stacks/terminal/files/devvm/tmux-attach.sh $DEVVM:/tmp/tmux-attach.sh
scp infra/stacks/terminal/files/devvm/ttyd-user-map $DEVVM:/tmp/ttyd-user-map
scp infra/stacks/terminal/files/devvm/sudoers.d-ttyd-users $DEVVM:/tmp/sudoers.d-ttyd-users
ssh $DEVVM "
sudo install -m 0644 /tmp/index.html /usr/local/share/ttyd/index.html
sudo install -m 0755 /tmp/tmux-attach.sh /usr/local/bin/tmux-attach.sh
sudo install -m 0644 /tmp/ttyd-user-map /etc/ttyd-user-map
sudo install -m 0440 -o root -g root /tmp/sudoers.d-ttyd-users /etc/sudoers.d/ttyd-users
sudo visudo -cf /etc/sudoers.d/ttyd-users
rm /tmp/index.html /tmp/tmux-attach.sh /tmp/ttyd-user-map /tmp/sudoers.d-ttyd-users
"
# 3. tmux-api binary
scp /tmp/tmux-api $DEVVM:/tmp/tmux-api
ssh $DEVVM "sudo install -m 0755 /tmp/tmux-api /usr/local/bin/tmux-api && rm /tmp/tmux-api"
# 4. systemd units
scp infra/stacks/terminal/files/devvm/ttyd.service $DEVVM:/tmp/
scp infra/stacks/terminal/files/devvm/tmux-api.service $DEVVM:/tmp/
ssh $DEVVM "
sudo mv /tmp/ttyd.service /etc/systemd/system/
sudo mv /tmp/tmux-api.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now tmux-api
sudo systemctl restart ttyd
"
# 5. Sanity checks
ssh $DEVVM "systemctl status ttyd tmux-api --no-pager"
ssh $DEVVM "curl -sf -H 'X-Authentik-Username: vbarzin' localhost:7684/whoami"
ssh $DEVVM "curl -sf -H 'X-Authentik-Username: emil.barzin' localhost:7684/whoami"
ssh $DEVVM "curl -si -H 'X-Authentik-Username: nobody' localhost:7684/whoami | head -3"
Notes
- ttyd ≥ 1.7 required for the
-aflag (URL args → argv). DevVM has 1.7.7. - Argv flow:
?arg=foo→ ttyd appendsfooas$1totmux-attach.sh→ wrapper regex-validates and runstmux new-session -A -s "$name". ttyd uses argv, never a shell string — no injection path. - No external exposure of 7681/7684 — DevVM is internal-VLAN-only; Authentik forward-auth is the access gate.
- Cutover history —
term.viktorbarzin.meandttyd-multi.service(port 7685) were the staging surface for this design; both retired when the multi-session config was promoted to port 7681. The per-Authentik-user isolation followed in a separate change.