diff options
-rwxr-xr-x | gitaccess | 157 | ||||
-rwxr-xr-x | gitaccess.pre-receive | 74 |
2 files changed, 141 insertions, 90 deletions
@@ -21,28 +21,153 @@ # Description # ----------- # -# This script is intended solely to read variables passed to it and set them up -# as environmental variables for later use. +# This script provides basic access controls to git repos. For this script to +# work, it requires that each repository has a 'users' file. This script reads +# that file and determines if the user associated with the logged in ssh key +# has access to that repo. # -# This script should be called by ~/.ssh/authorized keys using the following syntax +# This script also provides support for interactive git shell, interactive +# shell rejection via the no-interactive-shell script, and any other scripts +# that are placed inside the ~/git-shell-commands directory. +# +# To use this script for a specified ssh key, call it using the command +# directive in the ~/.ssh/authorized keys file using the following syntax # # # Key for user <username> -# command="~/bin/gitaccess <username>" ecdsa-sha2-nistp521 AAAAE2v.... +# command="gitaccess <username>" ecdsa-sha2-nistp521 AAAAE2v.... +# + + +# +# Logging function for standardized log output. This also ensures that log +# messages don't cause perceived corruption in git responses, as they always +# report to stderr. +# +# @param lvl Log level string. Can be anything, but error, info, warn, fatal, +# etc. are recomended +# @param msg Log message +# +log() { + local lvl=${1} + shift + local msg=${@} + + d=$(date '+%F %T') + printf "%s %s %s\n" "${d}" "${lvl}" "${msg[@]}" >> ~/git.log + printf "%s %s %s\n" "${d}" "${lvl}" "${msg[@]}" >&2 +} + +# Some logging macros to save typing time and space +lerror() { log 'error' ${@}; } +linfo() { log 'info ' ${@}; } +lwarn() { log 'warn ' ${@}; } +lwarn() { log 'fatal' ${@}; } + + +# +# Resolves the specified git repo path. Returns failure if... +# No repo exists at the path +# No repo exists at the path with .git appended +# The specified path exists, but is no a bare repository +# +# If the path is able to be resolved, the updated path is returned with an exit +# code of 0. +# +# @param repopath Path to the repo to resolve # +git_resolve_path() { + local repopath=${1:-} + local isbare + + # Resolve the path correctly if it has .git on the end that was not specified + [ -d ${repopath}.git ] && repopath=${repopath}.git + # If no repo can be found still, return failure + [ ! -d ${repopath} ] && lerror "No repo exists at ${repopath}" && return 1 + + isbare=$(git --git-dir="${repopath}" rev-parse --is-bare-repository 2>/dev/null) + [ "${isbare:-}" = 'true' ] && printf ${repopath} && return 0 + + lerror "Not a git repository: ${repopath}" + return 1 +} + + +# +# Checks if the specified user has acccess to the specified repo. +# Requires the presence of the users file at the top level of the bare repo. +# This file should contain one username per line +# +# @param repopath Path to the repo we're checking access to +# @param user Username to check for access +# +git_check_access() { + local repopath=${1} + local user=${2} + + local found=0 # Number of times the user is found in the users file + + if [ -d ${repopath} ]; then + # Fail if users file is not found + if [ ! -f ${repopath}/users ]; then + lerror "No users file found in ${repopath}." + lerror "Access denied" + printf 0 + return 1 + fi + + # Check if the user is in the users file + found=$(grep -c "^[ ]*${user}[ ]*$" ${repopath}/users) + if [ ${found} -eq 0 ]; then + lerror "Permission denied for ${user} to ${repopath}" + printf 0 + else + linfo "Permission granted for ${user} to ${repopath}" + printf 1 + fi + else + lerror "Could not find repo at ${repopath}." + return 3 + fi +} + + +# +# Ye olde main +# +main() { + local user="${1:-}" + + local repopath='none' -# Detect if someone tries to launch this script from this script, thus creating -# an infinite recursive loop spawning subshells. -if [ "${SSH_ORIGINAL_COMMAND:-}" == "$(basename ${0})" ]; then - printf "ERROR: Blocking infinite recursion\n" - exit 1 -fi + # Detect if someone tries to launch this script from this script, thus creating + # an infinite recursive loop spawning subshells. + if [ "${SSH_ORIGINAL_COMMAND:-}" = "$(basename ${0})" ]; then + log error "Blocking infinite recursion" + exit 1 + fi + # Launch git interractive shell if no commands were specified + if [ -z "${SSH_ORIGINAL_COMMAND:-}" ]; then + /usr/bin/env git shell + return $? + fi + + # If the user specified a git-* command, check if they have access to run it. + if [ "${SSH_ORIGINAL_COMMAND:0:4}" = 'git-' ]; then + # Parse the repo path out from the git command + repopath="$(echo ${SSH_ORIGINAL_COMMAND} | cut -d ' ' -f 2 | tr -d "'")" -# All checks passed... + # Resolve the repo path (in case it needs a .git or something else) + repopath=$(git_resolve_path "${repopath}") + [ $? -gt 0 ] && return 1 -# The first argument should be the username, as defailed in the script -# instructions -export GIT_USER="${1}" + # Verify the user has access to this repo + allowed=$(git_check_access "${repopath}" "${user}") + [ ${allowed} -ne 1 ] && exit 1 + fi + + # All checks passed. Run the command + /usr/bin/env git shell -c "${SSH_ORIGINAL_COMMAND}" +} -# Environmental variables set up. Proceed as was originally planned. -/usr/bin/env git shell -c "${SSH_ORIGINAL_COMMAND}" +main ${@} diff --git a/gitaccess.pre-receive b/gitaccess.pre-receive deleted file mode 100755 index e91c59e..0000000 --- a/gitaccess.pre-receive +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash -# -# Gitaccess implements basic access controls for git. -# Copyright (C) 2015 Aaron Ball <nullspoon@iohq.net> -# -# This program 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. -# -# This program 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 -# this program; if not, write to the Free Software Foundation, Inc., 51 -# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -# -# Description -# ----------- -# -# This is the pre-receive hook which allows for user filtering to the current -# working repo. -# -# Not that this REQUIRES the GIT_USER variable to be set. This is set by the -# gitaccess script. -# -# This script reads in the user's name as passed by the GIT_USER variable, and -# checks a users file (one user per line) for the given username. These -# usernames are directly associated with ssh keys used to log in. -# -# If the user is found, git proceeds as normal with a "user permitted" message. -# -# If the user is not found in the users file, an error message is printed and -# the git push is aborted. -# - -# Log whatever we want to -function log { - d=$(date '+%s') - echo "${d}: ${@}" >> ~/git.log -} - -# Read STDIN pre-receive arguments -read oldrev newrev refname - -# Determine the branch and repo names for better logging -branch=$(basename "${refname}") -repo=$(basename "$(pwd)") - -commit_dest_str="repo '${repo}', branch '${branch}'" - -# Check if user variable is set. Abort if it is not. -if [[ -z ${GIT_USER} ]]; then - echo "Error: Unknown ssh key. Rejecting push." - exit 1 -fi - -log "Attempted login for user ${GIT_USER} on ${commit_dest_str}" - -# See if user is permitted access to this repo -grep -v '^#' users | grep "^${GIT_USER}\$" 2>&1 1>/dev/null - -if [[ $? != 0 ]]; then - log "User is not permitted access to repo $(pwd)" - echo "Error: Permission denied for user ${GIT_USER} on ${commit_dest_str}." - echo "Aborting." - exit 1 -else - echo "User ${GIT_USER} accepted for ${commit_dest_str}. Allowing push." -fi |