chiark / gitweb /
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 a356ec18188de67d93ab6f02f9369ef44cd880d7..886e012312b770d7fe9e619b8e1c0a7bd7388607 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)"
@@ -153,6 +153,26 @@ userv/distorted-keys: userv/distorted-keys.in Makefile
                        >userv/distorted-keys.new && \
                mv userv/distorted-keys.new userv/distorted-keys
 
                        >userv/distorted-keys.new && \
                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.
 
 ###--------------------------------------------------------------------------
 ### Configuration snippets.
 
@@ -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 4d91172040ea147b32146e6e891d3a1a262827f4..ddf56e83a37933e21554649fc6b59c7125a4da6f 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 1de815ea4a872029aadc3b02284c9379d06721ba..7040f1bc0528f66c15ceae7cece2d6feedc12043 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 83b30ea4dd5db61585598e00aa4f12a61f1a796b..8d099897092d0abf71604ca87e0366225bccbd2f 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