Exercises in Restful Integration and Continuous Delivery

Musing on Software Development and Technologies.

Sat 10 November 2018

Git Repo in Shared Hosting #1 - Secure HTTP Access to a Repo

Posted by Mikko Koivunalho in How-To   

Git Logo

In this series of four articles we show how to share a Git repository via a Linux shell account and a shared hosting Apache HTTP server without root access. In practice this means that we are shell users on a remote server and we have a subdirectory dedicated for our web pages, often ${HOME}/public_html, and we can control the Apache HTTP server with a .htaccess file.

In the first article we will learn how we can share our Git repository with others securily and yet allow them to commit (push) to the repo. There are two ways for this: via the HTTP server and via SSH connection with SSH keys. We will only deal with HTTP now.

In the second article we will set up the web-based collaborative code review tool Review Board to work with our repository and make it accessible for users via our homepage. Review Board can be used for any text document review - not just code review - and is very convenient although not easy to set up. Review Board is under MIT license and free to use.

In the third article we will make our repository even more secure and usable with the help of a Git hooks framework, the Perl based Git::Hooks We will limit user access to only some branches and activate some quality checks. Git::Hooks is under Perl 5 license and free to use.

In the fourth article we will use SSH connection and SSH public keys to give access and also limit access to our repositories.

Prerequisites

  • Shell account on a Linux server. Bash Shell.
  • Apache HTTP server with distributed configuration allowed.
  • Git.
  • The need to work collaboratively! Need is the greatest innovator.
  • Disclaimer! This tutorial is for Debian. Other Linux distros might behave differently.

Two Variables for the Tutorial

Create two environmental variables to use in the rest of this tutorial.

The first one is easy. It is the directory in which your homepages reside + the root path you want to give to all your repositories. In many cases (Apache HTTP) the homepages would be (by default) located in directory ${HOME}/public_html. But change this to whatever it is! Don't forget the repositories sub directory, e.g. ${HOME}/public_html/repositories.

export REPOS_HTTP="<FULL PATH HERE>"

The next one is more tricky. You should place your repositories in a directory which is outside the public HTTP zone - for security reasons! But it still needs to be accessible by the HTTP server.

If your hosting service runs the Apache HTTP daemon in a different server, perhaps the home directory is not mounted - again for security reasons - or it is located in a different path. If that is the case, read chapter What is my Path? to discover the web directory path. If absolutely necessary, place the repositories in the web pages area but under a different root directory, e.g. git-repositories. But avoid this! It is always a bad idea to put anything else into webserver directories, than what is absolutely necessary! That is why the repositories will be accessed by a reference. Example: ${HOME}/repositories.

export REPOS_PATH="<FULL PATH HERE>"

Create Repository for Sharing

Let's start from the very basics. Create directories to use for the repositories in a secure area.

mkdir -p ${REPOS_PATH}/shared
mkdir -p ${REPOS_PATH}/public

Shared repositories are for collaboration (all users with access can pull and push) but public repoes are only for reading (all Internet can read but they cannot be updated via HTTP). We leave out private as those repos would not be available to Internet via HTTP at all.

Then create the bare repositories. They have no working tree, i.e. them can only be used as remote repositories for pulling and pushing).

git init --bare ${REPOS_PATH}/shared/shared-repo.git
git init --bare ${REPOS_PATH}/public/public-repo.git

Now make the shared repository ready to accept pushes via HTTP. Normal local access and access via ssh is always allowed. When using option -C you do not have to be in the repo directory. First, with configuration option http.receivepack, allow push for all users, including anonymous (we will later limit the users). Second, activate the post-update hook to interface properly with HTTP servers (generate required auxiliary information on the fly).

git -C ${REPOS_PATH}/shared/shared-repo.git config http.receivepack true
mv ${REPOS_PATH}/shared/shared-repo.git/hooks/post-update.sample ${REPOS_PATH}/shared/shared-repo.git/hooks/post-update
chmod 755 ${REPOS_PATH}/shared/shared-repo.git/hooks/post-update

First Steps in a New Repository

These steps are voluntary. We will take the shared repository into use. At this point we skip over the public repository and save the configuring for later.

Why the empty commit? Because there may come a situation when you need to return to a completely empty repository. Without the first empty commit you would need to reset the repository to the first commit available which would not be empty.

git clone file://${REPOS_PATH}/shared/shared-repo.git
cd shared-repo
git commit --allow-empty -m "Initial (empty) commit"
git push origin master
cd ..

Publish the Repository

Prepare Authentication

We will use Apache's method for HTTP Basic Authentication. Your system should have the Apache command htpasswd available. Create two passwords and write them to a file which - for protection - resides outside the WWW page area. Parameter -c creates a new file. Finally, just to be sure, remove all needless access to the file.

htpasswd -c ${REPOS_PATH}/shared/http.passwd <my userid>
> <password>
> <password again>
htpasswd ${REPOS_PATH}/shared/http.passwd <other userid>
> <password>
> <password again>
chmod 644 ${REPOS_PATH}/shared/http.passwd

Create Repository Endpoints

Now let's create HTTP endpoints for the repo. The last part of the path is the repository directory named shared-repo.git. We will - in essence - pretend to be the repository while the actual repository is safely outside the web server's public directory.

mkdir -p ${REPOS_HTTP}/shared/shared-repo.git
mkdir -p ${REPOS_HTTP}/public/public-repo.git

Secure Everything

Protect the whole ${REPOS_HTTP}/ subdirectory. Execute the following.

cat > ${REPOS_HTTP}/.htaccess <<EOF
<Limit GET POST PUT DELETE CONNECT OPTIONS PATCH PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK>
  order deny,allow
  deny from all
</Limit>

EOF

Now the directory and all its subdirectories are completely inaccessible. The effect of Apache \<Limit> directive is cancelled by the other \<Limit> in our .htaccess file in subdirectory shared/shared-repo.git, whole path ${REPOS_HTTP}/shared/shared-repo.git/.htaccess.

Secure The Shared Repository

Now configure the shared-repo with .htaccess file. If you add more repositories later, you can configure access to them individually. Every repository needs to have its own .htaccess file.

cat > ${REPOS_HTTP}/shared/shared-repo.git/.htaccess <<EOF
# Activate the *RewriteEngine* to enable redirecting.
RewriteEngine On

#Redirect HTTP to HTTPS every time.
RewriteCond %{ENV:HTTPS} !on
RewriteRule (.*) https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]

# Git repository specific redirect to the correct CGI script.
AcceptPathInfo On
RewriteCond %{REQUEST_URI} ^/(.*/(HEAD|info/refs|objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))|git-(upload|receive)-pack))\$
RewriteCond %{REQUEST_URI} !git-http-backend
RewriteRule (.*)\$ git-http-backend.cgi/\$1 [L]

# Do not return directory listings.
Options -Indexes

# Force authentication.
AuthType Basic
AuthName "Private"
Require valid-user
AuthUserFile ${REPOS_PATH}/shared/http.passwd

EOF

To seriously limit access from any IP address (and HTTP command) add the following at the end of ${REPOS_HTTP}/shared/shared-repo.git/.htaccess. This part is voluntary and often difficult in practice but if you know your users' IP addresses, limiting the IPs is probably the greatest security measure you can take. Use Apache's \<Limit> directive. We use the same configuration as above but now add allow from parameters for all trusted IP addresses.

cat >> ${REPOS_HTTP}/shared/shared-repo.git/.htaccess <<EOF
<Limit GET POST PUT DELETE CONNECT OPTIONS PATCH PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK>
  order deny,allow
  deny from all
  # My IP
  allow from <IP address>
  # An Example IP
  allow from 123.456.789.111
</Limit>

EOF

Or you can instead add the following to allow access to all (with password protection still active of course):

cat >> ${REPOS_HTTP}/shared/shared-repo.git/.htaccess <<EOF
<Limit GET POST PUT DELETE CONNECT OPTIONS PATCH PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK>
  order deny,allow
  allow from all
</Limit>

EOF

And execute the following to protect the file.

chmod 644 ${REPOS_HTTP}/shared/shared-repo.git/.htaccess

Connect The Shared Repository to This HTTP Endpoint

In case ScriptAlias (Apache configuration) is not allowed - probably due to security reasons - we have to use git-http-backend as a script.

cat > ${REPOS_HTTP}/shared/shared-repo.git/git-http-backend.cgi <<EOF
#!/usr/bin/env bash
export GIT_PROJECT_ROOT="${REPOS_PATH}/shared/shared-repo.git"
export GIT_HTTP_EXPORT_ALL=""
/usr/lib/git-core/git-http-backend

EOF

And again! Protect the file.

chmod 500 ${REPOS_HTTP}/shared/shared-repo.git/git-http-backend.cgi

Make The Public Repository Public

You can skip this part if you do not plan to expose any repository to all of Internet!

Do the same actions for the public-repo.git repo:

Create file .htaccess (remove the Force authentication part) the git-http-backend.cgi script.

But limit the access to only the HTTP GET action because reading is all we will do here.

cat > ${REPOS_HTTP}/public/public-repo.git/.htaccess <<EOF
<Limit GET>
  order deny,allow
  allow from all
</Limit>

EOF

And execute the following to protect the files.

chmod 644 ${REPOS_HTTP}/public/public-repo.git/.htaccess
chmod 500 ${REPOS_HTTP}/shared/shared-repo.git/git-http-backend.cgi

Create the git-http-backend.cgi script.

Verify

Check that our repository is reachable via HTTP for both pulling (cloning) and pushing.

git clone https://<USER>@<HOST>/<REPOSITORIES PATH>/shared/shared-repo.git
cd shared-repo.git
touch .gitignore
git commit -m "Add file .gitignore" .gitignore
git push origin master

Congratulations! We have now a Git repository we can share with others securely. In the next article we will set up a code review system which we can use together with this repo.

Git Repo in Shared Hosting #2 - Install Review Board

Extra: What is my Path? (In the Apache Server)

If your hosting service runs the Apache web server in a different server, the home directory might very well not be mounted or it is located in a different path.

To find out the path, copy this script to directory ${REPOS_HTTP}/shared.

1
2
3
4
5
6
#!/usr/bin/env bash
echo "Content-type: text/html"
echo
echo '<html> <head> <title> CGI script </title> </head> <body><h1>Current dir: '
echo "$(pwd)"
echo '</h1></body></html>'

Now make it executable.

chmod 700 ${REPOS_HTTP}/shared/mypath.cgi

Then access the URL from your browser. Now you know. Delete the script!

rm ${REPOS_HTTP}/shared/mypath.cgi

    
 
 

Comments