Quyền file trong Linux: chmod, chown và umask
1. Tổng quan
Lỗi Permission denied là một trong những lỗi gặp nhiều nhất khi deploy ứng dụng lên Linux. Ứng dụng không ghi được log, không đọc được file cấu hình, hoặc file bí mật lại bị mọi user đọc được.
Bài này giải thích mô hình quyền file của Linux và cách dùng ba công cụ chính: chmod đổi quyền, chown đổi chủ sở hữu, umask kiểm soát quyền mặc định của file mới. Cuối bài, bạn sẽ đọc được quyền của bất kỳ file nào, cấp quyền đúng mức cần thiết và thao tác quyền POSIX từ Java.
2. Đọc quyền file với ls -l
Bắt đầu bằng một thư mục ví dụ:
mkdir -p shop && cd shop
echo "id,total" > order.csv
mkdir reports
ls -l
-rw-r--r-- 1 an dev 9 Oct 13 09:00 order.csv
drwxr-xr-x 2 an dev 4096 Oct 13 09:00 reports
Cột đầu tiên có 10 ký tự:
- Ký tự 1 là loại:
-là file thường,dlà thư mục,llà symbolic link. - 9 ký tự còn lại chia thành ba nhóm, mỗi nhóm 3 ký tự: user (chủ sở hữu), group (nhóm), other (mọi người còn lại).
- Mỗi nhóm gồm
r(read),w(write),x(execute). Dấu-nghĩa là không có quyền đó.
Cột thứ ba và thứ tư là owner (an) và group (dev). Như vậy, với order.csv, an được đọc và ghi, thành viên nhóm dev chỉ được đọc, các user khác cũng chỉ được đọc.
Để xem quyền dạng số, bạn dùng stat:
stat -c '%a %A %U:%G %n' order.csv reports
644 -rw-r--r-- an:dev order.csv
755 drwxr-xr-x an:dev reports
Kernel kiểm tra quyền theo thứ tự. Nếu bạn là owner, chỉ nhóm quyền user được áp dụng. Nếu không phải owner nhưng thuộc group, chỉ nhóm quyền group được áp dụng. Còn lại thì dùng other. Các nhóm không cộng dồn. Vì vậy, một file có quyền ---r--r-- sẽ khiến chính owner không đọc được, dù mọi người khác đọc được.
3. Đổi quyền với chmod
3.1. Dạng ký hiệu
Dạng ký hiệu có cú pháp [ugoa][+-=][rwxX]. Trong đó u là user, g là group, o là other, a là tất cả.
chmod g+w order.csv # thêm quyền ghi cho group
chmod o-r order.csv # bỏ quyền đọc của other
chmod u=rw,go= order.csv # user: rw, group và other: không có gì
stat -c '%a %A' order.csv
600 -rw-------
Toán tử + và - chỉ thêm hoặc bớt đúng quyền được nêu, các quyền khác giữ nguyên. Toán tử = gán lại toàn bộ nhóm đó. Dạng ký hiệu an toàn khi bạn chỉ muốn sửa một phần mà không cần biết quyền hiện tại.
3.2. Dạng số bát phân
Mỗi quyền có một giá trị: r = 4, w = 2, x = 1. Cộng các giá trị lại sẽ ra một chữ số cho mỗi nhóm.
| Số | Quyền | Dùng cho |
|---|---|---|
| 7 | rwx |
owner của thư mục hoặc script |
| 6 | rw- |
owner của file dữ liệu |
| 5 | r-x |
đọc và đi vào thư mục, chạy script |
| 4 | r-- |
chỉ đọc |
| 0 | --- |
không có quyền |
Ví dụ, tạo một script và một file chứa mật khẩu:
printf '#!/bin/bash\necho "Backup orders..."\n' > backup.sh
echo "db.password=s3cret" > secret.properties
chmod 755 backup.sh
chmod 600 secret.properties
./backup.sh
stat -c '%a %n' backup.sh secret.properties
Backup orders...
755 backup.sh
600 secret.properties
755 cho phép mọi người chạy script nhưng chỉ owner sửa được nó. 600 khiến chỉ owner đọc và ghi được file bí mật. Dạng số gán toàn bộ quyền một lần, nên nó phù hợp khi bạn biết chính xác trạng thái mong muốn.
3.3. Đổi quyền đệ quy và chữ X hoa
Lỗi kinh điển là chạy lệnh sau:
chmod -R 755 shop/
Lệnh này gán quyền thực thi cho mọi file, kể cả order.csv. Cách đúng là dùng X hoa. Ký hiệu này chỉ thêm x cho thư mục, hoặc cho file vốn đã có x ở một nhóm nào đó:
chmod -R u=rwX,go=rX shop/
Kết quả là thư mục có quyền 755, file thường có quyền 644, còn backup.sh vẫn giữ quyền 755. Nếu cần kiểm soát chặt hơn, bạn có thể tách riêng bằng find:
find shop -type d -exec chmod 750 {} +
find shop -type f -exec chmod 640 {} +
4. Quyền trên thư mục khác quyền trên file
Với thư mục, rwx mang nghĩa khác:
r: liệt kê tên các mục bên trong (ls).w: tạo, xóa, đổi tên các mục bên trong. Quyền này cần đi kèmxmới có tác dụng.x: đi vào thư mục (cd) và truy cập các mục bên trong nếu đã biết tên.
Một hệ quả bất ngờ: việc xóa file phụ thuộc vào quyền của thư mục, không phụ thuộc vào quyền của file.
mkdir inbox && echo "data" > inbox/invoice.txt
chmod 444 inbox/invoice.txt # file chỉ đọc
rm -f inbox/invoice.txt # vẫn xóa được vì inbox có quyền w
ls inbox # trống
Ngược lại, nếu bỏ quyền x trên thư mục, bạn không đọc được file bên trong dù file có quyền 644:
echo "data" > inbox/invoice.txt
chmod 644 inbox/invoice.txt
chmod u-x inbox
cat inbox/invoice.txt
cat: inbox/invoice.txt: Permission denied
Khi ứng dụng báo Permission denied mà quyền của file trông có vẻ đúng, hãy kiểm tra mọi thư mục cha trên đường dẫn. Lệnh namei -l /path/to/file liệt kê quyền của từng cấp. Trước khi đi tiếp, bạn trả lại quyền cho thư mục bằng chmod u+x inbox.
5. Đổi chủ sở hữu với chown và chgrp
chown đổi owner, group hoặc cả hai:
sudo chown appuser order.csv # chỉ đổi owner
sudo chown appuser:dev order.csv # đổi cả owner và group
sudo chown :dev order.csv # chỉ đổi group
sudo chown -R appuser:appuser /opt/shop
Một số điểm cần nhớ:
- Chỉ
rootmới đổi được owner. User thường không thể “tặng” file của mình cho người khác. - User thường có thể đổi group của file mình sở hữu, nhưng chỉ sang một group mà mình là thành viên. Lệnh dùng là
chgrp dev order.csv. chown -Rđi theo cây thư mục. Khi gặp symbolic link, mặc định nó đổi chủ của file mà link trỏ tới. Thêm-hnếu bạn muốn đổi chủ của chính link.
Một mẫu thường dùng khi deploy là cho service chạy bằng user riêng, còn đội vận hành dùng chung một group:
sudo chown -R appuser:ops /var/log/shop
sudo chmod -R u=rwX,g=rX,o= /var/log/shop
Với thiết lập này, service ghi được log, nhóm ops đọc được log, còn các user khác không truy cập được.
6. Quyền mặc định với umask
Khi tạo file mới, chương trình thường yêu cầu quyền 666 cho file và 777 cho thư mục. Sau đó kernel tắt bớt các bit có trong umask. Kết quả là:
quyền thực tế = quyền yêu cầu AND (NOT umask)
umask # xem giá trị hiện tại
umask 027 # đổi cho shell hiện tại
touch customer.csv && mkdir archive
stat -c '%a %n' customer.csv archive
0022
640 customer.csv
750 archive
Với umask 027: file nhận 666 trừ đi các bit 027 nên còn 640, thư mục nhận 777 trừ đi 027 nên còn 750. Other mất hết quyền, group mất quyền ghi.
Lưu ý:
umasklà phép che bit, không phải phép trừ số học. Vớiumask 033, file mới có quyền644, không phải633. Lý do là bitxvốn không có trong666, nên không có gì để tắt.
Một vài giá trị phổ biến:
| umask | File | Thư mục | Khi nào dùng |
|---|---|---|---|
022 |
644 | 755 | mặc định trên đa số distro |
027 |
640 | 750 | server, chỉ chia sẻ trong group |
077 |
600 | 700 | dữ liệu nhạy cảm |
Lệnh umask chỉ có hiệu lực trong process hiện tại và các process con. Để đặt cố định cho một service systemd, bạn khai báo trong unit file:
[Service]
User=appuser
UMask=0027
7. Ba bit đặc biệt: setuid, setgid, sticky
Ngoài 9 bit cơ bản, Linux còn có ba bit đặc biệt. Chúng được viết thành chữ số thứ tư ở đầu dạng bát phân:
- setuid (4): chương trình chạy với quyền của owner. Ví dụ
/usr/bin/passwdhiển thị là-rwsr-xr-x. - setgid (2): khi đặt trên thư mục, file mới tạo bên trong sẽ kế thừa group của thư mục. Bit này rất hữu ích cho thư mục làm việc chung.
- sticky (1): khi đặt trên thư mục, chỉ owner của file mới xóa được file đó.
/tmpdùng bit này và hiển thị làdrwxrwxrwt.
Ví dụ tạo một thư mục chung cho nhóm dev:
sudo mkdir /srv/shared && sudo chown :dev /srv/shared
sudo chmod 2770 /srv/shared
stat -c '%a %A' /srv/shared
2770 drwxrws---
Lưu ý: Đừng gán setuid cho script hay file do bạn tự viết nếu không thật sự hiểu rủi ro. Một chương trình setuid root có lỗi là con đường leo thang đặc quyền kinh điển.
8. Thao tác quyền POSIX từ Java
Java NIO hỗ trợ quyền POSIX qua PosixFilePermissions. Lớp sau ghi file bí mật và đảm bảo chỉ owner đọc được:
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermissions;
public class SecretWriter {
public Path writeSecret(Path dir, String content) throws IOException {
Path file = dir.resolve("secret.properties");
Files.writeString(file, content);
Files.setPosixFilePermissions(file, PosixFilePermissions.fromString("rw-------"));
return file;
}
}
fromString("rw-------") chuyển chuỗi ký hiệu thành Set<PosixFilePermission>. Sau đó setPosixFilePermissions hoạt động như lệnh chmod 600.
Có thể bạn sẽ thử truyền PosixFilePermissions.asFileAttribute(...) vào Files.createFile. Cách này vẫn bị umask áp dụng, giống hệt lời gọi hệ thống open(). Với file nhạy cảm, hãy gọi setPosixFilePermissions sau khi tạo để chắc chắn kết quả. Một cách còn an toàn hơn là tạo file với quyền chặt ngay từ đầu, để không có khoảng thời gian nào file bị người khác đọc được.
Test với JUnit 5 và AssertJ:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermissions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
class SecretWriterTest {
@TempDir
Path tempDir;
@Test
void givenSecret_whenWritten_thenOnlyOwnerCanReadAndWrite() throws Exception {
assumeTrue(FileSystems.getDefault().supportedFileAttributeViews().contains("posix"));
Path file = new SecretWriter().writeSecret(tempDir, "db.password=s3cret");
String perms = PosixFilePermissions.toString(Files.getPosixFilePermissions(file));
assertThat(perms).isEqualTo("rw-------");
assertThat(Files.readString(file)).contains("db.password");
}
}
assumeTrue sẽ bỏ qua test trên Windows, nơi không có attribute view posix. Nếu thiếu dòng này, test sẽ ném UnsupportedOperationException.
9. Lỗi hay gặp
- Dùng
chmod 777để “sửa nhanh”. Cách này mở quyền ghi cho mọi user trên máy. Hãy xác định process chạy bằng user nào (ps -o user= -p <pid>), rồi chỉ cấp quyền cho user hoặc group đó. - Chạy
chmod -R 755trên cây thư mục. Lệnh này biến mọi file thành file thực thi. Hãy dùngXhoa hoặcfindnhư ở mục 3.3. - Chỉ kiểm tra quyền của file. Bạn cần kiểm tra cả các thư mục cha bằng
namei -l. - Chạy
sudorồi quên đổi chủ. File tạo bởisudothuộc vềroot, nên ứng dụng chạy bằng user thường sẽ không ghi được. Hãy chạychownngay sau đó. - Mount volume trong Docker. UID bên trong container có thể khác UID trên host. Quyền được kernel kiểm tra theo số UID, không theo tên user.
- SSH từ chối key. SSH bỏ qua
~/.ssh/authorized_keyshoặc private key nếu quyền quá rộng. Thư mục~/.sshcần quyền700, private key cần quyền600.
10. Kết luận
Mô hình quyền của Linux gồm ba nhóm (user, group, other) và ba quyền (r, w, x). Các quyền này mang nghĩa khác nhau giữa file và thư mục.
Bạn dùng chmod để đổi quyền: dạng ký hiệu khi chỉ cần sửa một phần, dạng số khi muốn gán toàn bộ. Bạn dùng chown và chgrp để giao file cho đúng user và group. Bạn dùng umask để file mới được tạo với quyền an toàn ngay từ đầu.
Nguyên tắc xuyên suốt là cấp ít quyền nhất có thể. Hãy chạy service bằng user riêng, chia sẻ qua group, và để other ở mức 0 với mọi thứ nhạy cảm.