t3code: harden dispatch — dedicated user + validated t3-mint + scoped sudoers
Run t3-dispatch as an unprivileged dedicated user instead of wizard (who has full sudo). Privileged minting goes through /usr/local/bin/t3-mint, which validates the target against /etc/ttyd-user-map before minting as that user; sudoers permits t3-dispatch to run only that wrapper. Compromise of the network-facing service can mint pairing tokens for mapped users at most. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0472f67d49
commit
9f551e3c13
4 changed files with 26 additions and 4 deletions
6
scripts/sudoers-t3-autopair
Normal file
6
scripts/sudoers-t3-autopair
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# The t3-dispatch service (unprivileged user t3-dispatch) may run ONLY the
|
||||
# t3-mint wrapper, as root. t3-mint validates the target user against
|
||||
# /etc/ttyd-user-map and mints a one-time t3 pairing token as that user.
|
||||
# A compromise of the network-facing dispatch service can therefore mint
|
||||
# pairing tokens for already-mapped users at most — never arbitrary root.
|
||||
t3-dispatch ALL=(root) NOPASSWD: /usr/local/bin/t3-mint
|
||||
|
|
@ -4,7 +4,9 @@ After=network.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=wizard
|
||||
# Unprivileged dedicated user; the only privileged action is `sudo t3-mint`
|
||||
# (scoped in /etc/sudoers.d/t3-autopair). Compromise => mint tokens at most.
|
||||
User=t3-dispatch
|
||||
ExecStart=/usr/local/bin/t3-dispatch
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
|
|
|||
|
|
@ -62,9 +62,10 @@ func lookup(ak string) (entry, bool) {
|
|||
// user, via the scoped sudoers entry) and exchanges it at the instance's
|
||||
// /api/auth/bootstrap, relaying the returned t3_session Set-Cookie to the browser.
|
||||
func autoPair(e entry, w http.ResponseWriter, r *http.Request) {
|
||||
out, err := exec.Command("sudo", "-n", "-u", e.OsUser,
|
||||
"t3", "auth", "pairing", "create",
|
||||
"--base-dir", "/home/"+e.OsUser+"/.t3", "--ttl", "5m", "--json").Output()
|
||||
// t3-mint (root, via scoped sudoers) validates the OS user is in
|
||||
// /etc/ttyd-user-map, then mints as that user. The dispatch service itself
|
||||
// runs unprivileged and can invoke nothing else.
|
||||
out, err := exec.Command("sudo", "-n", "/usr/local/bin/t3-mint", e.OsUser).Output()
|
||||
if err != nil {
|
||||
log.Printf("mint for %s failed: %v", e.OsUser, err)
|
||||
http.Error(w, "pairing mint failed", http.StatusInternalServerError)
|
||||
|
|
|
|||
13
scripts/t3-mint
Normal file
13
scripts/t3-mint
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
# Mint a one-time t3 pairing token for a mapped OS user.
|
||||
# Runs as root via the scoped sudoers entry for the t3-dispatch service user.
|
||||
# Validates the requested user is an actual t3 OS user (a value on the RHS of
|
||||
# /etc/ttyd-user-map) before minting as that user. Prints the t3 CLI JSON.
|
||||
set -euo pipefail
|
||||
os_user="${1:-}"
|
||||
[[ "$os_user" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || { echo "invalid user" >&2; exit 2; }
|
||||
# Must be a mapped t3 OS user (RHS of a non-comment "authentik=os" line).
|
||||
awk -F= '!/^[[:space:]]*#/ && NF==2 { gsub(/[[:space:]]/, "", $2); print $2 }' /etc/ttyd-user-map \
|
||||
| grep -qxF "$os_user" || { echo "user not mapped" >&2; exit 3; }
|
||||
exec runuser -u "$os_user" -- /usr/bin/t3 auth pairing create \
|
||||
--base-dir "/home/${os_user}/.t3" --ttl 5m --json
|
||||
Loading…
Add table
Add a link
Reference in a new issue