Lightweight access control solution for the pijul vcs.
Please see the introductory post at https://lthms.xyz/blog/pi-hoole
pi-hoole
pi-hoole
is a collection of tools to enforce access control for self-hosted pijul repositories. It can be seen as a cgit-like solution, for authenticated (public key, SSH) and anonymous accesses. You can grant read and write accesses to a whole repository, or to a determined subset of branches.
pi-hoole
is distributed under the terms of the AGPL v3.
Getting Started
Building From Source
It should not be a surprise that pi-hoole
is versioned under pijul
, with the patch format introduced by pijul-0.9
. Fair warning, use pijul-0.10.1
and you should be able to clone the pi-hoole
repository.
pijul clone https://pijul.lthms.xyz/pijul/pi-hoole
Under the hood, pi-hoole
is implemented in Haskell. We will need stack
to build it.
cd pi-hoole
stack build
We are using the latest lts available, but pi-hoole
has already been built with lts-10
(but does not build with older ones).
This build three executables:
pi-hoole-cfg
generates a.authorized_keys
file to enforce access control for SSH.pi-hoole-shell
is called to determine if a authenticated user is allowed to perform a givenpijul
command.pi-hoole-web
is a HTTP proxy to enforce access control for anonymous requests.
Installing
In order to use pi-hoole
on your server, the first step is to create a new, dedicated user (e.g.pijul
). You then need to make pijul
and pi-hoole-shell
available to this user. Currently, we do not provide any packaging solution to that end, but this might change in the future. Although it is not mandatory, we consider both pi-hoole-cfg
and pi-hoole-web
have also been made available for the pijul
user.
Configuring
pi-hoole
executables assume the configuration files are stored at ${XDG_CONFIG_DIRECTORY}/pi-hoole
, that is ~/.config/pi-hoole
by default.
In this directory, pi-hoole
will scan the keys/
directory, if it exists, to know the list of authorized authenticated users. One user may have as many public key as required. Keys should be saved in file with the following filename scheme: <user>(\.<label>)?\.pub
. Thus, lthms.pub
, lthms.laptop.pub
and lthms.work.pub
are three valid public key filenames for the lthms
user.
To generate a .ssh/authorized_keys
, use pi-hoole-cfg
.
pi-hoole-cfg generate > ~/.ssh/authorized_keys
Resulting file will be of the forms:
command="pi-hoole-shell \".lthms\" \"${SSH_ORIGINAL_COMMAND}\"",no-port-forwarding,no-x11-forwarding,no-agent-forwarding <ssh_keys_1>
command="pi-hoole-shell \".lthms\" \"${SSH_ORIGINAL_COMMAND}\"",no-port-forwarding,no-x11-forwarding,no-agent-forwarding <ssh_keys_2>
To configure the authorized accesses for the users, you have to set up a valid pi-hoole
configuration file. The latter uses the YAML syntax, and is located at ${XDG_CONFIG_DIRECTORY}/pi-hoole/config.yaml
. It has two root fields: groups
and repositories
.
You can define groups of users, and grant certain rights to a group, effectively granting there rights to each users who are member of the group.
Group names are prefixed by +
, and user names are prefixed by .
.
Then, if you want to create one group ogma
which contains the users lgeorget
and lthms
:
groups:
+ogma: [.lthms, .lgeorget]
You can create as many groups as you want, and one user may be added to several groups. Currently, nested groups are not allowed (i.e. a group being member of another group).
The repositories
field allows for defining which repositories are available, and what given users can do with these repositories. The contents of the repositories
is a map, where keys are path
to the repositories (relative to HOME
directory), and values are maps from role to rights.
For instance, given the following configuration:
repositories:
my/first/repo:
.lthms: +w
+contrib: +r +w[master]
anon: +r
my/second/repo:
.lthms: +r
.lgeorget: +w
We declare two repositories, one located at ${HOME}/my/first/repo
, and another at ${HOME}/my/second/repo
. The user lthms
(identified with .lthms
) can read and write to this repository, to arbitrary branches. The members of the group contrib
can read to any branch, but can only write to the master
branch. Finally, the anonymous user (through HTTP), can read to any branch.
Read means being able to clone or pull. Write means being able to push.
For the second repository, lthms
can read to any branch, when lgeorget
can read and write to any branch. Therefore, it is not possible to clone my/second/repo
through HTTP, because anon
has not been granted any particular rights.
Setting Up HTTP Proxy
pi-hoole-web
is a very simple HTTP proxy. It receives HTTP request, ideally issued by a pijul
client, and turns them into pijul
commands if the anon
role has been granted the required rights.
Currently, pi-hoole-web
cannot be configured, and listen the port 8080. Also, it does not daemonify itself. The easiest way to set up a pi-hoole-web
instance in a reliable way is to use systemd, for instance with the following unit:
[Unit]
Description=HTTP Proxy for Pijul anonymous accesses
[Service]
Type=simple
User=pijul
Group=pijul
ExecStart=/usr/bin/pi-hoole-web
Restart=of-failure
TimeoutStopSec=300
[Install]
WantedBy=multi-user.target
For reference, the instance of pi-hoole-web
responsible for pijul.lthms.xyz
is behind a nginx server. One very straightforward nginx configuration can be:
server {
listen 80;
server_name pijul.lthms.xyz;
access_log /var/log/nginx/pijul.lthms.xyz.access.log;
error_log /var/log/nginx/pijul.lthms.xyz.error.log;
location / {
proxy_pass http://localhost:8080/;
}
}
For pi-hoole-web
to work correcty, it needs an additional configuration file, assumed to be at ${XDG_CONFIG_DIRECTORY}/pi-hoole/web.yaml
. This file is currently very simple, it contains only three fields:
title
: The title of the summary pagebaseUrl
: The base URL used to clone the repositoriesexample
: Should be one of the public repositories
Here is an example:
title: "my repositories"
baseUrl: "https://my.repo.com"
example: "pi-hoole"
If you add a file called description
in the .pijul
directory of your public repositories, its content will be used by pi-hoole-web
when it gives a summary of the public repositories.
Limitations
Private Branches
The command pijul patch
does not have an argument to specify the branch from which a patch is initially fetched; as a consequence, having read access to a repo, even for another branch where the patch is not applied, is enough. For this reason, if a user who obtains a hash for a patch of a branch they cannot access can fetch the patch. That is, a patch is as private as its hash. Private branches will eventually be supported in a better manner, but for now, private branches should mean separated repositories.