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, d là thư mục, l là 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èm x mớ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ỉ root mớ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 -h nế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 ý: umask là phép che bit, không phải phép trừ số học. Với umask 033, file mới có quyền 644, không phải 633. Lý do là bit x vốn không có trong 666, 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/passwd hiể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 đó. /tmp dù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 755 trên cây thư mục. Lệnh này biến mọi file thành file thực thi. Hãy dùng X hoa hoặc find như ở 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 sudo rồi quên đổi chủ. File tạo bởi sudo thuộc về root, nên ứng dụng chạy bằng user thường sẽ không ghi được. Hãy chạy chown ngay 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_keys hoặc private key nếu quyền quá rộng. Thư mục ~/.ssh cần quyền 700, private key cần quyền 600.

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.

linuxchmodchownumaskbảo mật