summaryrefslogtreecommitdiff
path: root/gpgsecure.sh
blob: 3d0628a67fda7c1a2fa8bd8fe8b0538c91adb817 (plain)
    1 #!/usr/bin/env bash
    2 # GPGSecure is a shell script that manages GPG encrypted archives
    3 # Copyright (C) 2018  Aaron Ball <nullspoon@oper.io>
    4 #
    5 # This program is free software: you can redistribute it and/or modify
    6 # it under the terms of the GNU General Public License as published by
    7 # the Free Software Foundation, either version 3 of the License, or
    8 # (at your option) any later version.
    9 #
   10 # This program is distributed in the hope that it will be useful,
   11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13 # GNU General Public License for more details.
   14 #
   15 # You should have received a copy of the GNU General Public License
   16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
   17 
   18 
   19 export KEY=${KEY:-} # GPG key to encrypt the container with
   20 export DIR          # Directory path to present the gpg archive to
   21 export TMP          # Temp directory in memory to decrypt to
   22 
   23 trap shutdown SIGINT SIGTERM SIGKILL SIGQUIT SIGHUP
   24 
   25 
   26 shutdown() {
   27   gpgtar -e --recipient "${KEY}" -o "${DIR}.tar.gpg" .
   28   cd - 2>/dev/null 1>/dev/null
   29 
   30   # Shred all files in memory
   31   find "${TMP}" -type f -exec shred -n 100 -f -u "{}" \;
   32   # Delete the link
   33   rm "${DIR}"
   34   # Delete the temp dir from memory
   35   rm -rf "${TMP}"
   36   sync
   37   exit
   38 }
   39 
   40 
   41 writeback() {
   42   trap shutdown SIGINT SIGTERM SIGKILL SIGQUIT SIGHUP
   43   local tmp   # Temp file in memory to write re-encrypted container to. This is
   44               # used for faster writebacks to storage for bigger containers.
   45               # This also protects data, bigger containers take longer to write
   46               # to storage, leaving a bigger window for data corruption.
   47   local sleep=20
   48   local perms # Permissions of the dest encrypted file. Used for setting perms
   49               # on temp archive to avoid overwriting permissions on move
   50 
   51   # Read dest archive permissions, if it exists, else set to 700
   52   if [ -f "${DIR}.tar.gpg" ]; then
   53     perms=$(stat -c %a "${DIR}.tar.gpg")
   54   else
   55     perms=700
   56   fi
   57 
   58   cd "${TMP}"
   59 
   60   # Sync back to disk every ${sleep} seconds
   61   while [ 0 ]; do
   62     # Create temp archive for writing back so we don't risk corrupting the
   63     # actual destination archive in case of crash. Protect with 700 perms.
   64     tmp="$(mktemp /tmp/XXXXXXXXXXXX)"
   65     chmod 700 "${tmp}"
   66 
   67     # Write encrypted archive to temp file
   68     gpgtar -e --recipient "${KEY}" -o "${tmp}" .
   69     if [ $? -gt 0 ]; then
   70       printf 'WARNING: Something went wrong syncing back to encrypted storage\n'
   71       printf 'Your data is likely in danger.\n'
   72       printf 'If you see this message more than once, take a manual backup\n'
   73     fi
   74 
   75     # Update perms of temp file to match destination archive so we don't
   76     # overwrite those of the destination archive on move
   77     # TODO: This is a split second of permissions danger. We should find a way
   78     #       to remediate this. Set perms on dest archive *after* move instead?
   79     chmod "${perms}" "${tmp}"
   80     mv "${tmp}" "${DIR}.tar.gpg"
   81     if [ $? -gt 0 ]; then
   82       printf 'WARNING: Something went wrong syncing back to encrypted storage\n'
   83       printf 'Your data is likely in danger.\n'
   84       printf 'If you see this message more than once, take a manual backup\n'
   85     fi
   86 
   87     sleep ${sleep} &
   88     wait $!
   89   done
   90 }
   91 
   92 
   93 open() {
   94   local archive="${1}"
   95 
   96   # Convert DIR to absolute path to avoid cd issues
   97   local dirname="$(cd $(dirname ${archive}) && pwd)"
   98   local basename="$(basename ${archive})"
   99   local dir="${dirname}/${basename}"
  100   # Create a temp dir in memory to extract to for safety
  101   export TMP=$(mktemp -d /tmp/dec-XXXXXXXXXXXXXX)
  102   # Link!
  103   ln -s "${TMP}" "${dir}"
  104 
  105   if [ ! -f "${dir}.tar.gpg" ]; then
  106     # Tell the user if that encrypted archive does not exist.
  107     printf 'Encrypted archive does not exist. Creating.\n'
  108   else
  109     # Extract the encrypted tarchive if it exists
  110     gpgtar --decrypt --directory "${TMP}" "${dir}.tar.gpg"
  111   fi
  112 
  113   writeback &
  114   echo $! > "${dirname}/.${basename}.pid"
  115 }
  116 
  117 
  118 status() {
  119   local archive=${1}
  120 
  121   local dirname="$(dirname ${archive})"
  122   local basename="$(basename ${archive})"
  123   local pidfile="${dirname}/.${basename}.pid"
  124 
  125   # If no pidfile, assume closed
  126   if [ ! -f "${pidfile}" ]; then
  127     printf '%s is closed\n' "${archive}"
  128     return 0
  129   fi
  130 
  131   local pid="$(cat ${pidfile})"
  132 
  133   ps "${pid}" 2>/dev/null 1>/dev/null
  134   if [ $? -eq 0 ]; then
  135     printf '%s is open\n' "${archive}"
  136   elif [ $? -gt 0 ]; then
  137     printf '%s is closed but a stale pidfile was found. Removing\n' "${archive}"
  138     rm -f "${pidfile}"
  139   else
  140     printf '%s is closed\n' "${archive}"
  141   fi
  142 }
  143 
  144 
  145 close() {
  146   local archive=${1}
  147 
  148   local dirname="$(dirname ${archive})"
  149   local basename="$(basename ${archive})"
  150   local pidfile="${dirname}/.${basename}.pid"
  151   local pid="$(cat ${pidfile})"
  152 
  153   ps "${pid}" 2>/dev/null 1>/dev/null
  154   if [ $? -gt 0 ]; then
  155     printf "Stale pidfile detected but share is not open. Removing\n"
  156     rm -f "${pidfile}"
  157     return 1
  158   else
  159     # Send SIGTERM (15) to tell the process to exit cleanly
  160     kill -15 "${pid}"
  161     [ $? -eq 0 ] && rm "${pidfile}" && return 0
  162 
  163     printf 'Error closing archive "%s"\n' "${archive}"
  164     return 1
  165   fi
  166 }
  167 
  168 
  169 pathtoabs() {
  170   local path="${1}"
  171 
  172   if [ -f "${path}" ]; then
  173     cd $(dirname ${path})
  174     printf "%s/%s\n" "$(pwd)" "$(basename ${path})"
  175     return 0
  176   elif [ -d "${path}" ]; then
  177     cd ${path} && pwd
  178     return 0
  179   elif [ ! -e "${path}" ]; then
  180     printf -- "%s/%s\n" "$(pwd)" "${path}"
  181     return 0
  182   fi
  183   return 1
  184 }
  185 
  186 main() {
  187   local action="${1}"
  188   local archive="${2}"
  189 
  190   # Input validation
  191   if [ -z "${action:-}" ]; then
  192     printf 'Action (open, close, or status) required\n'
  193     return 1
  194   fi
  195   if [ -z "${archive:-}" ]; then
  196     printf 'Archive to decrypt required\n'
  197     return 1
  198   fi
  199 
  200   if [ -z "${KEY}" ]; then
  201     printf 'KEY variable unset. Cannot re-encrypt. Exiting.\n'
  202     return 1
  203   fi
  204 
  205   gpg --list-keys ${KEY} 2>/dev/null 1>/dev/null
  206   if [ $? -gt 0 ]; then
  207     printf 'Unknown key "%s". Cannot proceed.\n' "${KEY}"
  208     return 1
  209   fi
  210 
  211   local dirname="$(dirname ${archive})"
  212   local basename="$(basename ${archive})"
  213   export DIR="$(pathtoabs ${dirname}/${basename})"
  214 
  215   if [ "${action}" = 'open' ]; then
  216     # Check if already open
  217     if [ -f "${dirname}/.${basename}.pid" ]; then
  218       printf 'ERROR: Archive "%s" is already open\n' "${archive}"
  219       return 1
  220     else
  221       printf 'Opening!\n'
  222       open "${archive}"
  223       return $?
  224     fi
  225   elif [ "${action}" = 'close' ]; then
  226     # Check if already closed
  227     if [ ! -f "${dirname}/.${basename}.pid" ]; then
  228       printf 'ERROR: Archive "%s" is not open\n' "${archive}"
  229       exit 1
  230     else
  231       printf 'Closing!\n'
  232       close "${archive}"
  233       return $?
  234     fi
  235   elif [ "${action}" = 'status' ]; then
  236     status "${archive}"
  237     return $?
  238   fi
  239 
  240   # If we make it here, something went wrong.
  241   printf 'ERROR: Unknown action "%s"\n' "${action}"
  242   return 1
  243 }
  244 
  245 main ${@}

Generated by cgit