Skip to content

Commit

Permalink
refactor: git Qrexec helper with drop-in commands
Browse files Browse the repository at this point in the history
Drop-in scripts can complement the remote-helper ability.
Basic trace of the communication of git with the helper.
  • Loading branch information
ben-grande committed Dec 21, 2023
1 parent f8e21e9 commit b6c5298
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 48 deletions.
4 changes: 4 additions & 0 deletions salt/sys-git/files/client/git-core/git-init-qrexec
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

set -eu

case "${GIT_TRACE_HELPER:-}" in
true|1) set -x;;
esac

usage(){
echo "Usage: ${helper} [<qube>] [<repository>]"
echo "Note: qube defaults to '@default' and repository to the current repository"
Expand Down
121 changes: 94 additions & 27 deletions salt/sys-git/files/client/git-core/git-remote-qrexec
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

## Portable git-remote-helper.
## Rename this helper to git-remote-<scheme>.
## Valid URL format: <scheme>://<authority>/<path>.
## Supported commands: capabilities, connect.
## URL: <scheme>://<authority>/<path>[?query=value][&another_query=value]
## Capabilities commands are sent to git-remote-<scheme>-<capability>.

set -eu

usage(){
echo "Usage: ${helper} <remote> [${scheme}://<authority>/<path>]" >&2
echo "Usage: ${helper} <remote> [${scheme}://<authority>/<path>[?query=value][&other_query=value]]" >&2
}

die(){
Expand All @@ -21,6 +21,12 @@ die(){
exit 1
}

log(){
case "${GIT_TRACE_REMOTE_HELPER:-}" in
true|1) echo "${@}" >&2
esac
}

## Validate URL and return it without the scheme.
validate_url(){
url_valid=""
Expand All @@ -31,30 +37,79 @@ validate_url(){
## Checks if Authority and Path exist, but not if they are valid, this is
## implementation specific and should be checked by the connect helper.
case "${url_check}" in
"${scheme}"://*?/*?) url_valid="$(echo "${url_check}" | sed "s|.*://||")";;
"${scheme}"://*?/*?) url_valid="${url_check}";;
"${scheme}"://*?) die "URL has no path to resource: '${url_check}'";;
"${scheme}"://) die "URL has no authority: '${url_check}'";;
*?://*) die "URL has unsupported scheme: '${scheme_user_url}'";;
*) die "URL has no scheme: '${url_check}'";;
esac

urn_pattern="[0-9A-Za-z@:_.-]+/[0-9A-Za-z_.-]+(\?[0-9A-Za-z=&_-]*)?"
if ! (echo "${url_valid}" | grep -qE "^${scheme}://${urn_pattern}$")
then
die "URL contains forbidden characters"
fi

echo "${url_valid}"
}

get_urn(){
echo "${1#*://}"
}

get_authority(){
echo "${1%%/*}"
}

get_path(){
echo "${1##*/}" | cut -d "?" -f1
}

get_query(){
query=""
## If URL contains '?', set query.
if test "${1}" != "${1##*\?}"; then
query="${1##*\?}"
fi
echo "${query}"
}

## Find remote-<scheme>-<command>.
find_capabilities(){
cap_helpers=""
for f in "${exec_path}/${script}"-*; do
test -f "${f}" || continue
test -x "${f}" || continue
if test -z "${cap_helpers}"; then
cap_helpers="${f##*"${script}-"}"
else
cap_helpers="${cap_helpers}\n${f##*"${script}-"}"
fi
done

echo "${cap_helpers}"
}

## Send capabilities to remote helper specific for that capability.
send_cap(){
exec_path="$(git --exec-path)"
test -n "${exec_path}" || die "Couldn't locate Git's executables path"

cap="${1}"
shift
cap_file="${script}-${cap}"
cap_path="${exec_path}/${cap_file}"

test -e "${cap_path}" || die "Git's exec path missing: '${cap_file}'"
test -x "${cap_path}" || die "Git script is not executable: '${cap_file}'"

"${cap_path}" "${@}"
if ! (echo "${capabilities}" | grep -q "^${cap}$"); then
die "Unsupported capability: '${cap}'"
fi

cap_helper="${helper}-${cap}"
#cap_file="${script}-${cap}"
#cap_path="${exec_path}/${cap_file}"

## Call capability helper.
remote="${remote}" pushurl="${pushurl}" url="${url}" \
authority="${authority}" path="${path}" query="${query}" \
pushauthority="${pushauthority}" pushpath="${pushpath}" \
pushquery="${pushquery}" \
git "${cap_helper}" "${@}"
#"${cap_path}" "${@}"
}

## Basic requirements.
Expand All @@ -75,7 +130,10 @@ esac
## Get URL and Push URL (fallback to URL)
case "${2-}" in
"")
## Happens when 'remote-qrexec' is called directly from the command-line.
## Necessary when the remote-helper is called from the command-line.
if ! git remote get-url "${remote}" >/dev/null 2>&1; then
die "Remote doesn't exist: '${remote}'"
fi
url="$(git remote get-url "${remote}" || true)"
pushurl="$(git remote get-url --push "${remote}" || true)"
;;
Expand All @@ -85,23 +143,32 @@ esac
test -n "${url}" || die "Remote URL is unset"
test -n "${pushurl}" || die "Remote Push URL is unset"

url="$(validate_url "${url}")"
pushurl="$(validate_url "${pushurl}")"
pushurn="$(get_urn "${pushurl}")"
pushauthority="$(get_authority "${pushurn}")"
pushpath="$(get_path "${pushurn}")"
pushquery="$(get_query "${pushurn}")"

url="$(validate_url "${url}")"
urn="$(get_urn "${url}")"
authority="$(get_authority "${urn}")"
path="$(get_path "${urn}")"
query="$(get_query "${urn}")"

exec_path="$(git --exec-path)"
test -n "${exec_path}" || die "Git's executables path not found"

capabilities="$(find_capabilities)"

## Communicate with the git-remote-helpers protocol.
while read -r cmd arg; do
log "<- $cmd $arg"
case "${cmd}" in
"") exit 0;;
"capabilities") printf "connect\n\n";;
"connect")
printf "\n";
case "${arg}" in
git-upload-pack) send_cap "${cmd}" "${arg}" "${url}";;
git-receive-pack) send_cap "${cmd}" "${arg}" "${pushurl}";;
"") die "Argument can't be empty";;
*) die "Unsupported argument: '${arg}'";;
esac
;;
*) die "Unsupported command: '${cmd}'";;
capabilities)
for c in ${capabilities}; do log "-> ${c}"; done; log "->"
printf %s"${capabilities}\n\n";;
*) send_cap "${cmd}" "${arg}";;
esac
done

log "<-"
65 changes: 44 additions & 21 deletions salt/sys-git/files/client/git-core/git-remote-qrexec-connect
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
#
# SPDX-License-Identifier: AGPL-3.0-or-later

## Should be called by git-remote-qrexec.
set -eu

usage(){
echo "Usage: ${helper} git-upload-pack|git-receive-pack <qube>/<path>"
echo "Usage: ${helper} (git-upload-pack|git-receive-pack)"
echo "Note: ${helper} is supposed to be called by ${parent_helper}"
exit 1
}
Expand All @@ -18,45 +17,69 @@ die(){
exit 1
}

log(){
case "${GIT_TRACE_REMOTE_HELPER:-}" in
true|1) echo "${@}" >&2
esac
}

## Establish capability working.
log "->"
printf "\n"

helper="${0##*/git-}"
parent_helper="${helper%-*}"
scheme="${helper##*remote-}"; scheme="${scheme%%-*}"

case "${1-}" in
-h|--?help|"") usage;;
*) arg="${1}";;
esac
case "${2-}" in
"") usage;;
*) url="${2}";;
esac

test -n "${remote-}" || die "Remote is unset"
test -n "${url-}" || die "URL is unset"
test -n "${authority-}" || die "URL Authority is unset"
test -n "${path-}" || die "URL Path is unset"
: "${query-}"
test -n "${pushurl-}" || die "PushURL is unset"
test -n "${pushauthority-}" || die "PushURL Authority is unset"
test -n "${pushpath-}" || die "PushURL Path is unset"
: "${pushquery-}"

case "${arg}" in
git-upload-pack) rpc=GitFetch;;
git-receive-pack) rpc=GitPush;;
"") die "Argument can't be empty";;
*) die "Unsupported argument: '${arg}'";;
git-receive-pack) rpc=GitPush; url="${pushurl}";
authority="${pushauthority}"; path="${pushpath}"; query="${pushquery}";;
"") die "${helper} requires an argument";;
*) die "${helper} called with unsupported argument: '${arg}'";;
esac

qube="$(echo "${url}" | cut -d "/" -f1)"
repo="$(echo "${url}" | cut -d "/" -f2-)"
test -n "${repo}" || die "Repository name can't be empty"
test -n "${path}" || die "Repository name can't be empty"

vendor="qusal"
default_qube="sys-git"
rpc_cmd="${vendor}.${rpc}+${repo}"
rpc_cmd="${vendor}.${rpc}+${path}"

if echo "${query}" | grep -qE "(^|&)verify_signatures=(1|[tT]rue|yes|on)($|&)"
then
die "Remote helper does not support signature verification yet"
fi

if command -v qrexec-client-vm >/dev/null; then
exec qrexec-client-vm -- "${qube}" "${rpc_cmd}"
log "->" qrexec-client-vm -- "${authority}" "${rpc_cmd}"
exec qrexec-client-vm -- "${authority}" "${rpc_cmd}"
elif command -v qrexec-client >/dev/null; then
qubes_version="$(awk -F '=' '/^VERSION_ID=/{print $2}' /etc/os-release)"
if test "$(echo "${qubes_version}" | tr -d ".")" -le 41; then
if test "${qube}" = "@default"; then
qube="${default_qube}"
if test "${authority}" = "@default"; then
authority="${default_qube}"
fi
else
policy="$(qrexec-policy --assume-yes-for-ask dom0 "${qube}" "${rpc_cmd}")"
qube="$(echo "${policy}" | awk -F '=' '/^target=/{print $2}')"
policy="$(qrexec-policy --assume-yes-for-ask dom0 "${authority}" "${rpc_cmd}")"
authority="$(echo "${policy}" | awk -F '=' '/^target=/{print $2}')"
fi
exec qrexec-client -d "${qube}" -- "DEFAULT:QUBESRPC ${rpc_cmd} dom0"
else
die "Qrexec programs not found: qrexec-client-vm, qrexec-client"
log "->" qrexec-client -d "${authority}" -- "DEFAULT:QUBESRPC ${rpc_cmd} dom0"
exec qrexec-client -d "${authority}" -- "DEFAULT:QUBESRPC ${rpc_cmd} dom0"
fi

die "Qrexec programs not found: qrexec-client-vm, qrexec-client"

0 comments on commit b6c5298

Please sign in to comment.