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.
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.