summaryrefslogtreecommitdiff
path: root/gitaccess
blob: 7c0a7fe7a1f9913c3878a93b233161acb5ddad44 (plain)
    1 #!/usr/bin/env bash
    2 #
    3 # Gitaccess implements basic access controls for git.
    4 # Copyright (C) 2017 Aaron Ball <nullspoon@oper.io>
    5 # 
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of the GNU General Public License as published by the Free Software
    8 # Foundation; either version 2 of the License, or (at your option) any later
    9 # version.
   10 # 
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
   14 # details.
   15 # 
   16 # You should have received a copy of the GNU General Public License along with
   17 # this program; if not, write to the Free Software Foundation, Inc., 51
   18 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   19 #
   20 #
   21 # Description
   22 # -----------
   23 #
   24 # This script provides basic access controls to git repos. For this script to
   25 # work, it requires that each repository has a 'users' file. This script reads
   26 # that file and determines if the user associated with the logged in ssh key
   27 # has access to that repo.
   28 #
   29 # This script also provides support for interactive git shell, interactive
   30 # shell rejection via the no-interactive-shell script, and any other scripts
   31 # that are placed inside the ~/git-shell-commands directory.
   32 #
   33 # To use this script for a specified ssh key, call it using the command
   34 # directive in the ~/.ssh/authorized keys file using the following syntax
   35 #
   36 #     # Key for user <username>
   37 #     command="gitaccess <username>" ecdsa-sha2-nistp521 AAAAE2v....
   38 #
   39 
   40 
   41 #
   42 # Logging function for standardized log output. This also ensures that log
   43 # messages don't cause perceived corruption in git responses, as they always
   44 # report to stderr.
   45 #
   46 # @param lvl Log level string. Can be anything, but error, info, warn, fatal,
   47 #            etc. are recomended
   48 # @param msg Log message
   49 #
   50 log() {
   51   local lvl=${1}
   52   shift
   53   local msg=${@}
   54 
   55   d=$(date '+%F %T')
   56   printf "%s   %s   %s\n" "${d}" "${lvl}" "${msg[@]}" >> ~/git.log
   57   printf "%s   %s   %s\n" "${d}" "${lvl}" "${msg[@]}" >&2
   58 }
   59 
   60 # Some logging macros to save typing time and space
   61 lerror() { log 'error' ${@}; }
   62 linfo()  { log 'info ' ${@}; }
   63 lwarn()  { log 'warn ' ${@}; }
   64 lwarn()  { log 'fatal' ${@}; }
   65 
   66 
   67 #
   68 # Resolves the specified git repo path. Returns failure if...
   69 #   No repo exists at the path
   70 #   No repo exists at the path with .git appended
   71 #   The specified path exists, but is no a bare repository
   72 #
   73 # If the path is able to be resolved, the updated path is returned with an exit
   74 # code of 0.
   75 #
   76 # @param repopath Path to the repo to resolve
   77 #
   78 git_resolve_path() {
   79   local repopath=${1:-}
   80   local isbare
   81 
   82   # Resolve the path correctly if it has .git on the end that was not specified
   83   [ -d ${repopath}.git ] && repopath=${repopath}.git
   84   # If no repo can be found still, return failure
   85   [ ! -d ${repopath} ] && lerror "No repo exists at ${repopath}" && return 1
   86 
   87   isbare=$(git --git-dir="${repopath}" rev-parse --is-bare-repository 2>/dev/null)
   88   [ "${isbare:-}" = 'true' ] && printf ${repopath} && return 0
   89 
   90   lerror "Not a git repository: ${repopath}"
   91   return 1
   92 }
   93 
   94 
   95 #
   96 # Checks if the specified user has acccess to the specified repo.
   97 # Requires the presence of the users file at the top level of the bare repo.
   98 # This file should contain one username per line
   99 #
  100 # @param repopath Path to the repo we're checking access to
  101 # @param user     Username to check for access
  102 #
  103 git_check_access() {
  104   local repopath=${1}
  105   local user=${2}
  106 
  107   local found=0 # Number of times the user is found in the users file
  108   
  109   if [ -d ${repopath} ]; then
  110     # Fail if users file is not found
  111     if [ ! -f ${repopath}/users ]; then
  112       lerror "No users file found in ${repopath}."
  113       lerror "Access denied"
  114       printf 0
  115       return 1
  116     fi
  117   
  118     # Check if the user is in the users file
  119     found=$(grep -c "^[ 	]*${user}[ 	]*$" ${repopath}/users)
  120     if [ ${found} -eq 0 ]; then
  121       lerror "Permission denied for ${user} to ${repopath}"
  122       printf 0
  123     else
  124       linfo "Permission granted for ${user} to ${repopath}"
  125       printf 1
  126     fi
  127   else
  128     lerror "Could not find repo at ${repopath}."
  129     return 3
  130   fi
  131 }
  132 
  133 
  134 #
  135 # Ye olde main
  136 #
  137 main() {
  138   local user="${1:-}"
  139 
  140   local repopath='none'
  141 
  142   # Detect if someone tries to launch this script from this script, thus creating
  143   # an infinite recursive loop spawning subshells.
  144   if [ "${SSH_ORIGINAL_COMMAND:-}" = "$(basename ${0})" ]; then
  145     log error "Blocking infinite recursion"
  146     exit 1
  147   fi
  148 
  149   # Launch git interractive shell if no commands were specified
  150   if [ -z "${SSH_ORIGINAL_COMMAND:-}" ]; then
  151     /usr/bin/env git shell
  152     return $?
  153   fi
  154   
  155   # If the user specified a git-* command, check if they have access to run it.
  156   if [ "${SSH_ORIGINAL_COMMAND:0:4}" = 'git-' ]; then
  157     # Parse the repo path out from the git command
  158     repopath="$(echo ${SSH_ORIGINAL_COMMAND} | cut -d ' ' -f 2 | tr -d "'")"
  159 
  160     # Resolve the repo path (in case it needs a .git or something else)
  161     repopath=$(git_resolve_path "${repopath}")
  162     [ $? -gt 0 ] && return 1
  163 
  164     # Verify the user has access to this repo
  165     allowed=$(git_check_access "${repopath}" "${user}")
  166     [ ${allowed} -ne 1 ] && exit 1
  167   fi
  168   
  169   # All checks passed. Run the command
  170   /usr/bin/env git shell -c "${SSH_ORIGINAL_COMMAND}"
  171 }
  172 
  173 main ${@}

Generated by cgit