Merge pull request #408 from CyberMind-FR/feature/407-privilege-separated-cookie-install-root

Privilege-separated cookie install: root path-unit (no sudo, CSPN) (#407)
This commit is contained in:
CyberMind 2026-05-28 12:39:29 +02:00 committed by GitHub
commit b43a67b098
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 57 additions and 71 deletions

View File

@ -489,39 +489,22 @@ def _save_cred_state(state: Dict[str, Any]):
def _apply_youtube(poke: CredPoke) -> Dict[str, str]:
"""Install YouTube creds into PeerTube. Cookies use the existing, working
sudo `peertubectl set-youtube-cookies` path (#388). PO-token application is
experimental (needs the peertubectl set-youtube-potoken verb + browser
capture, #401 P4) — stored + reported, applied when that lands."""
"""Apply YouTube creds to PeerTube. The avatar daemon stays UNPRIVILEGED
(CSPN): it writes the cookies to the spool and the root
peertube-cookie-install.path/.service installs them into the LXC (#407) —
no sudo, NoNewPrivileges intact. PO-token application is experimental and
pending the browser capture + a peertubectl verb (#401 P4)."""
results: Dict[str, str] = {}
if poke.cookies:
try:
with open(_PT_COOKIE_SPOOL, "w") as f:
f.write(poke.cookies)
os.chmod(_PT_COOKIE_SPOOL, 0o600)
r = subprocess.run(
["sudo", "-n", _PEERTUBECTL, "set-youtube-cookies", _PT_COOKIE_SPOOL],
capture_output=True, text=True, timeout=90,
)
results["cookies"] = "ok" if r.returncode == 0 else (r.stderr or r.stdout or "failed").strip()
results["cookies"] = "queued" # root path-unit installs + restarts PeerTube
except Exception as e:
results["cookies"] = f"error: {e}"
finally:
try:
os.remove(_PT_COOKIE_SPOOL)
except OSError:
pass
if poke.po_token:
# Experimental: applied once peertubectl gains set-youtube-potoken (P4).
if os.path.exists(_PEERTUBECTL):
r = subprocess.run(
["sudo", "-n", _PEERTUBECTL, "set-youtube-potoken",
poke.po_token, poke.visitor_data or ""],
capture_output=True, text=True, timeout=30,
)
results["po_token"] = "ok" if r.returncode == 0 else "pending (peertubectl verb not deployed)"
else:
results["po_token"] = "pending"
results["po_token"] = "pending" # P4: browser PO-token relay not wired yet
return results
@ -568,7 +551,10 @@ async def cred_poke(service: str, poke: CredPoke, user=Depends(require_jwt)):
"by": user.get("sub", "?"),
}
_save_cred_state(state)
ok = bool(results) and all(v == "ok" for v in results.values())
# Cookies are the primary credential ("queued" = handed to the root installer);
# an experimental po_token "pending" must not fail the poke.
cstat = results.get("cookies")
ok = cstat in ("ok", "queued") or (not poke.cookies and results.get("po_token") == "ok")
return {"success": ok, "service": service, "results": results}

View File

@ -476,21 +476,20 @@ async def import_video(req: VideoImport, user=Depends(require_jwt)):
return {"success": False, "error": result.get("error", "import failed")}
# Spool path the dashboard (secubox) can write and that the narrowly-scoped
# sudoers rule lets peertubectl read as root (see debian/secubox-peertube.sudoers).
# Spool the dashboard (unprivileged secubox) writes; the root-run
# peertube-cookie-install.path/.service (#407) applies it — no sudo here (CSPN).
COOKIES_SPOOL = "/run/secubox/peertube-yt-cookies.txt"
@router.post("/import/cookies")
async def import_cookies(request: Request, user=Depends(require_jwt)):
"""Install operator-supplied YouTube cookies (Netscape format) so yt-dlp
import can fetch YouTube. Body = the cookies.txt text (the SecuBox
WebExtension reads them from the logged-in browser and POSTs here, #401).
"""Queue operator-supplied YouTube cookies (Netscape format) for the import
pipeline. Body = the cookies.txt text (the SecuBox WebExtension / avatar
broker reads them from the logged-in browser and POSTs here, #401/#402).
The dashboard runs unprivileged (secubox); it spools the body to
COOKIES_SPOOL then escalates via a single narrowly-scoped sudoers rule to
`peertubectl set-youtube-cookies`, which writes them into the LXC + enables
the feature + restarts PeerTube."""
The dashboard stays unprivileged: it writes the spool, and the root
peertube-cookie-install.path watches it and installs them into the LXC +
restarts PeerTube (#407). No sudo, NoNewPrivileges intact."""
body = (await request.body()).decode("utf-8", "replace")
if "\t" not in body or "youtube" not in body.lower():
return {"success": False, "error": "body is not a Netscape youtube cookies file"}
@ -500,25 +499,9 @@ async def import_cookies(request: Request, user=Depends(require_jwt)):
os.chmod(COOKIES_SPOOL, 0o600)
except Exception as e:
return {"success": False, "error": f"spool write failed: {e}"}
log.info(f"Installing YouTube cookies ({len(body)} bytes) by {user.get('sub', 'unknown')}")
try:
r = subprocess.run(
["sudo", "-n", PEERTUBECTL, "set-youtube-cookies", COOKIES_SPOOL],
capture_output=True, text=True, timeout=90,
)
ok = r.returncode == 0
return {"success": ok,
"message": "cookies installed; PeerTube restarting" if ok else None,
"error": None if ok else (r.stderr or r.stdout or "install failed").strip()}
except subprocess.TimeoutExpired:
return {"success": False, "error": "timeout"}
except Exception as e:
return {"success": False, "error": str(e)}
finally:
try:
os.remove(COOKIES_SPOOL)
except OSError:
pass
log.info(f"Queued YouTube cookies ({len(body)} bytes) by {user.get('sub', 'unknown')}")
return {"success": True,
"message": "cookies queued; installing into the LXC + restarting PeerTube shortly"}
# ============================================================================

View File

@ -8,7 +8,7 @@ Standards-Version: 4.6.2
Package: secubox-peertube
Architecture: all
Depends: ${misc:Depends}, secubox-core (>= 1.0), python3-uvicorn | python3-pip,
lxc, lxc-templates, nftables, openssl, sudo
lxc, lxc-templates, nftables, openssl
Description: SecuBox PeerTube — federated video platform (native LXC)
Dashboard + FastAPI backend for a PeerTube video-hosting instance
installed NATIVELY inside a dedicated Debian LXC (its own

View File

@ -0,0 +1,13 @@
[Unit]
Description=Watch for spooled YouTube cookies to install into the PeerTube LXC
Documentation=https://github.com/CyberMind-FR/secubox-deb/issues/407
[Path]
# An unprivileged dashboard (secubox-peertube / secubox-avatar) drops the
# Netscape cookies here; this root-run service installs them — no sudo, so the
# dashboards keep NoNewPrivileges (CSPN privilege separation).
PathChanged=/run/secubox/peertube-yt-cookies.txt
Unit=peertube-cookie-install.service
[Install]
WantedBy=paths.target

View File

@ -0,0 +1,11 @@
[Unit]
Description=Install spooled YouTube cookies into the PeerTube LXC (root, #407)
Documentation=https://github.com/CyberMind-FR/secubox-deb/issues/407
[Service]
Type=oneshot
# Runs as root (no User=) so peertubectl can lxc-attach into the container.
# Installs the spooled cookies, then removes the spool (sensitive + avoids
# re-triggering the watching .path unit).
ExecStart=/usr/sbin/peertubectl set-youtube-cookies /run/secubox/peertube-yt-cookies.txt
ExecStartPost=-/bin/rm -f /run/secubox/peertube-yt-cookies.txt

View File

@ -58,6 +58,8 @@ if [ "$1" = "configure" ]; then
systemctl enable secubox-peertube.service || true
systemctl start secubox-peertube.service || true
fi
# Root path-unit (#407) that installs spooled YouTube cookies without sudo.
systemctl enable --now peertube-cookie-install.path 2>/dev/null || true
# Reload nginx to pick up the /api/v1/peertube/ + /peertube/ locations
# and the public vhost. NOTE: PeerTube itself is NOT installed by this

View File

@ -32,9 +32,9 @@ override_dh_auto_install:
# Config example
install -d debian/secubox-peertube/etc/secubox
[ -f conf/peertube.toml.example ] && cp conf/peertube.toml.example debian/secubox-peertube/etc/secubox/peertube.toml.example || true
# Systemd service (host dashboard daemon)
# Systemd units: the unprivileged dashboard daemon + the root cookie-install
# path-unit (#407) that applies spooled cookies without sudo (CSPN).
install -d debian/secubox-peertube/usr/lib/systemd/system
install -m 644 debian/secubox-peertube.service debian/secubox-peertube/usr/lib/systemd/system/
# sudoers: narrowly-scoped escalation for the cookie-relay (#401)
install -d debian/secubox-peertube/etc/sudoers.d
install -m 0440 debian/secubox-peertube.sudoers debian/secubox-peertube/etc/sudoers.d/secubox-peertube
install -m 644 debian/peertube-cookie-install.path debian/secubox-peertube/usr/lib/systemd/system/
install -m 644 debian/peertube-cookie-install.service debian/secubox-peertube/usr/lib/systemd/system/

View File

@ -13,10 +13,10 @@ Restart=on-failure
RestartSec=5
UMask=0000
# NoNewPrivileges intentionally NOT set: the dashboard escalates via a single
# narrowly-scoped sudoers rule (peertubectl set-youtube-cookies) to install
# operator-supplied YouTube cookies into the LXC. NoNewPrivileges=true would
# block sudo. See debian/secubox-peertube.sudoers.
# CSPN: the dashboard stays unprivileged. Cookie installs are spooled to
# /run/secubox/peertube-yt-cookies.txt and applied by the root-run
# peertube-cookie-install.path/.service (#407) — no sudo needed here.
NoNewPrivileges=true
RuntimeDirectory=secubox
RuntimeDirectoryPreserve=yes
RuntimeDirectoryMode=0775

View File

@ -1,9 +0,0 @@
# Installed at /etc/sudoers.d/secubox-peertube (mode 0440).
#
# Lets the unprivileged dashboard user (secubox) install operator-supplied
# YouTube cookies into the PeerTube LXC, for the WebExtension/dashboard cookie
# relay (#401). Tightly scoped: one command + one fixed spool path — the
# dashboard writes the Netscape cookies body to /run/secubox/peertube-yt-cookies.txt
# then runs exactly this command, which copies them into the LXC, enables the
# import cookies feature, and restarts PeerTube.
secubox ALL=(root) NOPASSWD: /usr/sbin/peertubectl set-youtube-cookies /run/secubox/peertube-yt-cookies.txt