Directory claiming and ephemeral filesystems.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 12 Feb 2012 23:14:36 +0000 (23:14 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 23 Feb 2012 03:14:20 +0000 (03:14 +0000)
Two new related tools.

  * `mount-ephemeral' creates (and removes) a temporary filesystem,
    encrypted using a fresh random key so the contents are irretrievably
    lost when the host reboots or the power fails.

  * `claim-dir' is a `userv' service which allows users to claim
    directories in a shared filesystem without the hazardous
    free-for-all that results from world writability with a sticky bit.

These go in their own separate Debian package.  There's no direct link
between the two, but bundling them together provides a hint regarding
possible applications.

Makefile.am
claim-dir.tab [new file with mode: 0644]
debian/.gitignore
debian/claim-dir.install [new file with mode: 0644]
debian/control
debian/distorted-keys.install
mount-ephemeral [new file with mode: 0755]
userv/claim-dir.in [new file with mode: 0644]

index a356ec1..886e012 100644 (file)
@@ -49,7 +49,7 @@ SUBSTVARS = \
        PACKAGE="$(PACKAGE)" VERSION="$(VERSION)" \
        PYTHON="$(PYTHON)" \
        bindir="$(bindir)" sbindir="$(sbindir)" \
-       pkgconfdir="$(pkgconfdir)" \
+       sysconfdir="$(sysconfdir)" pkgconfdir="$(pkgconfdir)" \
        pkgstatedir="$(localstatedir)/lib/$(PACKAGE)" \
        pkglibdir="$(pkglibdir)" \
        user="$(user)"
@@ -154,6 +154,26 @@ userv/distorted-keys: userv/distorted-keys.in Makefile
                mv userv/distorted-keys.new userv/distorted-keys
 
 ###--------------------------------------------------------------------------
+### Secure storage management.
+
+## Ephemeral filesystem construction.
+sbin_SCRIPTS           += mount-ephemeral
+EXTRA_DIST             += mount-ephemeral
+
+## Directory claiming service.
+noinst_DATA            += userv/claim-dir
+EXTRA_DIST             += userv/claim-dir.in
+CLEANFILES             += userv/claim-dir
+userv/claim-dir: userv/claim-dir.in Makefile
+       $(AM_V_at)mkdir -p userv/
+       $(SUBST) $(srcdir)/userv/claim-dir.in $(SUBSTVARS) \
+                       >userv/claim-dir.new && \
+               mv userv/claim-dir.new userv/claim-dir
+
+## Configuration file.
+EXTRA_DIST             += claim-dir.tab
+
+###--------------------------------------------------------------------------
 ### Configuration snippets.
 
 dist_profile_DATA      += profile.d/00base
@@ -176,6 +196,8 @@ EXTRA_DIST          += debian/rules debian/compat
 EXTRA_DIST             += debian/distorted-keys.install
 EXTRA_DIST             += debian/distorted-keys.postinst
 
+EXTRA_DIST             += debian/claim-dir.install
+
 EXTRA_DIST             += debian/admin.users debian/admin.groups
 
 ###----- That's all, folks --------------------------------------------------
diff --git a/claim-dir.tab b/claim-dir.tab
new file mode 100644 (file)
index 0000000..f5ba774
--- /dev/null
@@ -0,0 +1,21 @@
+### -*-conf-*-
+###
+### This file lists the available filesystems which may be claimed by users.
+### Each line has the form
+###
+###    FS      DIR     [OPT=VALUE ...]
+###
+### The FS is a simple name for the filesystem, matched against the FILSYS
+### argument to `claim-dir'.  The DIR is the directory name in which to
+### create user directories.  The remaining options are as follows.
+###
+### acl=[-]{USER|%GROUP},...
+###    Access control list for this filesystem.  Entries prefixed with `-'
+###    forbid access, other entries permit; the entries are glob patterns
+###    matched against the user's name or groups.  The first match wins; if
+###    no entry matches, access is forbidden.  Without this option, access
+###    is open to all.
+###
+### mount=SCRIPT
+###    If DIR is not a mount point already, run SCRIPT DIR.
+
index 4d91172..ddf56e8 100644 (file)
@@ -1,5 +1,7 @@
 build
 distorted-keys
+claim-dir
+tmp
 *.log
 *.substvars
 files
diff --git a/debian/claim-dir.install b/debian/claim-dir.install
new file mode 100644 (file)
index 0000000..202f94b
--- /dev/null
@@ -0,0 +1,4 @@
+usr/sbin/mount-ephemeral
+
+debian/build/userv/claim-dir                   /etc/userv/default.d
+claim-dir.tab                                  /etc
index 1de815e..7040f1b 100644 (file)
@@ -23,3 +23,23 @@ Description: Basic key-management system with secure recovery features.
  This system doesn't actually do very much cryptography itself.  Instead,
  it uses other existing implementations, such as GnuPG, OpenSSL, and
  Seccure.
+
+Package: claim-dir
+Architecture: all
+Depends: userv
+Recommends: cryptsetup, dmsetup
+Description: Allow users to claim directories on file systems
+ Machines sometimes have storage devices with useful special properties --
+ such as high performance, or secure erasure on power failure.  Rather than
+ set the root of such a filesystem world-writable and sticky, thereby making
+ another filesystem as hard to use safely as `/tmp', `claim-dir' lets users
+ claim directories on such filesystems via `userv'.  A newly claimed
+ directory is named after the calling user, and created readable and writable
+ only by the calling user -- so he or she can relax the permissions later if
+ necessary.
+ .
+ A script `mount-ephemeral' is included which allows the construction of an
+ ephemeral filesystem -- one which is backed by normal storage (typically in
+ `/tmp'), but encrypted using a temporary key which will be lost at reboot.
+ This script can be used to build a safe place for the storage of
+ temporary secrets.
index 83b30ea..8d09989 100644 (file)
@@ -1,3 +1,8 @@
+usr/bin
+usr/sbin/keys
+usr/lib/distorted-keys
+etc/distorted-keys
+
 debian/build/userv/distorted-keys      /etc/userv/default.d
 debian/admin.users                     /etc/distorted-keys
 debian/admin.groups                    /etc/distorted-keys
diff --git a/mount-ephemeral b/mount-ephemeral
new file mode 100755 (executable)
index 0000000..3641e2a
--- /dev/null
@@ -0,0 +1,196 @@
+#! /bin/sh
+###
+### Mount an ephemeral filesystem
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the distorted.org.uk key management suite.
+###
+### distorted-keys is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### distorted-keys is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with distorted-keys; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+set -e
+
+QUIS=${0##*/}
+VERSION=1.0.0
+USAGE="usage: $QUIS [-u] [-R RANDOM] [-n BYTES] [-C CIPHER] [-H HASH]
+       [-l LABEL] [-t FSTYPE] [-b BACKING-FILE] MOUNTPOINT [SIZE]"
+
+###--------------------------------------------------------------------------
+### Parse the command line.
+
+## Set initial defaults.
+mode=mount
+cipher=aes-xts-plain
+hash=sha256
+random=/dev/random
+randbytes=512
+fail=nil
+backing=/tmp
+unset label
+
+## Report version number.
+version () { echo "$QUIS, version $VERSION"; }
+
+## Report help text.
+help () {
+  version
+  cat <<EOF
+$USAGE
+
+Options:
+  -h           Show this help text.
+  -v           Show the program's version number.
+  -C CIPHER    Cipher to use to encrypt the filesystem [$cipher].
+  -H HASH      Hash function for hashing the random data [$hash].
+  -R RANDOM    Source of random bytes for key material [$random].
+  -b BACKING   Where to store the ciphertext [$backing].
+  -l LABEL     Device mapper label [basename of MOUNTPOINT].
+  -n RANDBYTES Number of random bytes to read for the key [$randbytes].
+  -u           Unmount the filesystem, destroying all data in it.
+EOF
+}
+
+## Loop over the options.
+while getopts "C:H:R:b:hl:n:t:uv" opt; do
+  case $opt in
+    h) help; exit 0 ;;
+    v) echo "$VERSION"; exit 0 ;;
+    C) cipher=$OPTARG ;;
+    H) hash=$OPTARG ;;
+    R) random=$OPTARG ;;
+    n) randbytes=$OPTARG ;;
+    b) backing=$OPTARG ;;
+    l) label=$OPTARG ;;
+    u) mode=umount ;;
+    *) fail=t ;;
+  esac
+done
+shift $(( $OPTIND - 1 ))
+case $fail,$mode,$# in
+  nil,mount,2) mntpt=$1 size=$2 ;;
+  nil,umount,1) mntpt=$1 ;;
+  *) echo >&2 "$USAGE"; exit 1 ;;
+esac
+
+## Default omitted arguments.
+case "${label+t}" in t) ;; *) label=${mntpt##*/} ;; esac
+
+###--------------------------------------------------------------------------
+### Do the job.
+
+case $mode in
+
+  mount)
+    ## Mount the filesystem.
+
+    ## Determine a name for the backing file.  If BACKING is a directory then
+    ## we should make a file there and delete it once we've created a
+    ## mapping.  The directory may be a shared bit of filesystem, so we must
+    ## be very careful.
+    rmbacking=nil
+    if [ -d "$backing" ]; then
+      i=0
+      while :; do
+       gorp=$(openssl rand -base64 6)
+       bkdir=$backing/mnteph.$$.$gorp
+       if mkdir >/dev/null 2>&1 -m700 "$bkdir"; then break; fi
+       i=$(( $i + 1 ))
+       if [ $i -ge 100 ]; then
+         echo >&2 "$QUIS: failed to create backing directory"
+         exit 1
+       fi
+      done
+      backing=$bkdir/fs
+      trap 'rc=$?; rm "$backing"; rmdir "$bkdir"; exit $rc' EXIT
+      trap 'exit 127' INT TERM
+      rmbacking=t
+    fi
+
+    ## Create the backing file.
+    truncate -s"$size" "$backing"
+    loop=$(losetup -f --show "$backing")
+
+    ## Attach a device-mapper entry to the file.
+    dd 2>/dev/null if="$random" bs=1 count="$randbytes" |
+    cryptsetup \
+      --cipher="$cipher" --hash="$hash" \
+      --key-file=- \
+      create "$label" "$loop"
+
+    ## Create the filesystem.
+    if spew=$(mkfs 2>&1 "/dev/mapper/$label"); then
+      :
+    else
+      rc=$?
+      echo >&2 "$QUIS: mkfs failed (rc = $rc)"
+      echo "$spew" | sed >&2 's/^/| /'
+      exit $rc
+    fi
+
+    ## Mount.
+    mount "/dev/mapper/$label" "$mntpt"
+    ;;
+
+  umount)
+    ## Unmount a filesystem.
+
+    ## Find the numbers of the loopback device.
+    deps=$(dmsetup deps "/dev/mapper/$label")
+    set -- $(echo "$deps" |
+      sed 's!^.*:.*(\([0-9]\+\),[[:space:]]*\([0-9]\+\)).*$!\1 \2!')
+    case "$#" in
+      2) ;;
+      *)
+       echo >&2 "$QUIS: unexpected answer from \`dmsetup deps'"
+       echo "$deps" | sed >&2 's/^/| /'
+       exit 1
+       ;;
+    esac
+    maj=$1 min=$2
+
+    ## Convert that into a name.
+    dev=$(readlink /sys/dev/block/$maj:$min)
+    dev=${dev##*/}
+    case "$dev" in
+      loop*) ;;
+      *)
+       echo >&2 "$QUIS: expected a loopback device; found \`$dev'"
+       exit 1
+       ;;
+    esac
+
+    ## Unmount the filesystem.
+    umount "$mntpt"
+
+    ## Remove the cryptoloop mapping.
+    if spew=$(cryptsetup 2>&1 remove "$label"); then
+      :
+    else
+      rc=$?
+      echo >&2 "$QUIS: cryptsetup failed (rc = $rc)"
+      echo "$spew" | sed >&2 's/^/| /'
+      exit $rc
+    fi
+
+    ## Disconnect the loopback device.
+    losetup -d "/dev/$dev"
+    ;;
+
+esac
+
+###----- That's all, folks --------------------------------------------------
diff --git a/userv/claim-dir.in b/userv/claim-dir.in
new file mode 100644 (file)
index 0000000..4cc6032
--- /dev/null
@@ -0,0 +1,82 @@
+### -*-conf-*-
+###
+### userv service for claiming a directory in a special filesystem
+
+if ( glob service claim-dir
+   & glob service-user root
+   & grep calling-user-shell /etc/shells
+   )
+       no-suppress-args
+       null-fd 0
+       require-fd 1-2 write
+       ignore-fd 3-
+       no-set-environment
+       execute sh -c "set -e; quis=$0;                                 \
+       case $# in                                                      \
+         1) filsys=$1 ;;                                               \
+         *) echo >&2 \"usage: $quis FILSYS\"; exit 1 ;;                \
+       esac;                                                           \
+       foundp=nil;                                                     \
+       while read fs dir opts; do                                      \
+         case \"$fs\" in                                               \
+           \\#* | \"\") continue ;;                                    \
+           \"$filsys\") foundp=t; break ;;                             \
+         esac;                                                         \
+       done <@sysconfdir@/claim-dir.tab;                               \
+       case $foundp in                                                 \
+         nil)                                                          \
+           echo >&2 \"$quis: unknown filesystem \\`$filsys'\";         \
+           exit 1                                                      \
+           ;;                                                          \
+       esac;                                                           \
+       for opt in $opts; do                                            \
+         arg=${opt#*=};                                                \
+         case \"$opt\" in                                              \
+           acl=*)                                                      \
+             verdict=forbid acl=$arg;                                  \
+             while :; do                                               \
+               case \"$acl\" in ?*) ;; *) break ;; esac;               \
+               case \"$acl\" in                                        \
+                 *,*) word=${acl%%,*} acl=${acl#*,} ;;                 \
+                 *) word=$acl acl=\"\" ;;                              \
+               esac;                                                   \
+               case \"$word\" in                                       \
+                 -*) sense=forbid word=${word#-} ;;                    \
+                 *) sense=allow word=$word ;;                          \
+               esac;                                                   \
+               case \"$word\" in                                       \
+                 %*) pat=${word#%} list=\"$USERV_GROUP $USERV_GID\" ;; \
+                 *) pat=$word list=\"$USERV_USER $USERV_UID\" ;;       \
+               esac;                                                   \
+               matchp=nil;                                             \
+               for i in $list; do                                      \
+                 case \"$i\" in $pat) matchp=t; break ;; esac;         \
+               done;                                                   \
+               case $matchp in t) verdict=$sense; break ;; esac;       \
+             done;                                                     \
+             case $verdict in                                          \
+               forbid)                                                 \
+                 echo >&2 \"$quis: permission denied\";                \
+                 exit 1                                                \
+                 ;;                                                    \
+             esac                                                      \
+             ;;                                                        \
+           mount=*)                                                    \
+             if ! mountpoint -q \"$dir\"; then $arg \"$dir\"; fi       \
+             ;;                                                        \
+           *)                                                          \
+             echo >&2 \"$quis: unknown option \\`$opt'\";              \
+             exit 1                                                    \
+             ;;                                                        \
+         esac;                                                         \
+       done;                                                           \
+       set _ $USERV_USER; user=$2;                                     \
+       set _ $USERV_GROUP; group=$2;                                   \
+       cd \"$dir\";                                                    \
+       if [ ! -d \"$user\" ]; then                                     \
+         mkdir -m700 \"$user\";                                        \
+         chown \"$user:$group\" \"$user\";                             \
+       fi;                                                             \
+       echo \"$dir/$USERV_USER\"                                       \
+       " claim-dir
+fi