Making a Git repository available via http

I've setup this tutorial when I was dealing with PXE boot to get automated installs going. The config files were in a Git repo. I wanted to be able to access the repo over http/https. This is how I did this.

Git repo

Install the necessary packages on the server:

apt install git git-core fcgiwrap apache2-utils

Create the repository on the server:

cd /srv
mkdir -p git/bionic-workstation
cd git/bionic-workstation
git init --bare .

git config --local --add http.receivepack true
git update-server-info
chown -R www-data:<user> repo.git
chmod -R 754 repo.git

In your home directory or a location you find appropriate, clone the repo and start putting the config files in it you want to be on the target machine.:

cd /home/adminuser
mkdir repos

git clone /srv/git/bionic-workstation/ bionic-workstation

Ignore the warning, we know it's an empty repo :) This way the "origin" remote is already set.

nginx

You can also make the git repo accessible via nginx. Add this location to the default site or the site config you're going to use. We are going to split up the git repo read access (public) and the write access (authenticated). The common part goes into a file in nginx.:

vi /etc/nginx/git-http-backend.conf

fastcgi_pass unix:/var/run/fcgiwrap.socket;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /srv/git;
fastcgi_param PATH_INFO $1;
fastcgi_param REMOTE_USER $remote_user;

Next the nginx config. In our example we create a new site.:

vi /etc/nginx/sites-available/<mygitsites>

server {
    listen 80;
    listen [::]:80;

    root /srv/;
    access_log   /var/log/nginx/<mysite>-access.log;
    error_log   /var/log/nginx/<mysite>-error.log;

    index index.html index.htm;

    server_name <mysite>;

    # Serve git repositories
    location ~ /git(/.*) {
        if ($arg_service = git-receive-pack) {
            rewrite /git(/.*) /git_write$1 last;
        }

        if ($uri ~ ^/git/.*/git-receive-pack$) {
            rewrite /git(/.*) /git_write$1 last;
        }

        if ($arg_service = git-upload-pack) {
            rewrite /git(/.*) /git_read$1 last;
        }

        if ($uri ~ ^/git/.*/git-upload-pack$) {
            rewrite /git(/.*) /git_read$1 last;
        }
    }
    location ~ /git_read(/.*) {
        include git-http-backend.conf;
    }
    location ~ /git_write(/.*) {
        auth_basic "Password required to push to Git.";
        auth_basic_user_file /etc/nginx/.htpasswd;
        include git-http-backend.conf;
    }
}

cd /etc/nginx/sites-enabled
ln -s /etc/nginx/sites-available/<mysite>

Test config and reload nginx.:

nginx -t
systemctl reload nginx

Now try to clone the repo on the client machine. If you don't have a client machine, do an automated install first or try from another machine.:

cd /home/adminuser
git clone https://<mysite>/git/bionic-workstation

Note

The settings above allow anonymous users to pull the repo. We will use htpasswd (apache2-utils) to create a basic realm authentication file to prevent unauthorized users to push to the repo.

Password protect

To add password protection to our site, we used the auth_basic* settings in nginx. We need to create a file with the users and passwords.:

echo -n '<user>:' >> /etc/nginx/.htpasswd
openssl passwd -apr1 >> /etc/nginx/.htpasswd

Or with the htpasswd utility, first run:

htpasswd -c /etc/nginx/.htpasswd <myuser>

Consecutive users:

htpasswd <myuser>

Test if the cloning of a repo still works without authenticating:

git clone https://<mysite>/git/myrepo
Cloning into 'myrepo'...
...

Change a file in the repo and try to push:

git push origin master
Username for 'https://<mysite>':

We get a password prompt as we want.

SSL

Now the last part is to use SSL. Since the site I'm using is an internal purpose only site, a self signed certificate will do. If you run a public service, don't use a self signed certificate but use a Certificate Authority (CA) to get/buy a certificate.

Create the certificate. Answer the questions and make sure the Common Name (CN) matches the server_name in the site config of nginx.:

openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/private/<mysite>.key -out /etc/ssl/certs/<mysite>.crt

...
Country Name (2 letter code) [AU]:BE
State or Province Name (full name) [Some-State]:<myprovince>
Locality Name (eg, city) []:<mycity>
Organization Name (eg, company) [Internet Widgits Pty Ltd]:<mycompany>
Organizational Unit Name (eg, section) []:<mydepartment>
Common Name (e.g. server FQDN or YOUR name) []:<server_name in nginx>
Email Address []:

To increase security, create a strong Diffie-Hellman group:

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

We will redirect the http traffic to https. Next we need to adjust the nginx https site settings:

vi /etc/nginx/sites-available/<mygitsites>

server {
    listen 80;
    listen [::]:80;
    server_name <mysite>;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    root /srv/;
    access_log   /var/log/nginx/<mysite>-access.log;
    error_log   /var/log/nginx/<mysite>-error.log;

    index index.html index.htm;

    server_name <mysite>;

    # SSL
    gzip off;
    ssl_certificate /etc/ssl/certs/<mysite>.crt;
    ssl_certificate_key /etc/ssl/private/<mysite>.key;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    #ssl_stapling on;
    #ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    # Disable preloading HSTS for now.  You can use the commented out header line that includes
    # the "preload" directive if you understand the implications.
    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    # Serve git repositories
    location ~ /git(/.*) {
        if ($arg_service = git-receive-pack) {
            rewrite /git(/.*) /git_write$1 last;
        }

        if ($uri ~ ^/git/.*/git-receive-pack$) {
            rewrite /git(/.*) /git_write$1 last;
        }

        if ($arg_service = git-upload-pack) {
            rewrite /git(/.*) /git_read$1 last;
        }

        if ($uri ~ ^/git/.*/git-upload-pack$) {
            rewrite /git(/.*) /git_read$1 last;
        }
    }
    location ~ /git_read(/.*) {
        include git-http-backend.conf;
    }
    location ~ /git_write(/.*) {
        auth_basic "Pushing to Git repositories is restricted";
        auth_basic_user_file /etc/nginx/.htpasswd;
        include git-http-backend.conf;
    }
}

Check if nginx doesn't detect any errors and reload the config. Ignore the "ssl_stapling" warning or don't use ssl_stapling for an internal site:

nginx -t
systemctl restart nginx

Clone the repo by trying the http access. nginx should redirect you to the https site:

git clone https://<mysite>/git/bionic-workstation

If you browse, you will get a warning that the certificate isn't signed by a CA your browser knows and trusts. This is normal as this is a self-signed certificate. Go to the advanced options and exception for the site.

If you want to deal with the SSL untrust in a better way, the following section will show you how.

Create your own CA

If you want to be able to access the site via ip rather than hostname, make the certificate on ip (CN=<ip>) rather than hostname otherwise you'll get an error when the certificate is matched to the hostname.

You get more control if you create your own root CA to issue certificates.

See create root CA