Rack::Session::XFile — Atomic File-Based Session Management

Design and implementation

XFile leverages mutex and file locking to guarantee atomic updates to session data. Exclusive create locks are also used to allocate the resource file when generating a new session id (SID). This eliminates race conditions and ensures data consistency for multi-threaded and multi-process applications.

File-based sessions are the fastest and simplest persistent-storage option. To maximize performance, store session files on an SSD, an in-memory filesystem, or a disk-based filesystem with performant caching, such as ZFS. Session files are distributed across 62 directories to minimize access time on some filesystems.

Goals

  1. Simple: We already have a working filesystem; no configuration.

  2. Reliable: We are using a time-tested filesystem and file lock mechanism.

  3. Secure: Session data is always stored on the server under our control.

  4. Performant: Session access is very responsive with memory cache or SSDs.

  5. Persistent: Sessions persist regardless of application crashes or restarts.

How session data is stored and referenced

Session data is marshaled and written to the persistent file on the server associated with the SID. The client cookie (set by the key option) is then assigned the SID.

Request filtering

The request filter uses Rack's skip option to completely bypass session processing for matching requests. When a request matches the filter, a temporary, in-memory session hash is created instead. The filesystem is never accessed.

User agents can be filtered by setting the user_agent_filter option with a regular expression.

This feature was originally implemented to avoid processing sessions for robots. Many sources state that one-third to two-thirds of all web traffic is from bots. However, most robots don't store or forward cookies. Consequently, processing sessions for robots wastes resources and pollutes the session store with needless “one-time” sessions.

By default, SIDs are validated by the request filtering system to detect SID tampering, which could lead to directory traversal exploits. Requests with invalid SIDs are skipped.

Purging expired session files

The XFile session manager does not concern itself with purging expired sessions. This task is much better suited for an out-of-band process, such as cron.

For example, run an hourly cron job that executes find and deletes expired session files. For instance, if the expire_after option is set to 30 days, then delete session files with last-modification times greater than 30 days.

Additionally, files with modification times past an X amount of time but less than expiration could be truncated to free up disk space. This effectively returns an empty session, but will preserve session IDs previously issued to clients. SID preservation may be critical if these session IDs are referenced by another backend system or service, such as an analytics system.

Example cron job to purge sessions older than 30 days

0  *  *  *  *  find /tmp/xfile-sessions -type f -mtime +30 -exec rm -- {} +

Example cron job to clear sessions older than 15 days while maintaining SIDs

0  0  *  *  *  find /tmp/xfile-sessions -type f -mtime +15 -exec cp /dev/null {} \;

Caveat: There is potential for a race condition anytime a process modifies XFile sessions without utilizing file locks. However, in this case with cron, the potential is very small considering the session files to be deleted haven't been accessed in a relatively long time and probably won't be any time soon.

Usage Examples

Basic usage with sessions stored under the application directory

require 'rack-session-xfile'

use Rack::Session::XFile,
  session_dir: File.join(settings.root, 'tmp/sessions'),
  expire_after: 60*60*24*30,
  secure: true,
  key: 'sid'

By default, sessions are stored in the directory named xfile-sessions under the operating system's temporary file path.

Bypass session processing for robots

require 'rack-session-xfile'

use Rack::Session::XFile,
  user_agent_filter: /(bot|crawler|spider)/i

A more extensive filter could include: wget|archive|search|seo|libwww|index.

Install, test & contribute

XFile is tested with Christian Neukirchen's awesome test framework, Bacon. First, get bacon. Then crank up the heat on the entire test suite. Let's get cooking!

$ sudo gem install bacon
$ cd rack-session-xfile
$ bacon -a

If you find or fix a bug, please send reports and patches to pachl@ecentryx.com or open an issue on the bug tracker.

Homepage

ecentryx.com/gems/rack-session-xfile

Ruby Gem

rubygems.org/gems/rack-session-xfile

Source Code

bitbucket.org/pachl/rack-session-xfile/src

Bug Tracker

bitbucket.org/pachl/rack-session-xfile/issues

Compatibility

Rack::Session::XFile was originally developed and tested on OpenBSD 5.6 using Ruby 2.1.2 and Rack 1.6.0.

Currently, Rack::Session::XFile is compatible with Rack 2.0 and Ruby 2.2+ only.

History

  1. 2015-01-29, v0.10.0: First public release

    • XFile ran in production for more than a year before public release.

  2. 2017-06-07, v1.0.0

    • Update for compatibility with Rack 2.0 and Ruby 2.2+ (breaks compatibility with Rack 1.x; use XFile 0.10.x)

License

(ISC License)

Copyright © 2014-2017, Clint Pachl <pachl@ecentryx.com>

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.