Linux File Permissions: Giving One User Read Access

I recently did some work setting up multiple users on a RHEL box of mine and I wanted to document some understandings regarding giving fine-grained, user-based permissions to files.

chmod is too broad

Linux file permissions dictate how users and processes interact with files and directories, ensuring secure and controlled access to the system. The chmod command, short for “change mode,” is one tool for modifying these permissions. There are three primary permission categories: owner, group, and others. Each category has three permission types: read (r), write (w), and execute (x). The chmod command utilizes a numeric or symbolic representation to grant or revoke permissions. For example, ‘chmod 755 file’ grants the owner full permissions (read, write, execute), while the group and others get read and execute permissions. So that works for the file owner and the group that the user is in, but the last permission category of “others” is just wide open.

This is where ACLs come in. The setfacl command in Linux extends the traditional file permission model by enabling access control lists (ACLs). ACLs allow for more granular control over file and directory permissions beyond the standard owner, group, and others. With setfacl, users can assign specific permissions to individual users and groups, offering a more nuanced approach to access management. The command allows the addition or modification of ACL entries, specifying both permissions and users or groups. For instance, ‘setfacl -m u:user:rw file’ grants read and write permissions specifically to the user ‘user.’ This flexibility makes setfacl a powerful tool for administrators who need to customize access rights in a more fine-grained manner than traditional Linux file permissions.

Example Walkthrough

We are going to add two users to the system. Then we will switch into the first user and simply create a file. We are also going to print out the chmod and getfacl for the file.

[root@ocpservices ~]# useradd user1
[root@ocpservices ~]# useradd user2
[root@ocpservices ~]# su user1
[user1@ocpservices root]$ cd ~
[user1@ocpservices ~]$ touch test.txt
[user1@ocpservices ~]$ echo "success!" > test.txt
[user1@ocpservices ~]$ ls -la /home/user1/test.txt
-rw-r--r--. 1 user1 user1 0 Feb 16 10:57 /home/user1/test.txt
[user1@ocpservices ~]$ getfacl /home/user1/test.txt
getfacl: Removing leading '/' from absolute path names
# file: home/user1/test.txt
# owner: user1
# group: user1
user::rw-
group::r--
other::r--

The chmod for the file is rw- for the user, r-- for the group and r-- for others. The ACL for the file is the same. Someone might look at these permissions and think “it clearly gives read access to others. I should be able to access that file in read only from other accounts”. But Linux doesn’t work like that. Here’s the output from user2 when they try to cat the file.

[user2@ocpservices ~]$ cat /home/user1/test.txt
cat: /home/user1/test.txt: Permission denied

The key to the answer lies in the directory permissions. Look at the output of the chmod and ACL for the containing directory.

[user1@ocpservices ~]$ ls -la /home/user1
total 12
drwx------. 2 user1 user1  78 Feb 16 10:57 .
drwxr-xr-x. 7 root  root   73 Feb 16 10:56 ..
-rw-r--r--. 1 user1 user1  18 Nov 24  2022 .bash_logout
-rw-r--r--. 1 user1 user1 141 Nov 24  2022 .bash_profile
-rw-r--r--. 1 user1 user1 492 Nov 24  2022 .bashrc
-rw-r--r--. 1 user1 user1   0 Feb 16 10:57 test.txt
[user1@ocpservices ~]$ getfacl /home/user1
getfacl: Removing leading '/' from absolute path names
# file: home/user1
# owner: user1
# group: user1
user::rwx
group::---
other::---

The directory permissions do not allow any access to anyone except the owner (of course, root excluded). So the access issue is really around the folder itself. Ok, no worries. So you might think here “I’ll just give read access to others for the folder as well and that should solve the problem”. Let’s do that and print out the permissions for not only the containing folder, but let’s validate read access on all parent folders.

[user1@ocpservices ~]$ chmod o+r /home/user1
[user1@ocpservices ~]$ ls -la /home/user1
total 12
drwx---r--. 2 user1 user1  78 Feb 16 10:57 .
drwxr-xr-x. 7 root  root   73 Feb 16 10:56 ..
-rw-r--r--. 1 user1 user1  18 Nov 24  2022 .bash_logout
-rw-r--r--. 1 user1 user1 141 Nov 24  2022 .bash_profile
-rw-r--r--. 1 user1 user1 492 Nov 24  2022 .bashrc
-rw-r--r--. 1 user1 user1   0 Feb 16 10:57 test.txt
[user1@ocpservices ~]$ ls -la /home
total 0
drwxr-xr-x.  7 root    root     73 Feb 16 10:56 .
dr-xr-xr-x. 18 root    root    235 Feb 15 06:10 ..
drwx---r--.  2 user1   user1    78 Feb 16 10:57 user1
drwx------.  2 user2   user2    83 Feb 16 11:12 user2
[user1@ocpservices ~]$

Now that should work.

[user2@ocpservices root]$ cat /home/user1/test.txt
cat: /home/user1/test.txt: Permission denied

Nope. user2 still does not have access. The reason is in Linux, execute permissions on a folder are required for accessing its contents, including files. This is because the execute permission allows a user to traverse the directory structure. To read a file, the user needs to navigate through the directories leading to that file, and execute permissions on each directory in the path are necessary for this traversal.

So let’s backout the read permission and just set executable.

[user1@ocpservices ~]$ chmod o-r /home/user1
[user1@ocpservices ~]$ ls -la /home/user1
total 16
drwx------. 2 user1 user1  99 Feb 16 11:15 .
drwxr-xr-x. 7 root  root   73 Feb 16 10:56 ..
-rw-------. 1 user1 user1  86 Feb 16 11:15 .bash_history
-rw-r--r--. 1 user1 user1  18 Nov 24  2022 .bash_logout
-rw-r--r--. 1 user1 user1 141 Nov 24  2022 .bash_profile
-rw-r--r--. 1 user1 user1 492 Nov 24  2022 .bashrc
-rw-r--r--. 1 user1 user1   0 Feb 16 10:57 test.txt
[user1@ocpservices ~]$ chmod o+x /home/user1
[user1@ocpservices ~]$ ls -la /home/user1
total 16
drwx-----x. 2 user1 user1  99 Feb 16 11:15 .
drwxr-xr-x. 7 root  root   73 Feb 16 10:56 ..
-rw-------. 1 user1 user1  86 Feb 16 11:15 .bash_history
-rw-r--r--. 1 user1 user1  18 Nov 24  2022 .bash_logout
-rw-r--r--. 1 user1 user1 141 Nov 24  2022 .bash_profile
-rw-r--r--. 1 user1 user1 492 Nov 24  2022 .bashrc
-rw-r--r--. 1 user1 user1   0 Feb 16 10:57 test.txt

Now let’s switch over to user2 and see how things are going to work out.

[user2@ocpservices ~]$ cat /home/user1/test.txt
success!
[root@ocpservices ~]# useradd user3
[root@ocpservices ~]# su user3
[user3@ocpservices root]$ cd ~
[user3@ocpservices ~]$ cat /home/user1/test.txt
success!

Success! But now every user can see this file. That’s no good. Let’s get more fine grained.

Break out the ACLs

Let’s reset the stage. After deleting the previous users, we get things setup again.

[root@ocpservices ~]# useradd user1
[root@ocpservices ~]# useradd user2
[root@ocpservices ~]# su user1
[user1@ocpservices root]$ cd ~
[user1@ocpservices ~]$ touch test.txt
[user1@ocpservices ~]$ echo "success!" > test.txt
[user1@ocpservices ~]$ ls -la /home/user1/test.txt
-rw-r--r--. 1 user1 user1 9 Feb 16 12:15 /home/user1/test.txt
[user1@ocpservices ~]$ getfacl /home/user1/test.txt
getfacl: Removing leading '/' from absolute path names
# file: home/user1/test.txt
# owner: user1
# group: user1
user::rw-
group::r--
other::r--

...

[user2@ocpservices ~]$ cat /home/user1/test.txt
cat: /home/user1/test.txt: Permission denied

This time, instead of chmod, we are going to use setfacl to add fine grained, user based permissions. We are going to give user2 read access to the file but we also need to provide execute on the containing folder, just like on the chmod side.

[user1@ocpservices ~]$ setfacl -m u:user2:r test.txt
[user1@ocpservices ~]$ getfacl /home/user1/test.txt
getfacl: Removing leading '/' from absolute path names
# file: home/user1/test.txt
# owner: user1
# group: user1
user::rw-
user:user2:r--
group::r--
mask::r--
other::r--

[user1@ocpservices ~]$ setfacl -m u:user2:x /home/user1
[user1@ocpservices ~]$ getfacl /home/user1
getfacl: Removing leading '/' from absolute path names
# file: home/user1
# owner: user1
# group: user1
user::rwx
user:user2:--x
group::---
mask::--x
other::---

...

[user2@ocpservices ~]$ ls -la /home/user1
ls: cannot open directory '/home/user1': Permission denied
[user2@ocpservices ~]$ cat /home/user1/test.txt
success!

...

[root@ocpservices ~]# useradd user3
[root@ocpservices ~]# su user3
[user3@ocpservices ~]$ cd ~
[user3@ocpservices ~]$ cat /home/user1/test.txt
cat: /home/user1/test.txt: Permission denied

The output is exactly what we want. user2 can read that file and only that file. They can’t see what’s in /home/user1. And user3 doesn’t have any access to that file.

Success!