Creating a simple git repository server (with ACLs) on FreeBSD
January 2016.
Let's create a simple git server on FreeBSD.
It should:
- Allow people to clone/pull/push using both SSH and HTTP.
- Have a web view.
- Have ACLs to allow repositories to only be visible and/or accessible by some specific users.
SSH interaction: gitolite
Let's install gitolite. It handles SSH connections and have the ACL functionality we're after.
First, here's a good read about how gitolite works: http://gitolite.com/gitolite/how.html#%281%29
On the git server
Install gitolite:
# make -C /usr/ports/devel/gitolite/ install clean
Copy your public key on the server, naming it
[username].pub
. That username will be considered the admin user.Create a UNIX user that will own the files:
# pw useradd gitolite
# mkdir /home/gitolite
# chown gitolite:gitolite /home/gitolite
# cd /home/gitolite
Login as the UNIX user and initialize the system:
# sudo -s -u gitolite
% id
uid=1003(gitolite) gid=1003(gitolite) groups=1003(gitolite)
% /usr/local/bin/gitolite setup -pk admin.pub
Notice that the admin user can login using SSH, and that it will only execute gitolite's shell:
% cat .ssh/authorized_keys
command="/usr/local/libexec/gitolite/gitolite-shell admin",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa [some key]== zwm@git.example.net
That's all you need to do on the server.
On your client
Creating users and repositories
Clone the admin repository.
# git clone gitolite@git.example.net:gitolite-admin
Create two new keys (and thus users) and add them to the repository:
# ssh-keygen -t rsa -f erika
# ssh-keygen -t rsa -f jean
# cp erika.pub gitolite-admin/keydir
# cp jean.pub gitolite-admin/keydir
# git add keydir/jean.pub
# git add keydir/erika.pub
# git commit -m "Add users Jean and Erika."
# git push origin master
Create new repositories by setting their ACLs in the config file:
# cat conf/gitolite.conf:
repo gitolite-admin
RW+ = admin
repo testing
RW+ = @all
repo erika_only
RW+ = erika
repo erika_and_jean
RW+ = erika jean
# git add conf/gitolite.conf
# git commit -m "Add two new repos"
# git push origin master
Using the server
Try to clone repository
erika_only
with user jean
:
# setenv GIT_SSH_COMMAND 'ssh -i jean'
# git clone gitolite@git.example.net:erika_only
Cloning into 'erika_only'...
FATAL: R any erika_only jean DENIED by fallthru
(or you mis-spelled the reponame)
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Our access was denied. ACLs are working.
Try to clone a ACL allowed repository:
# git clone gitolite@git.example.net:erika_and_jean
# cd
# echo "Test" > test.txt
# git add test.txt
# git commit -m "Test commit"
# git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 218 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To gitolite@git.example.net:erika_and_jean
* [new branch] master -> master
Success.
HTTP interaction: nginx+git-http-backend
I assume you already know how to install and do the basic configuration of nginx.
Install fcgiwrap:
# make -C /usr/ports/www/fcgiwrap install clean
Configure fcgiwrap to use the right UNIX user:
/etc/rc.conf
:
fcgiwrap_enable="YES"
fcgiwrap_user="gitolite"
fcgiwrap_profiles="gitolite"
fcgiwrap_gitolite_socket="tcp:198.51.100.42:7081"
Create a password file:
# cat /usr/local/etc/nginx/git_users.htpasswd
jean:$apr1$fkADkYbl$Doen7IMxNwmD/r6X1LdM.1
erika:$apr1$fOOlnSig$4PONnRHK3PMu8j1HnxECc0
Use
openssl passwd -apr1
to generate passwords.Configure nginx:
server {
[usual config here]
auth_basic "RESTRICTED ACCESS";
auth_basic_user_file /usr/local/etc/nginx/git_users.htpasswd;
client_max_body_size 256m;
location ~ /git(/.*) {
root /home/gitolite/;
fastcgi_split_path_info ^(/git)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME /usr/local/libexec/gitolite/gitolite-shell;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_param GIT_PROJECT_ROOT /home/gitolite/repositories;
fastcgi_param GIT_HTTP_BACKEND /usr/local/libexec/git-core/git-http-backend;
fastcgi_param GITOLITE_HTTP_HOME /home/gitolite;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
# This include must be AFTER the above declaration. Otherwise, SCRIPT_FILENAME will be set incorrectly and the shell will 403.
include fastcgi_params;
fastcgi_pass 198.51.100.42:7081;
}
}
Here we call
gitolite-shell
instead of git-http-backend
directly to have gitolite check the users' permissions.Let's clone a repository, add a commit and push it:
# git clone 'http://jean:lol@git.example.net:8080/git/erika_and_jean.git' erika_and_jean
Cloning into 'erika_and_jean'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
Checking connectivity... done.
# cd erika_and_jean/
root@test:~/gitolite2/erika_and_jean # vim test.txt
root@test:~/gitolite2/erika_and_jean # git add test.txt
root@test:~/gitolite2/erika_and_jean # git commit -m "Pushed from HTTP"
[master 7604185] Pushed from HTTP
1 file changed, 1 insertion(+)
# git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 258 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To http://jean:lol@git.example.net:8080/git/erika_and_jean.git
fa03b7d..7604185 master -> master
Let's try to clone a repository we're not allowed to see:
# git clone 'http://jean:lol@git.example.net:8080/git/erika.git' erika
Cloning into 'erika'...
fatal: remote error: FATAL: R any erika jean DENIED by fallthru
(or you mis-spelled the reponame)
ACLs are working. Success.
Web view: GitWeb
Make sure
git
is compiled with option GITWEB
.Copy the gitweb files where nginx will look for them:
# cp -r /usr/local/share/examples/git/gitweb /usr/local/www/gitweb
Configure nginx:
location / {
root /usr/local/www/gitweb;
index gitweb.cgi;
location ~ ^/(.*\.cgi)$ {
include fastcgi_params;
fastcgi_pass 198.51.100.42:7081;
fastcgi_index gitweb.cgi;
fastcgi_param SCRIPT_FILENAME /usr/local/www/gitweb/gitweb.cgi;
fastcgi_param DOCUMENT_ROOT /usr/local/www/gitweb;
fastcgi_param GITWEB_CONFIG /usr/local/etc/gitweb.conf;
fastcgi_param REMOTE_USER $remote_user;
}
}
No magic here. The Gitolite/GitWeb interaction is irrelevant to the webserver.
Use the gitolite command to find the values of the
GL_
variables:
gitolite query-rc -a
Configure gitweb in
/usr/local/etc/gitweb.conf
:
BEGIN {
$ENV{HOME} = "/home/gitolite";
$ENV{GL_BINDIR} = "/usr/local/libexec/gitolite";
$ENV{GL_LIBDIR} = "/usr/local/libexec/gitolite/lib";
}
use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;
$projectroot = $ENV{GL_REPO_BASE};
our $site_name = "Example.net Git viewer";
$ENV{GL_USER} = $cgi->remote_user || "gitweb";
$export_auth_hook = sub {
my $repo = shift;
# gitweb passes us the full repo path; we need to strip the beginning and
# the end, to get the repo name as it is specified in gitolite conf
return unless $repo =~ s/^\Q$projectroot\E\/?(.+)\.git$/$1/;
# call Easy.pm's 'can_read' function
return can_read($repo);
};
When connected as erika:
When connected as jean:
ACLs are working. Success.
Conclusion
Our users can now see, read and sometimes write into the repositories of our git server.
You can create guest accounts that will only be able to see specific repositories, and they won't even know the other ones are here.
No need to maintain a gitlab instance if your needs are simple.