Login Server

The login server lets several people use Rex on one machine, each as their own operating system account, with their own files and permissions. It’s the server mode to use when people shouldn’t share an account.

The login server has no passwords or keys. It identifies people only by how they connect, such as their Tailscale login. Anyone who can reach it that way can use every account the accounts file lists for them. This is only a short-term limitation, we have a full authentication and authorization system planned (including supporting legacy SSH keys), but until then we recommend using something like Tailscale.

How It Works

The login server runs as root, as rex-login-server.service. When a client connects:

  1. The login server identifies the caller, such as by their Tailscale login or their local user ID.
  2. It checks /etc/rex/accounts.jsonc for the accounts that caller may use, and picks one.
  3. It opens a login session for that account through PAM, the same as a console or SSH login, and starts a Rex server as that account. If one is already running, it uses that one instead.
  4. It hands the connection to that server.

Each account’s server keeps running when the login server stops, restarts, or is upgraded, so sessions survive all three. After an upgrade, each one keeps the old version until its sessions end. See What Restarts.

On machines with systemd-logind, an account’s server is the same as that person’s personal server. Sessions they start with rex on the machine and sessions they start from another computer are on the same server.

Accounts File

/etc/rex/accounts.jsonc lists the accounts people may use and who may use each one. It’s JSON, with comments and trailing commas allowed. The file ships empty, which lets nobody in by default.

This example lets Alice use her own account and root. She connects as alice unless she asks for root:

{
  "version": 1,
  "accounts": {
    "alice": { "principals": ["tailscale:alice@example.com"] },
    "root": { "principals": ["tailscale:alice@example.com"] },
  },
  "defaults": { "tailscale:alice@example.com": "alice" },
}

Under accounts, each key is an account name, and principals lists the callers allowed to use it. A caller may use an account if any of its principals match:

PrincipalMatches
tailscale:alice@example.comA person, by their Tailscale login
tailscale:tag:ciA device with the Tailscale tag tag:ci
tailscale:*Any person on the tailnet
tailscale:tag:*Any tagged device
peer:1000A local user, by user ID
identifiedAny caller Rex can identify
anonymousCallers Rex can’t identify, such as on a plain TCP listener

A tagged device never matches a person’s login or tailscale:*.

Any Account

The special account "*" lets its callers use any regular account, meaning any account with a user ID of 1000 or higher, whether or not the file names it. Root and system accounts must always be listed by name.

{
  "version": 1,
  "accounts": {
    "*": { "principals": ["tailscale:*"] },
  },
}

For safety, anonymous can’t appear under "*", and anonymous callers can never use root.

Defaults

When a client doesn’t ask for a specific account, defaults picks one. A default only chooses an account. The caller must still be allowed to use it.

If several defaults match a caller, the most specific one wins: a login or user ID first, then a tag, then a Tailscale wildcard, then identified, then anonymous. Between equally specific matches, the first in the file wins.

With no matching default, a caller allowed exactly one account connects as that account. Otherwise, the client must name the account it wants.

Starting and Reloading

Add at least one account so a user can access, then start the service:

sudo $EDITOR /etc/rex/accounts.jsonc
sudo systemctl enable --now rex-login-server.service

This will enable it to start now and for every reboot. On Alpine, use the OpenRC commands instead.

Reload the server anytime you change the accounts file:

sudo systemctl reload rex-login-server.service

On Alpine, run rc-service rex-login-server reload.

Reloading doesn’t drop connections that are still allowed. It closes connections whose access you removed, but leaves every account’s server and its sessions running.

Package upgrades keep your changes to the accounts file. The login server’s logs are in the journal:

journalctl -u rex-login-server.service

Alpine has no journal, so there the logs are in /var/log/rex-login-server.log.

The login server listens only on /run/rex/login-server.sock, which only root can use. To let people connect from other computers, see Remote Access.

Choosing an Account

To ask for a specific account, put it before the host in the server’s address:

rex -S root@build.example.ts.net ls

The same address works in the Mac app’s Add Remote Host…. Without an account in the address, the caller’s default applies.

Accounts can only be chosen in an https:// or http:// address, so the login server needs a network listener first. Remote Access covers setting one up and connecting to it as each account.

PAM

The login server uses PAM each time it starts a server for an account. PAM decides whether the account may log in at all, such as whether it’s locked or expired, and sets up the login session: resource limits, environment variables, and a systemd-logind session on machines that have one.

Rex has already identified the caller before PAM runs, so its policy never asks for a password, the same as SSH with keys. While /etc/nologin exists, only root may log in. Accounts whose shell is nologin or false are always refused.

The package installs a policy at /etc/pam.d/rex-login-server that fits your distribution: Debian and Ubuntu, Red Hat and its relatives, SUSE, or Alpine. Upgrades keep your changes to it.

To skip PAM, run sudo systemctl edit rex-login-server.service and replace the service’s command line with one that adds --pam=false. Rex then checks /etc/shadow and /etc/nologin itself and opens no login session.

On Alpine, set command_args in /etc/conf.d/rex-login-server instead, to the service’s command line with --pam=false added:

command_args="login-server --listen unix:///run/rex/login-server.sock \
  --accounts /etc/rex/accounts.jsonc --runtime-root /run/rex --log-format text \
  --pam-service rex-login-server --pam=false"

NixOS

The NixOS module writes the accounts file from services.rex.loginServer.accounts and services.rex.loginServer.defaults. It doesn’t create the accounts, so declare them with users.users:

{
  users.users.alice.isNormalUser = true;

  services.rex.loginServer = {
    enable = true;
    accounts.alice.principals = [ "tailscale:alice@example.com" ];
  };
}

To keep the order of defaults, which decides ties, use services.rex.loginServer.accountsFile to load your own file instead.