Xpra's authentication modules can be useful for:
For more information on the different types of connections, see network. For more generic security information, please see security considerations
SSL mode can also be used for authentication using certificates (see #1252)
When using SSH to connect to a server, encryption and authentication can be skipped: by default the unix domain sockets used by ssh do not use authentication.
Starting with version 6.5, options for individual authentication
modules can be specified using brackets.
ie for starting a seamless server with a
TCP socket protected by a password stored in a
file:
xpra seamless --start=xterm -d auth
--bind-tcp=0.0.0.0:10000,auth=file(filename=password.txt)
Multiple sockets can use different authentication modules, and those modules can more easily be chained:
xpra seamless --start=xterm -d auth \
--bind-tcp=0.0.0.0:10000,auth=hosts,auth=file(filename=password.txt) \
--bind-tcp=0.0.0.0:10001,auth=sys
Xpra supports many authentication modules. Some of these modules require extra dependencies.
| Module | Result | Purpose |
|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
| allow
| always allows the user to login, the username used is the one supplied
by the client | dangerous / only for testing | | none
| always allows the user to login, the username used is the one the
server is running as | dangerous / only for testing | | fail
| always fails authentication, no password required | useful for testing
| | reject
| always fails authentication, pretends to ask for a password | useful
for testing | | env
| matches against an environment variable (XPRA_PASSWORD by
default) | alternative to file module | | password
| matches against a password given as a module option, ie:
auth=password,value=mysecret | alternative to file module |
| multifile
| matches usernames and passwords against an authentication file |
proxy: see password-file below | | file
| compares the password against the contents of a password file, see
password-file below | simple password authentication | | scram
| SCRAM authentication using python-scramp | supports
plaintext files and SCRAM stored-key records | | pam
| linux PAM authentication | Linux system authentication | | win32
| win32security authentication | MS Windows system authentication | |
sys | system authentication | virtual module which will
choose win32 or pam authentication automatically | | sqlite
| sqlite database authentication | #1488
| | sql
| sqlalchemy database authentication | #2288 | | mysql
| MySQL database authentication | #2287 | | capability
| matches values in the capabilities supplied by the client | #3575
| | peercred
| SO_PEERCRED authentication | #1524 | |
hosts
| TCP Wrapper |
#1730
| | exec
| Delegates to an external command | #1690 | | kerberos-password
| Uses kerberos to authenticate a username + password | #1691 | | kerberos-token
| Uses a kerberos ticket to authenticate a client | #1691 | | gss
| Uses a GSS ticket to authenticate a client | #1691 | | oauth
| Uses an OAuth2 Bearer token from websocket HTTP headers or client
capabilities | validates a static token or token introspection endpoint
| | keycloak
| Uses a keycloak token to authenticate a client | #3334 | | ldap
| Uses ldap via python-ldap | #1791 | | ldap3
| Uses ldap via python-ldap3 | #1791 | | u2f
| Universal
2nd Factor | #1789 | | fido2
| FIDO
Alliance | #1789 | | otp
| One Time Password | pyotp | | otpscreen
| Generates a one-time secret and shows it in a local GUI dialog for the
user to type | local secondary-channel confirmation (distinct from
otp) | | http-header
| validate websocket http headers | #4438 |
XPRA_PASSWORD=mysecret xpra seamless --bind-tcp=0.0.0.0:10000,auth=envSOME_OTHER_ENV_VAR_NAME=mysecret xpra seamless --bind-tcp=0.0.0.0:10000,auth=env,name=SOME_OTHER_ENV_VAR_NAMExpra seamless --bind-tcp=0.0.0.0:10000,auth=password,value=mysecretxpra seamless --bind-tcp=0.0.0.0:10000,auth=file,filename=/path/to/mypasswordfile.txtxpra seamless --bind-tcp=0.0.0.0:10000,auth=sqlite,filename=/path/to/userlist.sdbxpra seamless --bind-tcp=0.0.0.0:10000,auth=otpscreen,mode=alphanumeric,count=8,timeout=60Beware when mixing environment variables and password files as the latter may contain a trailing newline character whereas the former often do not.
The otpscreen module accepts the following options:
mode (digits, alpha or
alphanumeric, default digits),
count (number of characters in the generated secret,
default 6), timeout (how long the dialog stays
up, in seconds, default 120), and display
(which display to open the dialog on, default auto which
reuses the server's saved DISPLAY /
WAYLAND_DISPLAY).
The syntax with older versions used a dedicated switch for each socket type:
--auth=MODULE for unix domain sockets and named
pipes--tcp-auth=MODULE for TCP sockets--vsock-auth=MODULE for vsock (#983) etcFor more information on the different socket types, see network examples
By default, challenge-handlers=all which means that the
python client will try all authentication handlers available until one
succeeds. If the server is configured with multiple authentications
modules for the same socket, the client will do the same.
Authenticating as username foo with password
bar using the URI:
xpra attach tcp://foo:bar@host:port/
For a more secure option, storing the password value in a file, with debugging enabled:
echo -n "foo" > ./password.txt
xpra attach tcp://host:port/ --challenge-handlers=file:filename=./password.txt --debug auth
| Module | Behaviour and options |
|-------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| env
| name specifies the environment variable containing the
password
defaults to XPRA_PASSWORD | | file
| filename specifies the file containing the passowrd | |
scram
| SCRAM password proof handler using python-scramp;
legacy-sha1=yes enables SCRAM-SHA-1 | | gss
| use gss-services to specify the name of the security
context | | kerberos
| kerberos-services specifies the valid kerberos services
to connect to
the wildcard * may be used | | prompt
| GUI clients should see a dialog, console users a text prompt | | u2f
| APP_ID specifies the u2f authentication application ID |
| fido2
| APP_ID specifies the FIDO2 authentication application ID
| | uri
| Uses values parsed from the connection string, ie:
tcp://foo:bar@host |
file module, the password-file contains a
single password, the whole file is the password (including any trailing
newline characters). To write a password to a file without the trailing
newline character, you can use
echo -n "thepassword" > password.txtmultifile, the password-file contains a list of
authentication values, see proxy server
- this module is deprecated in favour of the sqlite module
which is much easier to configureThe username can be specified:
xpra attach tcp://username:password@host:port/
When an authentication module is used to secure a single session, many modules will completely ignore the username part, and it can be omitted from the connection string. This can be overriden for some modules.
for connecting to the TCP socket and specifying the
password only:
xpra attach tcp://:password@host:port/
Since the username is ignored, it can also be replaced with any
string of your liking, ie using foobar here:
xpra attach tcp://foobar:password@host:port/
Only the following modules will make use of both the username and
password to authenticate against their respective backend:
kerberos-password, ldap, ldap3,
sys (pam and win32),
sqlite, sql, mysql,
multifile and u2f. In this case, using an
invalid username will cause the authentication to fail.
The username is usually more relevant when authenticating against a proxy server (see authentication details there).
The proxy server needs more than a yes/no answer from authentication: it also needs to know which xpra sessions the authenticated client may reach, and as which uid/gid to spawn (or connect to) the proxy instance.
Today, that lookup is bundled into the authentication module via the
get_sessions() method on SysAuthenticatorBase,
which returns a 5-tuple:
(uid, gid, displays, env_options, session_options)
uid, gid: the system identity the proxy
instance runs asdisplays: the list of display names the user may attach
to (e.g. [":10", ":11"])env_options: extra environment variables applied to the
proxy instance processsession_options: extra session-level options passed to
the proxy instanceThe proxy server iterates over the protocol's authenticator chain
after the challenge passes and uses the first non-empty result (see xpra/server/proxy/server.py).
The default implementation in
SysAuthenticatorBase.get_sessions() performs a
DotXpra socket-directory scan for the authenticated
uid, listing every live xpra socket owned by the user.
Most modules use this default (pam,
ldap, ldap3, password,
peercred, keycloak, kerberos-*,
gss, u2f, fido2,
otp, otpscreen, capability,
env, exec, hosts,
http-header, allow, none,
win32, file).
Three families override it to return data they already store per user:
| Module | Source of session data |
|-------------|-------------------------------------------------------------------------------------------------------------------------|
| multifile | Extra columns in the password file (see the
multifile format in Proxy-Server.md) | | sqlite |
Columns uid, gid, displays, env_options, session_options of
the users table (see xpra/auth/sqlauthbase.py
schema) | | sql | Same schema, via SQLAlchemy | |
mysql | Same schema, against MySQL |
The fail and reject modules deny
authentication outright and therefore never reach session lookup.
--session-registry proxy optionThe proxy server lets you pick the session registry independently of
the authenticator with
--session-registry=NAME[(opt=val,...)] (default
auth):
| Registry | Behaviour |
|-------------|----------------------------------------------------------------------------------------------------------------------|
| auth | Delegates to
authenticator.get_sessions() — the historical behaviour.
multifile/sql* setups need no changes. | |
socket | Performs a DotXpra socket-directory
scan for the authenticated uid — pairs any authenticator with socket
discovery. | | multifile | Reads
username\|password\|uid\|gid\|displays\|env\|session_options
from a file (filename option). Lookup is by username. | |
sqlite | Looks up
(uid, gid, displays, env_options, session_options) from the
users table of an sqlite database (filename
option). | | sql | Same schema, via SQLAlchemy
(uri option). | | mysql | Same schema, against
MySQL (uri option). | | live | Runtime map of
sessions populated by xpra servers that dial out to the proxy at startup
with --register=URI. See below. |
Example: use pam to authenticate but read the per-user
session mapping from an sqlite file:
xpra proxy --bind-tcp=0.0.0.0:14500,auth=pam --session-registry=sqlite(filename=/etc/xpra/users.sdb)
Registry modules live under xpra/server/session_registry/.
live backend and --registerA server can announce itself to a proxy at startup with the
--register=URI option (repeatable). For each URI the server
dials the proxy, authenticates as a client, and sends a hello packet
carrying request=register along with its uuid,
session-name and display.
# proxy side:
xpra proxy --bind-tcp=0.0.0.0:14500 --session-registry=live --auth=password,value=secret
# server side (--session-name names the registered session):
xpra seamless --start=xterm --session-name=demo --register=tcp://:secret@proxy.example.com:14500/
The proxy exposes the registered sessions under the
registered key in xpra info.
To address a specific registered session, pass
--display=NAME to xpra attach:
xpra attach tcp://proxy.example.com:14500/ --display=demo
When exactly one session is registered,
xpra attach tcp://proxy.example.com:14500/ is enough — the
proxy auto-selects it.
By default the name supplied by --display is matched
against each registered session's session-name (and then
its registered displays). The match policy can be changed on the proxy
with the lookup-by option (session-name,
uuid or display).
The proxy never dials out — it only ever accepts inbound connections, which makes it usable in front of NAT-ed servers. After each client is brokered, the server re-registers automatically so the slot stays warm for the next one.
The steps below assume that the client and server have been configured to use authentication:
challenge-handlers option, by default
the client will try the following handlers in the specified order:
uri (whatever password may have been specified in the
connection string), file (if the password-file
option was used), env (if the environment variable is
present), scram, kerberos, gss,
keycloak, u2f and finally
promptxpra infopam, win32,
kerberos-password, ldap and
ldap3) require the actual password to be sent across to
perform the authentication on the server - they therefore use the weak
xor hashing, which is insecurexor hashing so that the password is
protected during the exchange: the system will refuse to send a
xor hashed password unencryptedauth debug logging
may leak some authentication informationFor more information on packets, see network.
A new server-side authentication module is a Python file in xpra/auth/
that defines a class named Authenticator. Two base classes
are provided:
SysAuthenticatorBase
— the minimal base. Use this when the username does not need to map to a
local system account (e.g. token-based or capability-based
authenticators).SysAuthenticator (in the same file) — extends the base
by loading the local pwd entry for
self.username on POSIX. Use this when the module is tied to
system users (pam, peercred,
exec, etc.).The methods most commonly overridden:
| Method | Default | When to override |
|-----------------------------------------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| requires_challenge() | returns True | Return
False for modules that authenticate out of band (e.g.
peercred, hosts, http-header). |
| get_challenge(digests) | Generates a salt and chooses the
strongest compatible digest | Override when the module requires a named
non-HMAC digest or custom challenge payload. | |
get_next_challenge() | returns () | Override
for multi-step challenge protocols. Return
(challenge, digest, prompt) while another client response
is needed. | | get_passwords() /
get_password() | get_passwords returns
(get_password(),); get_password returns
"" | Override one of them to provide the expected
password(s) — used by HMAC challenge verification. | |
do_authenticate(caps) | Validates the challenge response
and calls authenticate_check | Override for non-HMAC flows
(e.g. challenge/response over a different transport, third-party token
verification). | | authenticate_hmac(caps) | Verifies the
HMAC challenge against get_passwords() results | Override
if you need to perform extra checks after a successful HMAC match. | |
get_uid() / get_gid() |
NotImplementedError | Always override. Return the uid/gid
the proxy instance should run as. Use parse_uid /
parse_gid from common.py. | |
get_sessions() | Performs a DotXpra socket
scan for the authenticated uid | Leave alone unless your backend stores
per-user session metadata (see multifile
and sqlauthbase.py
for examples). |
Helpers in xpra/auth/common.py:
SessionData — the
(uid, gid, displays, env_options, session_options) 5-tuple
returned by get_sessions()parse_uid(v) / parse_gid(v) — accept
either a numeric string or a username/group name, with safe
defaultsget_auth_exec_env(display="auto") — environment
dictionary suitable for spawning helper processes (used by
exec and otpscreen)Authenticator instances are constructed by auth_helper.get_auth_module(),
which parses the auth=NAME(opt=value,...) syntax and
imports xpra.auth.<name>. Each socket can chain
multiple authenticators; the first one to require a challenge issues it
and subsequent ones either verify additional caps or contribute to
get_sessions().
Multi-step authenticators keep their state on the
Authenticator instance. After
authenticate(caps) succeeds, the server calls
get_next_challenge(): return () when the
authenticator is complete, or return
(challenge_bytes, digest_name, prompt) to send another
challenge packet. The next client response arrives in
caps["challenge_response"] and is processed by the same
authenticator.
A new client-side challenge handler is a Python file in xpra/challenge/
that defines a class named Handler implementing
AuthenticationHandler.
| Method | Default | When to override |
|---------------------------|----------------------------------------------|--------------------------------------------------------------------------------------------------------|
| get_digests() | abstract | Return the digest names
handled by this module, or () for generic password
handlers. | | handle(challenge, digest, prompt) | abstract
| Return the response bytes/value for the server challenge, or a false
value if the handler cannot answer. | | is_done() | returns
True | Return False for multi-step handlers
that must keep state and handle the next challenge packet. |
For multi-step handlers, keep protocol state on the handler instance.
When handle() returns a response and is_done()
is False, the client keeps that handler at the front of the
handler list so the next server challenge is routed back to it.
HMAC_* and we did
get it wrong before...