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.
Release / release (push) Successful in 28s

This commit is contained in:
slawek committed 2026-10-26 00:21:13 +01:00
1 parent 2e1eb89f52
commit ec93bc6a2d
12 files changed
+432 -51

No files matched your search

+45 -11
View File
@@ -128,9 +128,9 @@ must have the type shown below; an unknown section or setting is an error.
| `web` | `database` | string | | A PostgreSQL connection string for the refresh tokens; without it, the `vpn-router` database on this host that the web service creates |
| `web` | `trusted_proxies` | list | the address the call came in on | Addresses whose `X-Forwarded-For` header names the client for the `/auth` rate limit and the Client IP of the status |
| `web` | `status_content` | string | `basic` | What `/` and `/status` show: `basic`, `standard` or `full` (see Status web service) |
| `web` | `shell` | boolean | `false` | `/shell` opens a login shell of `shell_user` for a member of `admins` (see The shell) |
| `web` | `shell_user` | string | | The system user the shell runs as, not `root`; required by `shell` |
| `web` | `shell` | boolean | `false` | `/shell` opens a login shell for a member of `admins` with console login (see The shell) |
| `web` | `shell_idle_minutes` | integer | `15` | Minutes without a keystroke after which a shell is closed, 1 to 1440 |
| `ssh` | `ldap_login` | boolean | `false` | sshd accepts the directory password of the console accounts in the directory (see Console login and SSH) |
| `https` | `enabled` | boolean | `true` | Caddy serves the status web service and the Router Manager over HTTPS for `wan.local_fqdn` |
| `https` | `email` | string | | The e-mail address of the Let's Encrypt account |
| `https` | `web_port` | integer | `7444` | HTTPS port of the status web service |
@@ -705,6 +705,7 @@ vpn-router-user otp-remove alice
| `otp-new <name>` | A new TOTP secret and its `otpauth://` URI, issued by `wan.local_fqdn`, for an authenticator app; nothing is stored |
| `otp-enrol <name> <code>` | Stores the secret read from standard input when the code is a current code of it, replacing a secret the user has |
| `otp-remove <name>` | Removes the user's TOTP secret |
| `console <name> (--posix \| --account LOCAL \| --off)` | Switches the console login of a member of `admins` on, as the user's own POSIX account or a local account, or off (see Console login and SSH) |
A user name is 1 to 32 lowercase letters, digits and hyphens, starting with a letter; a
password has 8 to 128 characters; a first or last name has 1 to 64 printable characters; an
@@ -725,6 +726,38 @@ the file.
The certificate is also accepted by road-warrior (IKEv2 EAP-TLS) access, which checks
certificates only, so `disable` does not affect it; `remove` revokes the certificate for both.
### Console login and SSH
A member of `admins` may have console login: the Router Manager's Shell page (see The shell),
and, for the user's own POSIX account, SSH and sudo. Only root on the router switches it, with
`vpn-router-user console` or a token of `vpn-router-token`, as for the group `admins`:
```sh
vpn-router-user console alice --posix
vpn-router-user console bob --account azureuser
vpn-router-user console alice --off
vpn-config-edit set ssh.ldap_login true
```
- **Own POSIX account** (`--posix`): the user's entry becomes a `posixAccount` of the directory
with the user's name, a uid from 20000 up, the local group `vpn-router-console` as its group,
`/home/<name>` and `/bin/bash`. `nslcd` makes it known to the system over slapd's local socket
and checks its password by binding as the user, so a user enrolled for TOTP types the password
followed by a current code, failed logins count towards the lockout, and a disabled user cannot
sign in. The home is created at the first login and kept when the user is removed. Members of
`vpn-router-console` may run any command with sudo, with the same password. A local account of
the same name is refused.
- **Local account** (`--account`): the console runs as an existing local account with a uid from
1000 to 19999, such as the VM's admin user; its SSH keys and sudo rights stay as they are.
- **Off** (`--off`): the system no longer knows the user's own POSIX account, and its sessions
end; its uid is kept for the next time. Leaving `admins`, or removing or renaming the group,
switches the console login of the members off.
While `ssh.ldap_login` is `true`, setup lets sshd accept the password of the members of
`vpn-router-console` (`/etc/ssh/sshd_config.d/vpn-router.conf`); other accounts keep sshd's
settings. A password change is made in the Router Manager or with `vpn-router-user passwd`, not
with `passwd`.
### Managing groups
`vpn-router-group` manages the groups. A group has a name, with the rules of a user name, an
@@ -928,12 +961,12 @@ every service and tunnel while the public status page shows less.
### The shell
While `web.shell` is `true`, `/shell` gives a member of `admins` a login shell on the router, as
the system user `web.shell_user`, for the Router Manager's Shell page. It is off by default and
switched on and off on the router only:
While `web.shell` is `true`, `/shell` gives a member of `admins` whose console login is on (see
Console login and SSH) a login shell on the router, as the user's console account, for the
Router Manager's Shell page. It is off by default and switched on and off on the router only:
```sh
vpn-config-edit set web.shell_user azureuser
vpn-router-user console alice --posix
vpn-config-edit set web.shell true
```
@@ -943,12 +976,12 @@ vpn-config-edit set web.shell true
| `GET /shell?ticket=<ticket>` | a WebSocket handshake | the terminal |
`POST /shell` needs an access token of `/auth`, not one of `vpn-router-token`, and signs its user
in again: a wrong password or code gets 403 and counts as a failed login. The ticket opens one
in again: a wrong password or code gets 403 and counts as a failed login; it gets 403 too while
`web.shell` is `false` or the user's console login is off. The ticket opens one
shell for the same user within 30 seconds. Over the WebSocket, binary messages of the client
are keystrokes, a text message `{"cols", "rows"}` sets the window size, and binary messages of
the router are the terminal's output. The shell is a PAM login session of `web.shell_user`, run
by `systemd-run` in a transient unit `vpn-router-shell-<id>`; sudo works as the user's sudoers
entry allows. It is closed after `web.shell_idle_minutes` without a keystroke, when the shell
the router are the terminal's output. The shell is a PAM login session of the console account,
run by `systemd-run` in a transient unit `vpn-router-shell-<id>`; 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.
@@ -984,7 +1017,7 @@ and answer bodies are JSON; a request body is at most 65536 bytes.
| Request | Body | Answer |
| --- | --- | --- |
| `GET /users` | | `{"users": [{"name", "manager_only", "service", "internal", "enabled", "locked", "certificate", "otp", "first_name", "last_name", "email"}, ...]}` |
| `GET /users` | | `{"users": [{"name", "manager_only", "service", "internal", "enabled", "locked", "certificate", "otp", "console", "first_name", "last_name", "email"}, ...]}`; `console` is `null` or `{"enabled", "account", "local", "uid"}` |
| `POST /users` | `{"name", "password", "first_name", "last_name", "email"}` for a VPN user; `{"name", "password", "email"?}` for a Manager-only account | 201 and the user |
| `PUT /users/<name>` | any of `{"first_name", "last_name", "email"}`; for a Manager-only account only `{"email"}`, `""` removing it | the user |
| `PUT /users/<name>/password` | `{"password": ...}` | the user |
@@ -996,6 +1029,7 @@ and answer bodies are JSON; a request body is at most 65536 bytes.
| `POST /users/<name>/otp` | | `{"name", "secret", "uri"}`, a new TOTP secret; nothing is stored |
| `PUT /users/<name>/otp` | `{"secret", "code"}` | the user, once the code matches the secret |
| `DELETE /users/<name>/otp` | | the user |
| `PUT /users/<name>/console` | `{"enabled", "account"?}`; only with a token of `vpn-router-token` | the user |
A failure is `{"error": ...}` with 400 for an invalid request, 404 for a user that does not
exist or is a service account, 409 for a user that exists and 500 otherwise. `/users` does not