rss

How to host and manage Git repositories

Monday, October 28, 2013


In this post I explain how to setup and manage Git repositories on Debian Wheezy. We use a tool called gitolite to manage and control the access to Git repositories using a single server user account and SSH keys as an identification method. Hence, we do not have to create shell accounts on the server for each user.

Installation of Git and Gitolite

In order to reproduce exactly this tutorial you will need to install sudo. Otherwise you have to switch to a super user account to perform the commands starting with sudo without the word sudo. You can install sudo using the command:

apt-get install sudo
sudo adduser <your current username> sudo

The next step is to install git. You need it installed on both: server and local machine.

apt-get install git
Once git is installed, it might be of your best interest to configure a few global variables such as your real name and email:
git config --global user.name <your real name>
git config --global user.email <your email>

The next thing to do is to create a user that will own the repositories you want to manage. This user is usually called git, but any name will work, and you can have more than one per system if you really want to. The user does not need a password, but does need a valid shell (otherwise, SSH will refuse to work).

sudo adduser --system --shell /bin/sh --gecos 'git user' --group --disabled-password --home /home/git git

You may change the home path to suit your taste. A successful user creation will look similar to:

Adding system user 'git'...
Adding new group 'git' (310).
Adding new user 'git' (310) with group 'git'.
Creating home directory '/home/git'.

You will need a public SSH key to continue. If you already have a private key and you know what is that, just skip the next step. Otherwise, you may generate one on your local machine.

ssh-keygen -t rsa

The public key will be in $HOME/.ssh/id_rsa.pub. Copy this file to your server. For example, you can use scp:

scp ~/.ssh/id_rsa.pub git@YOUR_SERVER_HOSTNAME:id_rsa.pub

After that, the next step is to get and install gitolite.

su git
cd /home/git
git clone git://github.com/sitaramc/gitolite
sudo gitolite/install -ln /usr/bin/

Now we have to setup the first gitolite's administrator. You will have to rename the file id_rsa.pub if you do not like the username id_rsa.

gitolite setup -pk ~/id_rsa.pub

That last command creates new Git repository called repositories/gitolite-admin on the server. You can change the location of the folder repositories by replacing it with a symbolic link. Also, you can remove id_rsa.pub from the server once the previous operations is concluded.

Run git clone git@YOUR_SERVER_HOSTNAME:gitolite-admin on your workstation to clone the repository gitolite-admin to your workstation.

This repository that you just cloned contains all the files needed to create repositories for your projects, add new users, and defined access rights. Edit the settings as you wish, commit, and push. Once pushed, gitolite will immediately make your changes take effect on the server.

Create new repositories

Create a new directory for your project and add the files.

mkdir myproject
cd myproject
git init
touch readme.txt

Now commit and push everything:

git add .
git commit -a -m "initial"
git remote add origin git@YOUR_SERVER_HOSTNAME:myproject.git
git push origin master

Pretty easy! Next we will explain how to add new users and set access rights

Adding new users and setting access rights

Adding users is easy. First, gather the public SSH keys of all your users; name them <username>.pub, e.g. "alice.pub" and "bob.pub", and drop them into keydir/ of your local gitolite-admin repository.

Second, edit gitolite.conf. You can group users or repos for convenience. The group names are just like macros; when defining them, it does not even matter whether they are projects or users; that distinction is only made when you use the "macro".

@oss_repos      = linux perl git gitolite
@secret_repos   = myproject

@admins         = bob
@engineers      = matt
@interns        = alice
@staff          = @admins @engineers @interns

You can control permissions at the "ref" level. In the following example, interns can only push the "int" branch. Engineers can push any branch whose name starts with "eng-", and tags that start with "rc" followed by a digit. And the admins can do anything (including rewind) to any ref.

repo @oss_repos
    RW  int$                = @interns
    RW  eng-                = @engineers
    RW  refs/tags/rc[0-9]   = @engineers
    RW+                     = @admins

This above configuration means: (RW+=Read Write Push, R=Read, RW = Read Write). Then we have to execute the following command to add all new SSH keys and update the configurations.

git add -A 

Now commit and push everything:

git commit -a -m "Updated contributors and settings"
git push origin master

Alice and Bob will also have commit rights.

A convenient feature is what happens when you try to ssh git@YOUR_SERVER_HOSTNAME. Gitolite shows you what repos you have access to.

Enable public access to some repositories

If you are running a public project, you will have your users with commit rights, and then you will have everyone else. How do we give everyone else read-only access without fiddling with SSH keys? We just use git-daemon [2]. This is independent of gitolite and it comes with git itself.

sudo -u git git-daemon --base-path=/home/git/repositories/ --export-all
This will make all the repositories you manage with gitolite read-only for the public. Users can then clone projects like this:
git clone git://YOUR_SERVER_HOSTNAME/PROJECT_NAME.git

To export only some repositories and not others, you need to touch git-daemon-export-ok inside the root directory (e.g. /home/git/repositories/some_project.git) of each repo that you want public. Then remove “–export-all” from the git-daemon command above.

Securing SSH server

First, I would suggest using fail2ban to prevent brute force login attempts and I would disable logging in as root via SSH. This means an attacker had to figure out both the username and the password making an attack more difficult. Change PermitRootLogin to "no" in your /etc/ssh/sshd_config.

I would open /etc/ssh/sshd_config, find the line that says #PasswordAuthentication yes, and change it to PasswordAuthentication no. Then I would also limit the users that can SSH to the server. Either by group or just specific users. For example, add AllowGroups group1 group2 or AllowUsers user1 user2 to limit who can SSH to the server.

Additionally, I would use TCPWrapper as an additional security layer. I would block all IP addresses in your /etc/hosts.deny by adding the following line:

sshd:  ALL

and open up SSH to specific IP addresses using /etc/hosts.allow. For example adding the following line.

sshd: 192.168.1.1, 10.10.0.0/255.255.0.0
Finally execute:
sudo service ssh restart

References

[2] Hosting Git Repositories, the Easy (and Secure) Way. 2013. http://scie.nti.st/2007/11/14/hosting-git-repositories-the-easy-and-secure-way/

[2] Git on the Server - Gitolite. 2013. http://git-scm.com/book/ch4-8.html

[3] Android Tools: Using the Hierarchy Viewer. http://mobile.tutsplus.com


How to overcome the iframe zoom limitation in iOS

Friday, March 15, 2013

The growing demand for fast prototyping of new mobile web applications is raising new challenges to mobile developers. An example, is the security limitation in iOS's Safari: iframes aren't allowed to hide content or zoom. Although the main idea is to protect the users from hidden malicious elements, it also restricts their actions as they cannot see the content with the zoom they want. This raises usability problems.

Nevertheless, there is the possibly to simulate this feature by hacking the CSS property zoom. This property controls the magnification level for the current element. The rendering effect for the element is that of a "zoom" function on a camera. The possible values are:

normal No magnification is applied. The object is rendered as it normally would be.
[number] Positive floating point number indicating a zoom factor. Numbers smaller than 1.0 indicate a “zoom out” or size reduction effect, while numbers greater than 1.0 indicate a magnifying effect.
[percentage] Positive floating point number, followed by a percentage character (“%”) which indicates a zoom factor. Percentages smaller than 100% indicate a “zoom out” or size reduction effect, while numbers greater than 100% indicate a magnifying effect.

You might want to take a look at the following CSS3 Transforms, if you want to create a cross-browser zooming mechanism:

zoom: 0.85;
-moz-transform: scale(0.85);
-moz-transform-origin: 0 0;
-o-transform: scale(0.85);
-o-transform-origin: 0 0;
-webkit-transform: scale(0.85);
-webkit-transform-origin: 0 0;

Now, we shall see an example:

div { zoom: 150% }
<div style="zoom: 150%">Hello!</div>

Of course, I would try to avoid the use of the zoom property to increase the font size. I would probably use font-size: 200%, as it is standard and supported.

The next step is to use Javascript to dynamically modify these properties. Ideally, we want to use pinch zoom gesture. The easiest solution is to use TouchSwipe, a jQuery plugin for touch and gesture-based interaction.

First, we define a JavaScript function that updates the CSS zoom property. In this case, I set the zoom to all iframe elements. However you can do it for a specific element as well.

function updateZoomProperty( pinchZoom )
{
  $("iframe").css("zoom", pinchZoom);
  $("iframe").css("-webkit-transform", "scale(" + pinchZoom + ")");
  $("iframe").css("-webkit-transform-origin", "0 0");
  $("iframe").css("-moz-transform", "scale(" + pinchZoom + ")");
  $("iframe").css("-moz-transform-origin", "0 0");
  $("iframe").css("-o-transform", "scale(" + pinchZoom + ")");
  $("iframe").css("-o-transform-origin", "0 0"); 
}

Then, we define the function that recognizes pinch zoom gesture, using TouchSwipe.

function activateSwipe(itemId, touchItemId)
{
  $('#' + itemId).contents().find(touchItemId).swipe( {
    pinchIn:function(event, direction, distance, 
                     duration, fingerCount, pinchZoom)
    {
       updateZoomProperty(pinchZoom);
    },
    pinchOut:function(event, direction, distance, 
                      duration, fingerCount, pinchZoom)
    {
       updateZoomProperty(pinchZoom);
    },
    pinchStatus:function(event, phase, direction, 
                         distance, duration, fingerCount, 
                         pinchZoom) 
    {
       console.log(
             "Pinch zoom scale " + pinchZoom +
             " <br/>Distance pinched " + distance +
             " <br/>Direction " + direction
 );
    },
    fingers:2,
    pinchThreshold:0
  });
}

Now, we can define a function to dynamically inject an iframe.

function injectFrameElement( id, itemId, url )
{
  var h = $(document).height();
   $(itemId).html(
      '<iframe id="' + id + '" width="100%" height="' + h + 'px" ' +
        'frameborder="0" marginheight="0" marginwidth="0" src="' + url + '" ' +
        ' onload="activateSwipe(\'' + id + '\',\'body\')">' + 
          '<p>Your browser does not support tampering 
               with iframe content.</p>' +
        '</iframe>'+
        '<small>Loading page ...</small>'
   );
}

And voilà! We only have to use the function in the following way.

injectFrameElement('frameviewer', '.myframe');
In this case, it will add the iframe to the following HTML element:
  <div class="myframe"></div>
This is just a draft idea. Please let me know your opinion.