Managing Git branch level permissions with TeamForge and Gerrit
Since version 6.2, TeamForge supports Git, using Gerrit as the backend. Gerrit is not only our Git server, it is also a great code review system.
In this blog post we will talk about Git branch level permissions and how to control them from TeamForge. Why would you like to use it in the first place? Well, with branch level permissions you can specify that certain groups of users can only read or push certain “refs” (branches or tags) but not others. There are two options for doing that:
- Use the custom repository category, which is turning off TeamForge autopilot and lets you, based on synched TeamForge project roles, control branch level access directly from Gerrit.
- Define your own repository category with different permissions per branch (or pattern to be more precise) and use it for multiple repositories.
You could ask: Why two options? Each option has its own use case. The first one is more suitable if your branch based permissions vary a lot from repository to repository. On the other hand, if you already managed (or consider) a common branch naming schema across repositories, the second option might be more appealing to you, since you don’t have to manually apply the same access rights fine tuning again and again.
Now let’s have an example which will show us how to use both options in real life.
But before we start, let’s address one question: what is a repository category? A short answer is that it is a mapping between TeamForge SCM permissions and Gerrit access rights. In other words it defines how Gerrit access rights are derived from Source Code Admin, Delete/View, Commit/View and View Only permissions from TeamForge. Those mappings are defined per repository – that is, all branches of a given repository are affected.
In TeamForge you typically can choose from three pre-defined repository categories: default (no review), optional_review and mandatory_review. Those categories apply to all branches and cover most of the use cases we have seen at our customers. They do not require you to look into Gerrit’s Web UI at all, in other words, they are using TeamForge auto pilot.
There is a fourth category called custom. In this one only Source Code Admin is mapped to Gerrit OWN access right. All the other permissions are fine tuned by users belonging to administrator roles from Gerrit. Role definition and membership is still controlled from TeamForge.
Back to the example. We would like to fulfill the following:
– Some users (let’s call them observers) should only have read access to the master branch.
– Developers can read all branches but only push to development branches following a certain naming pattern. We will use refs/heads/devel/* for our example.
– Release managers can read and push to all branches
How to achieve that?
Regardless of which option we choose some preparation on TeamForge side is necessary.
We need to decide what SCM permissions from TeamForge to use. In our case it is quite obvious: we will use View only permission for observers. Commit/View for developers and Source Code Admin for release managers. But SCM permissions alone are not enough. Just in case you didn’t know, in TeamForge users are not assigned directly to permissions but are assigned to roles, and roles have SCM permissions. That means we will have to create three project roles (with corresponding permissions). Let’s name them according to our requirements.
To create a role we go to TeamForge in Project Admin Menu and select Permissions.
Here how it should look after all three roles have been created.
Do not forget to assign the permissions to the roles. Let’s edit developer role and add Commit/View in Source Code Permissions:
Of course we need to add permissions to all three roles.
Eventually we will have: release manager (with Source Code Admin), developer (with Commit/View) and (observer with View only).
Last but not least, we will also need some users associated with those roles. Here it comes:
Now, we are done with the preparation part and ready to talk about our two options.
Let’s consider the first one – using custom category and fine tuning from Gerrit.
First, we need to login into TeamForge as admin and create repository with [RepoCategory:custom] in our demo project. Of course you could also use an existing repository and add [RepoCategory:custom] to its description.
After the new repository got created in TeamForge we go to Gerrit (embedded into TeamForge) and choose the project in question. Please note that it is a Gerrit project so it has the name of its corresponding TeamForge repository. In our case it is option1. We can see that Lucy, our admin, who belongs to TeamForge’s project role release manager, has access to option1 project and that she is able to add access rights to it:
[Please note that I unchecked the “Show Inherited Rights” checkbox to remove unrelated rights.]
Now it is time to add the necessary rights for the other TeamForge project roles one by one. As long as you are member of a TeamForge project role, Gerrit will even provide auto completion. Let’s start with the TeamForge observer role:
We continue to add required rights until our access rights table looks like this:
As we can see here release manager has admin rights, developer has read only access to everything besides refs/heads/devel/* where he can also push and observer has read only access to refs/heads/master.
As stated before, TeamForge will not override any of your access rights if you selected the custom category, so you can setup different configurations for every single project role. Let’s say, you had two different developer roles, both with Commit/View in TeamForge but one group should push to refs/heads/devel1/* and the other to refs/heads/devel2/*, you can just change this ad-hoc by using different ref sepcs for the two project roles synched to Gerrit. Of course it is also possible to have different settings for other Gerrit categories like review, submit and verify. While this allows you the most flexibility possible, it is also the most demanding approach since Gerrit access right are quite hard to understand and beginners might be confused by their complexity. This is why most projects typically start with predefined categories like default, optional_review or mandatory_review which do not require you to enter Gerrit Web UI at all. To match the basic requirements of all our customers those pre-shipped categories apply to all branches. If you like to see how you can define your own category with Git branch level permissions, read on!
Now we will focus on the second option – using a new repository category, which can be used across multiple repositories.
Let’s see how to implement it in this case:
First we define our new category. We will call it branch_based.
Let’s edit the gerritforge.mappings file located in the etc directory of the ctf-git-integration installation.
Typically it will be located under /opt/collabnet/gerrit/etc/gerritforge.mappings
By default, it contains the definitions for the out of the box categories default, mandatory_review, optional_review and custom. We will copy the default category definition and modify it according to our needs. In our case we only have to modify the properties corresponding to View Only (scm_view) and Commit/View (scm_commit) to add restrictions on certain refs. For more details on the exact format of this file, please refer to our README typically located under /opt/collabnet/gerrit/doc/README.pdf or at http://help.collab.net.
Let’s have a look at our new properties that should be added to the existing ones:
# branch based: branch_based.scm_admin.1=READ,1,3 branch_based.scm_admin.2=pHD,1,3 branch_based.scm_admin.3=pTAG,1,2,refs/tags/* branch_based.scm_admin.4=FORG,1,3 branch_based.scm_admin.5=OWN,1,1 branch_based.scm_delete.1=READ,1,3 branch_based.scm_delete.2=pHD,1,3 branch_based.scm_delete.3=pTAG,1,2,refs/tags/* branch_based.scm_delete.4=FORG,1,2 branch_based.scm_commit.1=READ,1,1 branch_based.scm_commit.2=READ,1,3,refs/heads/devel/* branch_based.scm_commit.3=pHD,1,3,refs/heads/devel/* branch_based.scm_view.1=READ,1,1,refs/heads/master branch_based.none.1=READ,-1,-1 branch_based.keep_rights_added_in_gerrit=false
And after restarting Gerrit integration with the following command:
$ service gerrit restart
we are ready to use our newly defined mappings.
Let’s go to our demo project in TeamForge and create a git repository which will use our mappings:
What is important to note here is the part: [RepoCategory:branch_based] in the description field. This tells Gerrit to use our new mappings for this repository. The category name has to exactly match the one we used in our properties definition in gerritforge.mappings file (it is branch_based in our case).
We now go into Gerrit again and have a look at the access rights for option2:
Looks familiar? Yes, it is exactly the same configuration as in the first option, just realized differently with the potential to use it across multiple TeamForge Git repositories. You can also switch between categories if you change the repository description.
Now it is time to verify that our settings worked. We will do that for the first option, leaving the second one as an exercise to the reader.
First, let’s try with Joe, our developer. After cloning and preparing one commit the git status should give us something like this:
$ git status # On branch master # Your branch is ahead of 'origin/master' by 1 commit. # nothing to commit (working directory clean)
So it is time to check if Joe is able to push to master:
$ git push origin HEAD:master Total 0 (delta 0), reused 0 (delta 0) To ssh://joe@tf62personal:29418/option1 ! [remote rejected] HEAD -> master (prohibited by Gerrit) error: failed to push some refs to 'ssh://joe@tf62personal:29418/option1'
So far so good, as this attempts have failed as expected.
Now, let’s try to create a new branch under refs/heads/devel/:
$ git push origin HEAD:devel/branchOne Counting objects: 4, done. Writing objects: 100% (3/3), 237 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To ssh://joe@tf62personal:29418/option1 * [new branch] HEAD -> devel/branchOne
Great, it worked. Now let’s check if Robin, the observer has read-only access to master.
A good way of checking this would be cloning the repository.
Check if git clone works:
$ git clone ssh://robin@tf62personal:29418/option1 Cloning into 'option1'... remote: Counting objects: 5, done remote: Finding sources: 100% (5/5) remote: Total 5 (delta 0), reused 3 (delta 0) Receiving objects: 100% (5/5), done.
Yes it worked, so the read permission works as expected.
So let’s see which branches are visible:
$ git ls-remote origin 1d3b415c5c1ea3c2c90318ef7410096d39b8add0 HEAD 1d3b415c5c1ea3c2c90318ef7410096d39b8add0 refs/heads/master
We cannot see any other branches here, so it seems Robin can access only master.
The same command run by Joe (our developer) gives us the following:
$ git ls-remote origin 1d3b415c5c1ea3c2c90318ef7410096d39b8add0 HEAD 3ac13ac746a6d11a74843d2d4fd33fec0551a600 refs/heads/devel/branchOne 1d3b415c5c1ea3c2c90318ef7410096d39b8add0 refs/heads/master
We can see here the development branch branchOne which was not available to Robin.
Now let’s try to prepare a commit and push as Robin (observer):
$ git push origin HEAD:master fatal: Upload denied for project 'option1' fatal: The remote end hung up unexpectedly
And for another branch:
$ git push origin HEAD:devel/branchTwo fatal: Upload denied for project 'option1' fatal: The remote end hung up unexpectedly
As you can see it didn’t work and that’s good because it was expected like that.
Settings for release manager are equivalent to default admin settings so it is safe to assume that it just works But for the sake of completeness we will push to master as Lucy:
$ git push origin HEAD:master Counting objects: 4, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 286 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To ssh://lucy@tf62personal:29418/option1 a56343f..0f299c2 HEAD -> master
It worked as expected.
Last thing to consider is what will happen if we work with more than one repository. If we use the first option (category custom) you will have to configure access rights in Gerrit for every repository and TeamForge project role separately. With the second option (defining your own category), all repositories using it will automatically follow your templatized access right configuration. On the other hand, they have to share the same branch naming conventions and you will not be able to make differentiations based on individual TeamForge roles (but only on TeamForge SCM access rights).
So, let’s summarize: branch based permissions are working out of the box on Gerrit and there are two different ways of configuring it. First one is to use the predefined repository category custom which is turning off TeamForge autopilot and lets you, based on synched TeamForge project roles, control branch level access directly from Gerrit.
The second, requires you to define your own repository category, which might sound more work intense initially. However, once defined, repository category can be used across multiple Git repositories managed in TeamForge. It is also possible to use more than one repository category definitions in one TeamForge project (one per repository) if you cannot narrow down to one branching naming pattern or require different Gerrit access rights per TeamForge SCM permission.
Do you already have your own repository categories defined? Which naming patterns do you use for your branches? Or maybe you have your own way how to deal with branch based permissions? Please share that knowledge with us by commenting here. Follow up questions appreciated.