I use Git as a version control and deployment system. When a website gets pushed to a server, all files get pulled into the web root (i.e.
htdocs) by a user named git executing
git pull in the post-receive hook.
By default, all files and folders git creates have 664 and 775 permissions, respectively, and are owned by that user. 664 translates to the user and group being able to read and write, and everyone else only being able to read, and 775 translates to the user and group being able to read, write and execute, and everyone else only being able to read and execute. (That’s a mouthful!)
-rw-rw-r-- 1 git git 30 Aug 15 23:04 test-file.txt drwxrwxr-x 1 git git 102 Aug 15 23:04 test-directory
Now, in an instance where you need a folder in
htdocs writable by another user, like apache, for let’s say a caching system, you need to be able to set those particular permissions accordingly.
To accomplish this, you really only have two options:
- Set permissions of files to 666 and folders to 777
- Set the owner or group to apache (or a group that apache is a member of)
Personally, I favor restrictive permissions over convenience, so option #1 is out, which means we’re going to take a look at how to implement option #2.
- Keep website files and folders owned by git, which essentially makes them read-only to apache and the public
- Allow apache to create and modify files and folders only in specified folders
- Maintain a streamlined deployment, meaning all permission modifications need to happen in the post-receive hook using only the permissions the git user already has
Note: I’m running CentOS/RedHat, meaning that some commands may differ if you’re using a different operating system.
1. Set group ID (setgid bit)
The first thing we need to do is ensure that everything inside
htdocs remains owned by git, in other words, ensure only git can create and modify files and folders. This leaves us with having to change the group of the folders that apache needs to write to, but the problem is that git doesn’t have the proper permissions to use the
What we’ll do instead is create a new group called
web, add git and apache as members of that group, and then enforce that all files and folders created in
htdocs are automatically placed in the
Let’s elevate our permissions to root and create a new group:
[[email protected] ~]$ sudo su - [[email protected] ~]$ groupadd web
Next we’ll add both git and apache to that group:
[[email protected] ~]$ usermod -a -G web git [[email protected] ~]$ usermod -a -G web apache
Lastly we’ll set the setgid bit on the
[[email protected] ~]$ ll /var/www/domains/domain.com/www drwxrwxr-x 5 git git 4096 Aug 15 23:56 htdocs [[email protected] ~]$ chgrp web /var/www/domains/domain.com/www/htdocs [[email protected] ~]$ chmod g-w /var/www/domains/domain.com/www/htdocs [[email protected] ~]$ chmod g+s /var/www/domains/domain.com/www/htdocs [[email protected] ~]$ ll /var/www/domains/domain.com/www drwxr-sr-x 5 git web 4096 Aug 15 23:56 htdocs
- Print out current permissions for
- Set the group of
- Remove write permissions for the group on
- Set the setgid bit on
- Print out new permissions (note the ‘s’ flag in the group)
Going forward, any file or folder that’s created in
htdocs will automatically set
web as the group. If you already have files and folders in
htdocs, you can recursively put them in the
web group, but be conscious about the effect this will have on everything you have in that folder.
chgrp -R web /var/www/domains/domain.com/www/htdocs
At first it my seem tempting to simply set the group to
apache, however when git later modifies a folder to allow apache to create files and folders inside, the setgid bit is unset. The reason for this is that git does not belong to the
apache group. To circumvent this particular issue, we created the new
web group that both users could be a member of, which allows git to effectively give apache write permissions without unsetting the setgid bit.
2. Set umask to 022
By doing the above, we’ve violated one of our goals, and that’s the fact that apache will now have write permissions on all files and folders after
git pull is run for the first time.
I don’t know if you’ve ever noticed, but when you create a new file or folder using root, the permissions are actually set to 644 and 755, respectively. Those default permissions are set via a command called
umask, and we’ll now emulate this behavior for the git user.
As a side note, if you want to learn more about
umask, there is a great article about what umask is and how it works.
There is a file called
profile which sets a default
umask value for all system users:
[[email protected] ~]# cat /etc/profile ... # By default, we want umask to get set. This sets it for login shell # Current threshold for system reserved uid/gids is 200 # You could check uidgid reservation validity in # /usr/share/doc/setup-*/uidgid file if [ $UID -gt 199 ] && [ "`id -gn`" = "`id -un`" ]; then umask 002 else umask 022 fi ...
You’ll notice that by default, all users with a user ID of 200 or greater will receive a
002, and all users with ID 199 or less, a
022. Looking at the chart below, you can look up what this translates to:
- 0: read, write and execute
- 1: read and write
- 2: read and execute
- 3: read only
- 4: write and execute
- 5: write only
- 6: execute only
- 7: no permissions
002 means rwx for owner, rwx for group, and r-x for everyone else. (The default for new system users).
022 means rwx for owner, r-x for group, and r-x everyone else. (The default for built-in system users).
We’ll use the git user’s
.bashrc file to automatically set the
.bashrc file is executed every time a user logs in or opens a new terminal window. Here is a brief explanation from linux.die.net:
Today, it is more common to use a non-login shell, for instance when logged in graphically using X terminal windows. Upon opening such a window, the user does not have to provide a user name or password; no authentication is done. Bash searches for ~/.bashrc when this happens, so it is referred to in the files read upon login as well, which means you don’t have to enter the same settings in multiple files.
Let’s edit the file now:
[[email protected] ~] su git [[email protected] ~]$ vi ~/.bashrc ... # User specific aliases and functions umask 022
- Switch over to the git user
- Edit the
.bashrcfile in the home directory
umask 022to the bottom
3. Use post-receive hook to update permissions
The last step, and this will be specific to your needs, we’ll use the post-receive hook to set permissions to allow apache to write to specified folders. I’ve pulled out and modified the relevant commands from my post-receive hook to provide an example:
# Set path to htdocs htdocs="/var/www/domains/domain.com/www/htdocs" # Unset global GIT_DIR so script can leave repository unset GIT_DIR # Move into htdocs cd $htdocs # If cache directory does not exist if [ ! -d "$htdocs/cache" ]; then # Create cache directory for cache files mkdir $htdocs/cache # Allow Apache to write to cache chmod g+w $htdocs/cache fi
That’s pretty much it, and it meets all of our goals. If you have any questions or comments, leave them below.
December 30, 2013 Update: I recently wrote an article on how to deploy WordPress as a submodule that showcases a more complete post-receive hook, which may be of interest to you.