Allow 2+ Users to Be Able to Share a Linux Directory with 2775 vs 0775

This is handy if you have 2 different users deploying code to the same directory, such as a deploy user and CI user.
Quick Jump: Going Over the Permission Problem | 0775 vs 2775 Permissions for a Directory | Demo Video
Prefer video? There’s a video version of this blog post on YouTube that goes into a bit more detail about certain topics listed below.
This could be used for a number of things but let’s focus on 1 concrete example.
Going Over the Permission Problem
For example, let’s say you git push
your code to your server and it ultimately gets checked out to /srv/mysite
. This could be a static site or your web application, it doesn’t matter.
Now let’s say you sometimes deploy your code from your dev box by pushing your code as the deploy
user but you also have CI set up which can push code to your server.
For security reasons it would be a good idea to use a heavily restricted ci
user that can only git push
code to your server and nothing more. This user wouldn’t even have an ability to run a shell like Bash, instead you can use git-shell which provides restricted git access.
It’s worth pointing out when you git push to your server, part of that process will run a git checkout
in perhaps a git post receive hook. The details aren’t important for this post’s topic but it’s important to know that files will be created as the user who performed the git push.
That’s because when you git push your code, you’re SSH’ing into your server as that user. The post receive hook runs and that script runs all commands as that user, such as git checkout
and whatever else you want to do.
That means /srv/mysite
(the checkout directory) needs to be writeable by both users. You can do this without doing anything fancy with permissions by simply creating a common group such as sshusers
or whatever you decide to name it and add both the deploy
and ci
users to that group.
You can create a new system group with groupadd --system sshusers
. The --system
flag ensures your group will be created with a group id less than 1000 or more specifically what’s defined in /etc/login.defs
.
If you run chown deploy:sshusers /srv/mysite
then your directory will allow both the deploy
and ci
users to be able to write to it. That would look like this:
drwxrwxr-x 2 deploy sshusers 4096 Sep 24 22:46 mysite
Then as the deploy
user you could do touch /srv/mysite/test
and you’ll end up with:
-rw-rw-r-- 1 deploy deploy 0 Sep 24 23:04 test
Then let’s say you git push your code as the ci
user. You’ll end up with:
-rw-rw-r-- 1 ci ci 0 Sep 24 23:07 index.html
This technically works, but as the ci
user you’ll get a permission error if you try to overwrite a file owned by deploy:deploy
, such as if you were able to run touch /srv/mysite/test
(assuming the user had permission to run the touch
command, I’m only doing this for an example). That will produce touch: cannot touch 'test': Permission denied
.
Basically what this means is if you git push as the deploy
user you’ll get permission errors when git pushing as the ci
user and vice versa. Neither user will be able to write to files owned by the other user.
That leads us to the problem. How can we have 2 different users be able to git push or more generally write to each others’ files in a shared directory?
0775 vs 2775 Permissions for a Directory
By default when you create a directory on Linux, unless you modified the default behavior then you’ll get 0775
as the permissions, as seen below:
$ stat /srv/mysite
File: /srv/mysite
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: fd00h/64768d Inode: 134418 Links: 2
Access: (0775/drwxrwxr-x) Uid: ( 0/ root) Gid: ( 998/sshusers)
Access: 2022-09-24 23:04:55.682777452 +0000
Modify: 2022-09-24 23:04:53.682799318 +0000
Change: 2022-09-24 23:04:53.682799318 +0000
Birth: 2022-09-24 22:46:03.543155216 +0000
The important line is Access: (0775/...)
which is the 4th line of output.
If you don’t specify the 0
then it defaults to 0 which gives us the default behavior we all know. If a user creates a file in this directory then it will own the file at the user and group level.
Here’s a little chart of the possible values you can set:
0: setuid, setgid, sticky bits are unset
1: sticky bit is in place
2: setgid bit is in place
3: setgid and sticky bits are in place
4: setuid bit is in place
5: setuid and sticky bits are in place
6: setuid and setgid bits are on
7: setuid, setgid, sticky bits are activated
We can use 2775
which will set the setgid
bit. Now if a user creates a file in this directory then it will own the file but the group will be set to whatever group owns the parent directory. That’ll solve our problem!
That means we can do this:
chmod 2775 /srv/mysite
And now we can see the new access permissions:
$ stat /srv/mysite
File: /srv/mysite
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: fd00h/64768d Inode: 134418 Links: 2
Access: (2775/drwxrwsr-x) Uid: ( 1000/ nick) Gid: ( 998/sshusers)
Access: 2022-09-24 23:04:55.682777452 +0000
Modify: 2022-09-24 23:04:53.682799318 +0000
Change: 2022-09-24 23:36:15.845483124 +0000
Birth: 2022-09-24 22:46:03.543155216 +0000
Now as the deploy
user you could do touch /srv/mysite/cool
and you’ll end up with:
-rw-rw-r-- 1 deploy sshusers 0 Sep 24 23:38 cool
Notice how the group is set to sshusers
. The ci
user and any other users in the sshusers
group will be able to write to this file without getting a permission denied error. Now we can safely deploy our code to our server as 2+ different users.
Victory!
Demo Video
Note worthy topics
In the video I incorrectly said that setgid
is a type of sticky bit, that’s not the case. Both setgid
and sticky
are their own types of bits.
Timestamps
- 0:05 – A use case for wanting to do this
- 2:07 – Creating a directory to demo things out
- 3:10 – Getting a permission denied error as a different user
- 3:59 – Making a common group that both users belong to
- 5:06 – Now a different user can create files in the directory
- 5:23 – We’re good to go right? Not quite
- 6:31 – setgid to the rescue
- 8:02 – Setting our directory to 2775
- 9:04 – Everything works how we want it to
Do you use this pattern on any of your servers? Let me know below.