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)" \
        PACKAGE="$(PACKAGE)" VERSION="$(VERSION)" \
        PYTHON="$(PYTHON)" \
        bindir="$(bindir)" sbindir="$(sbindir)" \
-       pkgconfdir="$(pkgconfdir)" \
+       sysconfdir="$(sysconfdir)" pkgconfdir="$(pkgconfdir)" \
        pkgstatedir="$(localstatedir)/lib/$(PACKAGE)" \
        pkglibdir="$(pkglibdir)" \
        user="$(user)"
        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
 
 ###--------------------------------------------------------------------------
                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
 ### 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/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 --------------------------------------------------
 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
 build
 distorted-keys
+claim-dir
+tmp
 *.log
 *.substvars
 files
 *.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.
  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
 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