refactor: initial commit

This commit is contained in:
Ben Grande 2023-11-13 14:33:28 +00:00
commit f6ac229306
No known key found for this signature in database
GPG key ID: 00C64E14F51F9E56
594 changed files with 18600 additions and 0 deletions

172
salt/sys-git/README.md Normal file
View file

@ -0,0 +1,172 @@
# sys-git
Git operations through Qrexec in Qubes OS.
## Table of Contents
* [Description](#description)
* [Alternatives comparison](#alternatives-comparison)
* [Installation](#installation)
* [Access control](#access-control)
* [Usage](#usage)
* [Initialize the server repository](#initialize-the-server-repository)
* [Prepare the client](#prepare-the-client)
* [Credits](#credits)
## Description
Setup a Git server called "sys-git", an offline Git Server that can be
accessed from client qubes via Qrexec. Access control via Qrexec policy can
restrict access to certain repositories, set of git actions for Fetch, Push
and Init. This is an implementation of split-git.
## Alternatives comparison
The following alternatives will be compared against each other and this
implementation:
- [Rudd-O/git-remote-qubes](https://github.com/Rudd-O/git-remote-qubes)
- [QubesOS-contrib/qubes-app-split-git](https://github.com/QubesOS-contrib/qubes-app-split-git)
- [qubes-os.org/doc/development-workflow/#git-connection-between-vms](https://www.qubes-os.org/doc/development-workflow/#git-connection-between-vms)
| | sys-git | git-remote-qubes | qubes-app-split-git | git-connection-between-vms |
| :--- | :---: | :---: | :---: | :---: |
| Codebase Size | Small | Large | Large | Small |
| Custom Protocol | True | True | True | False |
| Path | Repository | Absolute | Repository | Repository |
| Repository restriction | True | False | True | True |
| No hanging | True | True | True | False |
| Fetch | True | True | True (only tags) | True |
| Push | True | True | False | True |
| Init | True | False | False | False |
| Validates Git communication | False | False | True | False |
| Verifies tag signature | False | False | True | False |
## Installation
- Top
```sh
qubesctl top.enable sys-git
qubesctl --targets=tpl-sys-git,sys-git state.apply
qubesctl top.disable sys-git
```
- State
<!-- pkg:begin:post-install -->
```sh
qubesctl state.apply sys-git.create
qubesctl --skip-dom0 --targets=tpl-sys-git state.apply sys-git.install
qubesctl --skip-dom0 --targets=sys-git state.apply sys-git.configure
```
<!-- pkg:end:post-install -->
Installation on the client template:
```sh
qubesctl --skip-dom0 --targets=tpl-dev state.apply sys-git.install-client
```
## Access control
_Default policy_: `any qube` can `ask` via the `@default` target if you allow
it to `Fetch` from, `Push` to and `Init` on `sys-git`.
__Recommended usage__:
- __Init__: Argument useful when allowing a qube to always create a
repository on the server.
- __Fetch__: Fetch can be allowed by less trusted qubes.
- __Push__: Push should only be made by trusted qubes.
Allow qube `dev` to `Fetch` from `sys-git`, but ask to `Push` and `Init`:
```qrexecpolicy
qusal.GitFetch * dev @default allow target=sys-git
qusal.GitPush * dev @default ask target=sys-git default_target=sys-git
qusal.GitInit * dev @default ask target=sys-git default_target=sys-git
qusal.GitFetch * dev @anyvm deny
qusal.GitPush * dev @anyvm deny
qusal.GitInit * dev @anyvm deny
```
Allow qube `untrusted` to `Fetch` `repo` if using target name `sys-git` but
deny `Push` and `Init` to any other qube:
```qrexecpolicy
qusal.GitFetch +repo untrusted sys-git ask target=sys-git default_target=sys-git
qusal.GitFetch * untrusted @anyvm deny
qusal.GitPush * untrusted @anyvm deny
qusal.GitInit * untrusted @anyvm deny
```
Deny `Fetch`, `Push` and `Init` from any qube to any other qube:
```qrexecpolicy
qusal.GitFetch * @anyvm @anyvm deny
qusal.GitPush * @anyvm @anyvm deny
qusal.GitInit * @anyvm @anyvm deny
```
## Usage
### Initialize the server repository
There are a few constraints regarding repositories:
- Must be created under `/home/user/src` in `sys-git`;
- Names must have only letters, numbers, hyphen, underscore and dot. Must not
begin or end with dot, hyphen and underscore.
In `sys-git`, create bare repositories under `/home/user/src`.
From the `server`:
```sh
git init --bare ~/src/X.git
```
You must use the `.git` prefix to indicate a bare repository.
Or from the `client`, if the `qusal.GitInit` policy allows:
```sh
cd ~/path/to/repo
git init-qrexec
```
### Prepare the client
Qrexec protocol is supported with the following URL format:
`qrexec://<QUBE>/<REPO>`, where the `<QUBE>` field can be a literal name or
token and the `<REPO>` field is the name of the repository that exists on
`sys-git` under `/home/user/src`.
Clone an existing repository:
```sh
git clone qrexec://@default/qubes-doc
```
Or Initialize a new repository:
```sh
git init qubes-doc
cd qubes-doc
```
Add a remote using the Qrexec protocol:
```sh
git remote add sg qrexec://@default/qubes-doc
```
Test fetching from the newly added remote:
```sh
git fetch sg
```
Make changes to the git repository as you normally would on any system.
Push to the server and set it as the default upstream:
```sh
git push -u sg main
```
Following pushes will be simpler:
```sh
git push
```
## Credits
- [Unman](https://github.com/unman/shaker/tree/main/git)

8
salt/sys-git/clone.sls Normal file
View file

@ -0,0 +1,8 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{% from 'utils/macros/clone-template.sls' import clone_template -%}
{{ clone_template('debian-minimal', sls_path) }}

10
salt/sys-git/clone.top Normal file
View file

@ -0,0 +1,10 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
base:
'dom0':
- match: nodegroup
- sys-git.clone

View file

@ -0,0 +1,13 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{% if grains['nodename'] != 'dom0' -%}
include:
- dev.home-cleanup
- dotfiles.copy-git
{% endif -%}

View file

@ -0,0 +1,9 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
base:
'sys-git':
- sys-git.configure

46
salt/sys-git/create.sls Normal file
View file

@ -0,0 +1,46 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{%- from "qvm/template.jinja" import load -%}
include:
- .clone
{% load_yaml as defaults -%}
name: {{ slsdotpath }}
force: True
require:
- sls: {{ slsdotpath }}.clone
present:
- template: tpl-{{ slsdotpath }}
- label: gray
prefs:
- template: tpl-{{ slsdotpath }}
- label: gray
- netvm: ""
- vcpus: 1
- memory: 200
- maxmem: 300
features:
- enable:
- servicevm
- disable:
- service.cups
- service.cups-browsed
# tags:
# - add:
# - split-gpg2-client
{%- endload %}
{{ load(defaults) }}
"{{ slsdotpath }}-resize-private-volume":
cmd.run:
- name: qvm-volume resize {{ slsdotpath }}:private 20Gi
- require:
- qvm: {{ slsdotpath }}
{% from 'utils/macros/policy.sls' import policy_set with context -%}
{{ policy_set(sls_path, '80') }}

10
salt/sys-git/create.top Normal file
View file

@ -0,0 +1,10 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
base:
'dom0':
- match: nodegroup
- sys-git.create

View file

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
## Do not modify this file, create a new policy with with a lower number in the
## file name instead. For example `30-user.policy`.
qusal.GitFetch * @anyvm @default ask target={{ sls_path }} default_target={{ sls_path }}
qusal.GitPush * @anyvm @default ask target={{ sls_path }} default_target={{ sls_path }}
qusal.GitInit * @anyvm @default ask target={{ sls_path }} default_target={{ sls_path }}
qusal.GitFetch * @anyvm @anyvm deny
qusal.GitPush * @anyvm @anyvm deny
qusal.GitInit * @anyvm @anyvm deny
## vim:ft=qrexecpolicy

View file

@ -0,0 +1,53 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
set -eu
usage(){
echo "Usage: ${helper} [<qube>] [<repository>]"
echo "Note: qube defaults to '@default' and repository to the current repository"
exit 1
}
is_git_repo(){
if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
echo "Error: Either run from inside a git repository or provide it as an argument" >&2
usage
fi
}
helper="${0##*/git-}"
case "${1-}" in
-h|--?help) usage;;
"") qube="@default";;
*) qube="${1}";;
esac
case "${2-}" in
"") is_git_repo; repo="$(basename "$(git rev-parse --show-toplevel)")";;
*) repo="${2}";;
esac
rpc="GitInit"
vendor="qusal"
default_qube="sys-git"
rpc_cmd="${vendor}.${rpc}+${repo}"
if command -v qrexec-client-vm >/dev/null; then
exec qrexec-client-vm -- "${qube}" "${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}"
fi
else
policy="$(qrexec-policy --assume-yes-for-ask dom0 "${qube}" "${rpc_cmd}")"
qube="$(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"
fi

View file

@ -0,0 +1,107 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
## Portable git-remote-helper.
## Rename this helper to git-remote-<scheme>.
## Valid URL format: <scheme>://<authority>/<path>.
## Supported commands: capabilities, connect.
## Capabilities commands are sent to git-remote-<scheme>-<capability>.
set -eu
usage(){
echo "Usage: ${helper} <remote> [${scheme}://<authority>/<path>]" >&2
}
die(){
usage
echo "Error: ${1}" >&2
exit 1
}
## Validate URL and return it without the scheme.
validate_url(){
url_valid=""
url_check="${1?}"
scheme_user_url="$(echo "${url_check}" | sed "s|://.*||")"
## Scheme must be the same as the one in the name of this script.
## 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}"://*?) 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
echo "${url_valid}"
}
## 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}" "${@}"
}
## Basic requirements.
command -v git >/dev/null || die "Command not found: 'git'"
script="${0##*/}"
helper="${script##git-}"
scheme="${helper##remote-}"
if test "${script}" != "git-remote-${scheme}" || test -z "${scheme}"; then
die "Script must be named with the format: git-remote-<scheme>"
fi
## Get remote name or show usage.
case "${1-}" in
-h|--?help|"") usage; exit 1;;
*) remote="${1}";;
esac
## Get URL and Push URL (fallback to URL)
case "${2-}" in
"")
## Happens when 'remote-qrexec' is called directly from the command-line.
url="$(git remote get-url "${remote}" || true)"
pushurl="$(git remote get-url --push "${remote}" || true)"
;;
*) url="${2}"; pushurl="${2}";;
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}")"
## Communicate with the git-remote-helpers protocol.
while read -r cmd arg; do
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}'";;
esac
done

View file

@ -0,0 +1,62 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
#
# 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 "Note: ${helper} is supposed to be called by ${parent_helper}"
exit 1
}
die(){
echo "Error: ${1}" >&2
exit 1
}
helper="${0##*/git-}"
parent_helper="${helper%-*}"
case "${1-}" in
-h|--?help|"") usage;;
*) arg="${1}";;
esac
case "${2-}" in
"") usage;;
*) url="${2}";;
esac
case "${arg}" in
git-upload-pack) rpc=GitFetch;;
git-receive-pack) rpc=GitPush;;
"") die "Argument can't be empty";;
*) die "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"
vendor="qusal"
default_qube="sys-git"
rpc_cmd="${vendor}.${rpc}+${repo}"
if command -v qrexec-client-vm >/dev/null; then
exec qrexec-client-vm -- "${qube}" "${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}"
fi
else
policy="$(qrexec-policy --assume-yes-for-ask dom0 "${qube}" "${rpc_cmd}")"
qube="$(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"
fi

View file

@ -0,0 +1 @@
qusal.GitInit

View file

@ -0,0 +1,58 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
set -eu
base_path="$HOME/src"
repo="$QREXEC_SERVICE_ARGUMENT"
#origin="$QREXEC_REMOTE_DOMAIN"
die(){
echo "error: $1" >&2
exit 1
}
fail_invalid_name(){
if ! (echo "$repo" | grep -q "^[A-Za-z0-9][A-Za-z0-9_.-]\+$"); then
die "Invalid repository. Allowed chars: letters, numbers, hyphen, underscore and dot. It cannot begin with hyphen, underscore or dot."
fi
}
if ! command -v git >/dev/null; then
die "Command not found: git"
fi
fail_invalid_name
case "$repo" in
*".git") ;;
*) repo="$repo.git";;
esac
path="$base_path/$repo"
action="${0##*.Git}"
case "$action" in
Fetch) service=git-upload-pack;;
Push) service=git-receive-pack;;
Init) service="git init --bare";;
*) die "Invalid RPC name: ${0##*/}";;
esac
if test "$action" != "Init"; then
test -d "$path" || die "Directory doesn't exist: $repo"
git -C "$path" rev-parse >/dev/null 2>&1 || die "Not a git repository: $repo"
is_bare="$(git -C "$path" rev-parse --is-bare-repository)"
test "${is_bare}" = "true" || die "Not a bare repository: $repo"
fi
if ! test -d "$base_path"; then
# shellcheck disable=SC2174
mkdir -m 0700 -p "$base_path" >/dev/null 2>&1 ||
die "Cannot create directory: $base_path"
fi
# shellcheck disable=SC2086
exec $service -- "$path"

View file

@ -0,0 +1 @@
qusal.GitInit

14
salt/sys-git/init.top Normal file
View file

@ -0,0 +1,14 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
base:
'dom0':
- match: nodegroup
- sys-git.create
'tpl-sys-git':
- sys-git.install
'sys-git':
- sys-git.configure

View file

@ -0,0 +1,48 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
include:
- dotfiles.copy-git
- dotfiles.copy-sh
- dotfiles.copy-x11
- sys-pgp.install-client
"{{ slsdotpath }}-updated":
pkg.uptodate:
- refresh: True
"{{ slsdotpath }}-installed":
pkg.installed:
- refresh: True
- install_recommends: False
- skip_suggestions: True
- pkgs:
- git
{% set git = {
'Debian': {
'exec_path': '/usr/lib/git-core',
},
'RedHat': {
'exec_path': '/usr/libexec/git-core',
},
}.get(grains.os_family) -%}
"{{ slsdotpath }}-install-client-git-core-dir":
file.recurse:
- require:
- pkg: {{ slsdotpath }}-installed
- source: salt://{{ slsdotpath }}/files/client/git-core
- name: {{ git.exec_path }}
- file_mode: '0755'
- dir_mode: '0755'
- user: root
- group: root
- makedirs: True
- recurse:
- mode
- user
- group

View file

@ -0,0 +1,9 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
base:
'*':
- sys-git.install-client

46
salt/sys-git/install.sls Normal file
View file

@ -0,0 +1,46 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{% if grains['nodename'] != 'dom0' -%}
include:
- dotfiles.copy-git
- dotfiles.copy-sh
- dotfiles.copy-x11
- sys-pgp.install-client
"{{ slsdotpath }}-updated":
pkg.uptodate:
- refresh: True
"{{ slsdotpath }}-installed":
pkg.installed:
- refresh: True
- install_recommends: False
- skip_suggestions: True
- pkgs:
- git
"{{ slsdotpath }}-rpc":
file.recurse:
- name: /etc/qubes-rpc/
- source: salt://{{ slsdotpath }}/files/server/rpc
- user: root
- group: root
- file_mode: '0755'
- dir_mode: '0755'
- keep_symlinks: True
- force_symlinks: True
"{{ slsdotpath }}-skel-repository-directory":
file.directory:
- name: /etc/skel/src
- user: root
- group: root
- mode: '0755'
- makedirs: True
{% endif -%}

9
salt/sys-git/install.top Normal file
View file

@ -0,0 +1,9 @@
{#
SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
base:
'tpl-sys-git':
- sys-git.install