Stop idle `systemd-logind` sessions to prevent interactive access from unattended terminals. E.g., a laptop left unlocked in a coffee shop, with an SSH session into an internal network resource.
Project description
stop-idle-sessions
Stop idle systemd-logind sessions to prevent interactive access from
unattended terminals. E.g., a laptop left unlocked in a coffee shop, with an
SSH session into an internal network resource.
Background
NIST Special Publication 800-53 "Security and Privacy Controls for Information Systems and Organizations" includes a broadly-worded control, SC-10 "Network Disconnect," which requires:
Terminate the network connection associated with a communications session at the end of the session or after [Assignment: organization-defined time period] of inactivity. Network disconnect applies to internal and external networks. Terminating network connections associated with specific communications sessions includes de-allocating TCP/IP address or port pairs at the operating system level and de-allocating the networking assignments at the application level if multiple application sessions are using a single operating system-level network connection. Periods of inactivity may be established by organizations and include time periods by type of network access or for specific network accesses.
The OpenSSH software provides a general-purpose tool for accessing remote
systems. It includes functionality for remote terminal emulation, execution of
remote commands, tunneling of TCP sessions (e.g., for remote desktop access
with Xvnc), and file copying. This software package intends to address
systemd-logind sessions -- mostly OpenSSH sessions -- as a prime vector for
"communications session[s]" and to "de-allocate [OpenSSH] networking
assignments" after "[established] periods of activity."
Configuration
The application should be run by means of the following command, on a timer (either via SystemD or cron):
/usr/libexec/platform-python -m stop_idle_sessions.main --syslog
The purpose of using /usr/libexec/platform-python rather than
/usr/bin/python3 is to ensure that the base OS version of Python is selected
(i.e., the one which hosts the distro-provided python3-* packages) and NOT an
alternative via /etc/alternatives.
The location to a configuration may be specified with -c or simply by using
the default location, /etc/stop-idle-sessions.conf. It may contain the
following contents to control execution:
# Configuration for stop-idle-sessions. See the README:
# https://github.com/liverwust/stop-idle-sessions/README.md
#
[stop-idle-sessions]
# Threshold for termination of idle sessions, specified in minutes.
timeout = 15
# Set to a comma-separated list of users (default empty) to identify users
# whose sessions should not be affected by session termination.
#excluded-users = root, someone, serviceacct
# Override to 'yes' (default 'no') to prevent stop-idle-sessions from
# actually taking action. Instead, it will simply log the actions that it
# would take.
#dry-run = yes
# Override to 'yes' (default 'no') to force logging to syslog, even when
# run from the command-line. The SystemD unit will run the program with
# --syslog and so this option is probably not necessary.
#syslog = yes
# In the event that --syslog is provided (or syslog = yes is set), AND the
# --verbose flag is provided (or verbose = yes is set), then this option
# can be used to write out debugging information to a file at the given
# location. This is useful because the LOG_USER facility may not write
# less-important messages otherwise. This option is left blank by default,
# which means that no file is used for this purpose.
#debug-log = /var/log/stop-idle-sessions.log
# Override to 'yes' (default 'no') to trigger debug logging, which will
# print diagnostic messages every time that the application is run.
#verbose = yes
Although the timeout is specified, it is still necessary to set up a recurring SystemD timer or cronjob to run the app. It will not run itself.
Runtime Behavior
By design, this stop-idle-sessions package is stateless. It is not intended
to run as a daemon. Instead, it should be configured as a SystemD
one-shot service
(with a corresponding
.timer unit).
Alternatively, it can be run as a cronjob.
erDiagram
"LOGIND SESSION" ||--|| "SYSTEMD SCOPE": corresponds
"SYSTEMD SCOPE" ||--|{ "PROCESS": organizes
"LOGIND SESSION" ||--o| "PROCESS": leads
"PROCESS" ||--o| "TCP TUNNEL": connects
"TCP TUNNEL" ||--o| "PROCESS": listens
"VNC INSTANCE" |o--|| "TCP TUNNEL": listens
Each time it runs, the stop-idle-sessions package builds a relational
representation of system elements similar to the preceding diagram. For each
systemd-logind session meeting certain criteria, it then calculates an
"idleness" metric. This "idleness" is subject to configurable limits for
warning and termination.
Session Leaders
The concept of a Session Leader is critical to the design of this software
package. The Leader attribute of a systemd-logind Session
object
is defined simply as "the PID of the process that registered the session."
There is a lot of significance hiding behind this simple definition.
Every Session Leader can be described as follows: it is a privileged process
(run as root) which has initiated an authentication workflow via Pluggable
Authentication Modules (PAM) on behalf of a user. More specifically, the
pam_systemd module
provides the only mechanism by which a systemd-login session may be
registered.
Here are a few common examples of Session Leader:
/usr/libexec/gdm-session-workerfor local (GDM) graphical logins/usr/bin/loginfor local text-based logins/usr/sbin/vncsessionfor SystemD-mediated VNC sessions (Note: runningvncserver -localhostfrom the command-line does NOT create a separatesystemd-logindsession)/usr/sbin/sshd, specifically the fork which shows up assshd: username [priv]in the process table
All Session Leaders have something else in common: they are the singular
conduit through which users may continue to interact with their session. When
stop-idle-sessions needs to disconnect an idle user, it does so specifically
by terminating a Session Leader process.
Ineligible Sessions
Not every systemd-logind session is checked for idleness. The following types
of session are exempt from the idleness computation and will not be
terminated by stop-idle-sessions enforcement:
-
Graphical sessions. Both local (GDM) and remote (VNC) sessions are exempted from idleness computation and enforcement. Such environments have their own ways to protect idle sessions (e.g., screensavers). They aren't necessarily ignored --- as explained later, they might be referenced when computing idleness of other sessions --- but they themselves are not subject to any scrutiny by
stop-idle-sessions.Note: interestingly, running a
vncserver -localhostcommand does NOT launch a proper graphical session. An Xvnc instance will run inside of the original (SSH) session. Remember that only root can spawn Session Leaders. This isn't a problem --- see the later section which explains how these sessions are handled. -
systemd-logindsessions without an assigned teletype (TTY) or pseudo-teletype (PTY) device. These can be started by, for instance,ssh -T remote-host.remote-domain.com. Typically these are established by programs that are driving a remote system programmatically / noninteractively, and which only need to interact with the remote system thru CLI primitives. For example, Visual Studio Code Remoting will establish this kind of SSH session. Because these "sessions" are noninteractive, there is limited "walk-up" risk and they are therefore not subject to termination. -
systemd-logindsessions belonging to excluded users. If configured to do so,stop-idle-sessionswill ignore any session associated with a list of named user accounts. All SSH sessions associated with these users, even interactive ones (= with an assigned PTY), will be allowed to remain idle indefinitely. -
Any
systemd-logindsession whose Session Leader has been terminated. The preceding section explains the significance of Session Leaders. Things get even more interesting after a Session Leader has exited or been terminated.Each
systemd-logindsession also employs asystemd.scope(a concept related to cgroups) under the hood. Although thesystemd-logindsession tracks a Process Leader, asystemd.scopeis specifically designed NOT to treat any process specially. The manpage specifically states "all processes in the scope are equivalent. The lifecycle of the scope unit is thus not bound to the lifetime of one specific process."When the Leader exits or is terminated, both the
systemd-logindsession andsystemd.scopecontinue to exist. The scope is essentially unchanged, but the session undergoes three observable changes:- Its
Leaderattribute is reset to 0 - Its
Stateis demoted fromactivetoclosing(where it may remain indefinitely until the entiresystemd.scopehas gone away) - Its
TTYvalue, if it had one, will remain unchanged BUT is no longer trustworthy (i.e., it may claimpts/N, even if that device has automatically disappeared or been reassigned to another session)
Although confusing,
stop-idle-sessionsconsiders this behavior to be quite useful. The Session Leader concept basically guarantees that a user (or an attacker) cannot "interact" with the system unless they do so thru the "conduit" provided by a Session Leader. Consider the following sample workflow with some made-up session identifiers:- User connects via SSH.
systemd-logindsession ID=121 is established by its Session Leader, which is an instance ofsshd [priv]- User starts a TMUX session inside of this session (and scope).
- After a while,
stop-idle-sessionsterminates thesshd [priv]Session Leader process. - From the User's perspective: The SSH connection established is immediately disconnected.
- From the Session's perspective:
systemd-logindsession ID=121 undergoes the described changes. It is orphaned/separated from its original Session Leader. Importantly: processes other than the Session Leader continue to run, including TMUX. - User connects again via SSH.
systemd-logindsession ID=122 is established by its Session Leader, which is a new instance ofsshd [priv].- User re-attaches to the TMUX session, which is still running in the old session and scope.
The key point here is that the user --- or an attacker sitting behind them in the coffee shop --- cannot interact with the TMUX (or any other process) in-between steps 5 and 7. Or to put it another way, all of the user's interactions are "gated" by the existence of an interactive
systemd-logindsession (thru a Leader), but there is no limit on their ability to run processes (e.g., TMUX or VNC) in the background indefinitely. - Its
Idleness Calculation for Eligible Sessions
After setting aside the various ineligible sessions, stop-idle-sessions needs
to calculate an idleness metric for each of the remaining systemd-logind
sessions. Depending on the nature of the session and the processes running
inside of it, this information can come from multiple places.
In the context of a non-graphical session, it is obvious that a user's keystrokes should be counted as "activity" on the user's part. To borrow wording from the STIG control: it is nearly certain that a console session which is registering keystrokes has not been left unattended. Similarly, a program which is actively generating new output (such as a compiler or a shell loop) should also reset the idle timer. This information is encoded on the access time (atime) and modification time (mtime) on the teletype (TTY) or pseudo-teletype (PTY) device associated with that session.
Interestingly, the following patterns can be observed empirically — e.g., by
running man 1 inotifywatch against a tty or pty:
| Event | Updates mtime? |
Updates atime? |
|---|---|---|
| Keyboard input | Yes | Yes |
| Program output | Yes | No |
stop-idle-sessions will look for the later of the two times, giving the user
the benefit of the doubt when it comes to usage patterns which involve
continuous output but little or no keyboard input.
Note that programs which are displaying static output will NOT touch the
mtime and will therefore not be successful at extending the idle timeout. For
instance, a console session which is displaying a manpage for longer than the
threshold will be terminated at the timeout interval.
Attribution of Tunneled Graphical Sessions
OpenSSH provides various forwarding features, which can be used to instruct the server to "tunnel" traffic to/from remote locations on behalf of the client. This means that a console session's continued existence might affect more than just the interactive terminal session itself.
A key feature of stop-idle-sessions is the ability to trace through these
established tunnels to find a local Virtual Network Computing (VNC) service. If
it finds one, it can further trace the idleness status of that VNC service by
using information provided by the
X11 Screen Saver Extension.
In doing so, the idle-or-not-idle status of the VNC session is propagated to
the SSH session. So long as a user continues to generate regular input events
(e.g., mouse movement), their tunneled SSH session will also be considered
active by stop-idle-sessions.
Alternatives
The stop-idle-sessions package aims to be cohesive and reliable, but it is
definitely not a "native" way to accomplish SC-10 compliance. There are at
least three major alternatives which any security-conscious team should
consider before choosing this solution. These alternatives were not sufficient
in our case, but they are probably easier to implement and less likely to break
in the future.
TMOUT / autologout
The simplest possible way to terminate an idle non-graphical session is to set the TMOUT shell variable (POSIX shells) and the autologout shell variable (C shells like tcsh).
However, this has a few downsides:
-
POSIX shells offer the
readonlykeyword to lock down the TMOUT setting, but this can be bypassed with a bit of Googling. This creates a disparity between users who know how to bypass the control (plus everyone they tell) vs. those who do not. -
C shell doesn't even have the
readonlykeyword and so theautologoutsetting is trivially overridden. -
The
TMOUTandautologouttimeouts only occur at the shell prompt. So, for example, avimorcatsession will linger indefinitely even if not producing any new output. -
These settings are not sensitive to the existence of tunneled VNC traffic and will terminate such a tunnel without checking for graphical session idleness.
ChannelTimeout
Newer versions of OpenSSH provide a
ChannelTimeout setting
which is much more easily-audited than the TMOUT settings. This setting will
likely be featured in upcoming revisions of the NIST and DISA STIG control
sets. However, this new version (9.2) is not available in either Red Hat
Enterprise Linux (RHEL) major version 8 or 9.
StopIdleSessionSec
NIST S.P. 800-53 is not the only U.S. federal government standard which considers this attack vector. The Defense Information Systems Agency (DISA) Security Technical Implementation Guide (STIG) for Red Hat Enterprise Linux (RHEL) 8 includes the following technical control:
The implementation which is prescribed for this technical control is as follows:
# Verify that RHEL 8 logs out sessions that are idle for 15 minutes with the following command:
$ sudo grep -i ^StopIdleSessionSec /etc/systemd/logind.conf
StopIdleSessionSec=900
# If "StopIdleSessionSec" is not configured to "900" seconds, this is a finding.
This has the major benefit of being built-into SystemD and systemd-logind.
However, there are a few notable downsides:
-
This setting affects all
systemd-logindsessions, including graphical sessions. There is even a known bug in RHEL8 where it terminates the GDM login screen after that becomes "idle" (though this will eventually be fixed). Our team wanted to allow persistent graphical sessions while continuing to rely on screen-lock and/or SSH tunnel termination (for VNC) to protect these sessions. -
For a non-graphical session, such as one launched from the local console or from SSH, the StopIdleSessionSec setting (
man 5 logind.conf) only consults theatime(access time) of the tty or pty device to determine idleness. If theatimeis older than the specified threshold, then the session is terminated. As described in the idleness calculation section, active program output does not update theatime--- only user input (keystrokes) can update theatime. In our case, we wanted to avoid terminating long-running processes like compilers and shell loops, and so the solution needed to respect bothmtimeandatime. -
This implementation terminates an entire session by terminating each individual process associated with the
systemd.scope. This is a reasonable implementation which cleans up theState=closingsessions that would otherwise be left behind by terminating just the Sesssion Leader. However, for the reasons described above (4th bullet), our team actually considers these lingering sessions to be useful for allowing users to continue running persistent processes.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stop_idle_sessions-0.9.1.tar.gz.
File metadata
- Download URL: stop_idle_sessions-0.9.1.tar.gz
- Upload date:
- Size: 55.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bfcb8949444a8d324631931a83ba27d50d71e6283f91a718edbc44607ea535cb
|
|
| MD5 |
aeb4168e9f7adacff148be12eb661507
|
|
| BLAKE2b-256 |
aff52c0108121cb3f7f448c05b1eacccd0166303d5cb73343fde83e821824cbe
|
File details
Details for the file stop_idle_sessions-0.9.1-py3-none-any.whl.
File metadata
- Download URL: stop_idle_sessions-0.9.1-py3-none-any.whl
- Upload date:
- Size: 28.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f82d5a37133bf2be5d831323059f5580bbe03965751c0fb14e98bda9a0d5df2b
|
|
| MD5 |
cc9b9eaac5c7e0e5b0138c67fd3f3f98
|
|
| BLAKE2b-256 |
b6a78bc4a2e24825e375ce27e217db62b41b6b17d4d751b81f80073ba22e8815
|