Git Repo in Shared Hosting #1 - Secure HTTP Access to a Repo
Posted by Mikko Koivunalho in How-To
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