Fix terminal: pass JWT via query param, remove Docker-incompatible IP check
Deploy Dashboard / deploy (push) Failing after 2m11s
Deploy Dashboard / deploy (push) Failing after 2m11s
This commit is contained in:
@@ -5,16 +5,22 @@ import signal
|
|||||||
import struct
|
import struct
|
||||||
import fcntl
|
import fcntl
|
||||||
import termios
|
import termios
|
||||||
from fastapi import WebSocket, WebSocketDisconnect, Depends
|
import select
|
||||||
import auth
|
from fastapi import WebSocket, WebSocketDisconnect, Query
|
||||||
|
import jwt
|
||||||
|
import config
|
||||||
|
|
||||||
async def ws_endpoint(websocket: WebSocket, user: str = Depends(auth.get_current_user_ws)):
|
|
||||||
# Block non-Tailscale IPs (Defense in depth)
|
async def ws_endpoint(websocket: WebSocket, token: str = Query("")):
|
||||||
client_ip = websocket.client.host if websocket.client else ""
|
# Verify auth token
|
||||||
if not client_ip.startswith("100."):
|
try:
|
||||||
await websocket.close(code=1008, reason="Tailscale only")
|
payload = jwt.decode(token, config.SECRET_KEY, algorithms=[config.ALGORITHM])
|
||||||
|
if payload.get("sub") != config.ADMIN_USER:
|
||||||
|
await websocket.close(code=1008, reason="Unauthorized")
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
await websocket.close(code=1008, reason="Unauthorized")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
master_fd, slave_fd = pty.openpty()
|
master_fd, slave_fd = pty.openpty()
|
||||||
@@ -30,13 +36,13 @@ async def ws_endpoint(websocket: WebSocket, user: str = Depends(auth.get_current
|
|||||||
os.execvp("/bin/bash", ["/bin/bash"])
|
os.execvp("/bin/bash", ["/bin/bash"])
|
||||||
|
|
||||||
os.close(slave_fd)
|
os.close(slave_fd)
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
|
|
||||||
async def read_pty():
|
async def read_pty():
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.01)
|
||||||
if select_readable(master_fd):
|
r, _, _ = select.select([master_fd], [], [], 0)
|
||||||
|
if r:
|
||||||
data = os.read(master_fd, 4096)
|
data = os.read(master_fd, 4096)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
@@ -44,11 +50,6 @@ async def ws_endpoint(websocket: WebSocket, user: str = Depends(auth.get_current
|
|||||||
except (OSError, WebSocketDisconnect):
|
except (OSError, WebSocketDisconnect):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def select_readable(fd):
|
|
||||||
import select
|
|
||||||
r, _, _ = select.select([fd], [], [], 0)
|
|
||||||
return bool(r)
|
|
||||||
|
|
||||||
read_task = asyncio.create_task(read_pty())
|
read_task = asyncio.create_task(read_pty())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -56,7 +57,6 @@ async def ws_endpoint(websocket: WebSocket, user: str = Depends(auth.get_current
|
|||||||
msg = await websocket.receive()
|
msg = await websocket.receive()
|
||||||
if "bytes" in msg and msg["bytes"]:
|
if "bytes" in msg and msg["bytes"]:
|
||||||
data = msg["bytes"]
|
data = msg["bytes"]
|
||||||
# Handle resize: first byte 0x01 means resize message
|
|
||||||
if data[0:1] == b"\x01" and len(data) == 5:
|
if data[0:1] == b"\x01" and len(data) == 5:
|
||||||
cols = struct.unpack("!H", data[1:3])[0]
|
cols = struct.unpack("!H", data[1:3])[0]
|
||||||
rows = struct.unpack("!H", data[3:5])[0]
|
rows = struct.unpack("!H", data[3:5])[0]
|
||||||
|
|||||||
@@ -38,7 +38,8 @@
|
|||||||
fit.fit();
|
fit.fit();
|
||||||
|
|
||||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
const ws = new WebSocket(`${proto}//${location.host}/ws/terminal`);
|
const token = localStorage.getItem("token") || "";
|
||||||
|
const ws = new WebSocket(`${proto}//${location.host}/ws/terminal?token=${encodeURIComponent(token)}`);
|
||||||
ws.binaryType = "arraybuffer";
|
ws.binaryType = "arraybuffer";
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user