diff --git a/ADR.md b/ADR.md index e47cc4f..be84b0c 100644 --- a/ADR.md +++ b/ADR.md @@ -913,7 +913,7 @@ ticket gets the shell. The WebSocket of RFC 6455 is implemented on the standard web service's `http.server`, server side only, without extensions. `systemd-run --pty` starts the shell in a transient unit with a PAM login session, outside the sandbox, and `setsid --ctty` makes the terminal its controlling terminal so window sizes reach the shell. A shell idle for -`web.shell_idle_minutes` is closed, and the journal records every opening and end. The Router +`web.shell_idle_minutes` is closed, and the journal records every opening, end and refusal. The Router Manager checks the session and the WebSocket's origin, and relays the bytes to the router after both handshakes. diff --git a/README.md b/README.md index e2f7fc0..3808ff5 100644 --- a/README.md +++ b/README.md @@ -984,7 +984,7 @@ the router are the terminal's output. The shell is a PAM login session of the co run by `systemd-run` in a transient unit `vpn-router-shell-`; sudo works as for SSH. It is closed after `web.shell_idle_minutes` without a keystroke, when the shell exits, or when the connection closes; the router then sends a close frame with the reason. The journal of `vpn-router-web` records who opened each shell, from which address, in which unit, -when it ended and why. +when it ended and why, and each refused ticket or shell with the reason. ### The /config page diff --git a/debian-package/debian/changelog b/debian-package/debian/changelog index d6e39c7..2be2659 100644 --- a/debian-package/debian/changelog +++ b/debian-package/debian/changelog @@ -1,3 +1,14 @@ +vpn-router (0.23.2-1) unstable; urgency=medium + + fix: the shell of a user's own POSIX account opens on the first setup that sets up console login, and the journal records each refused shell with the reason. + + * Setup restarts vpn-router-web when it adds ldap to the name service + switch, so the web service finds the POSIX accounts of the directory. + * The journal of vpn-router-web records each refused ticket and shell, + with the user, the address and the reason. + + -- Slawomir Koszewski Mon, 02 Nov 2026 22:00:00 +0100 + vpn-router (0.23.1-1) unstable; urgency=medium fix: the shell runs as the console account of each member of admins, a local account or the user's own POSIX account in the directory, which also signs in with SSH and sudo, instead of one web.shell_user. diff --git a/debian-package/src/usr/lib/vpn-router/setup b/debian-package/src/usr/lib/vpn-router/setup index 1cc938e..2eb1a01 100755 --- a/debian-package/src/usr/lib/vpn-router/setup +++ b/debian-package/src/usr/lib/vpn-router/setup @@ -555,8 +555,8 @@ def manage_console(cfg): The local group vpnrouter_ldap.CONSOLE_GROUP is created when missing, and sudo lets its members run any command with their password. nslcd serves the directory's posixAccounts with console login on through the local socket, - and checks their passwords by binding as the user; the passwd line of the name service switch gains ldap, and PAM - creates a home at the first login. While ssh.ldap_login is true, sshd accepts the password of the group's members; + and checks their passwords by binding as the user; the passwd line of the name service switch gains ldap, which + restarts the web service, and PAM creates a home at the first login. While ssh.ldap_login is true, sshd accepts the password of the group's members; a drop-in sshd -t refuses is removed again. """ group = vpnrouter_ldap.CONSOLE_GROUP @@ -601,6 +601,9 @@ def manage_console(cfg): for line in lines] if switched != lines: NSSWITCH.write_text(''.join(switched), encoding='utf-8') + # A running process keeps the name service switch it read first, so the web service's shell would not find + # the console accounts of the directory until it restarts. + run(['systemctl', 'try-restart', 'vpn-router-web.service'], check=False) if 'pam_mkhomedir' not in PAM_SESSION.read_text(encoding='utf-8'): run(['pam-auth-update', '--enable', 'mkhomedir'], check=False) diff --git a/debian-package/src/usr/lib/vpn-router/web b/debian-package/src/usr/lib/vpn-router/web index 99bb0cb..01591bd 100644 --- a/debian-package/src/usr/lib/vpn-router/web +++ b/debian-package/src/usr/lib/vpn-router/web @@ -1193,9 +1193,11 @@ class Handler(BaseHTTPRequestHandler): The ticket must come from POST /shell for the user named by the token. Binary messages of the client are its keystrokes, a text message {"cols", "rows"} sets the window size, and binary messages of the server are the terminal's output. The shell ends after web.shell_idle_minutes without a keystroke, when it exits, or when the - client closes the connection; the server then closes it with the reason. Each opening and end is logged. + client closes the connection; the server then closes it with the reason. Each opening, end and refusal is logged + with its reason. """ def refuse(code, message): + log(f'shell of {name} from {self.client()} refused: {message}') body = json.dumps({'error': message}).encode() + b'\n' self.send_response(code) self.send_header('Content-Type', 'application/json') @@ -1360,6 +1362,8 @@ class Handler(BaseHTTPRequestHandler): elif directory == 'shell': code, document = shell_ticket(claims.get('sub'), payload) if (path, self.command) == ('/shell', 'POST') \ else (404, {'error': f'no route {self.command} {path}'}) + if code != 200: + log(f'shell ticket of {claims.get("sub")} from {self.client()} refused: {document["error"]}') body, kind = json.dumps(document, indent=2).encode() + b'\n', 'application/json' elif directory == 'account': code, document = account(claims['sub'], self.command, path, payload)