umask Linux: quyền mặc định của file bắt đầu từ đâu?
Hiểu umask trên Linux: chạy lệnh in mask, tạo file và directory, đọc mode thực tế để thấy vì sao root và user của bạn có quyền mặc định khác nhau.
Umask làm gì trên Linux
Umask là một giá trị mà mọi process trên Linux đều mang theo. Nó quyết định mode của từng file và directory mà process đó tạo ra. Khi tạo đối tượng, chương trình yêu cầu kernel cấp một tập quyền. Kernel sẽ xóa mọi bit được chỉ định trong mask, rồi áp dụng phần còn lại. Umask không bao giờ cấp thêm quyền truy cập. Nó chỉ loại bỏ các bit khỏi tập quyền mà chương trình tạo đối tượng yêu cầu.
Giá trị này không phải là thuộc tính của distribution. Nó phụ thuộc vào account bạn đang dùng và cách shell được khởi chạy. Hai yếu tố này có thể cho kết quả khác nhau trên cùng một máy, tại cùng một thời điểm, ngay cả với một stock image. Vì vậy, bước đầu tiên không phải là tra trong manual. Hãy đo trực tiếp trên máy bạn đang kiểm tra.
In umask trong shell hiện tại
umask
umask -SDạng đầu tiên in mask ở dạng bát phân. Dạng thứ hai in cùng mask dưới dạng các quyền mà mask cho phép, theo cú pháp symbolic mà chmod chấp nhận. Giữ cả hai dòng trên màn hình. Mọi nội dung bên dưới đều so sánh với kết quả shell vừa in.
umask là shell builtin, không phải một chương trình trên đĩa. Xác nhận điều đó bằng type umask. Điều này quan trọng vì builtin thay đổi trực tiếp tiến trình shell. Một chương trình riêng chỉ có thể thay đổi tiến trình của chính nó, rồi thoát và mang theo thay đổi đó.
Tạo một file và một thư mục, sau đó đọc lại mode
cd "$(mktemp -d)"
touch probe.file
mkdir probe.dir
stat -c '%a %A %n' probe.file probe.dir%a in mode theo dạng bát phân và %A in cùng mode theo dạng drwxr-xr-x mà ls -l sử dụng. So sánh cả hai dòng với mask bạn vừa in trước đó. Mỗi bit được set trong mask đều bị thiếu trong các mode, vì xóa những bit đó là chức năng duy nhất của mask. Nếu cột %A vẫn chưa dễ đọc, chuỗi quyền drwxr-xr-x là phần bạn cần nắm rõ trước.
File và thư mục khác nhau, và mask không phải nguyên nhân của khác biệt đó. touch yêu cầu kernel cấp quyền đọc và ghi cho owner, group và other. mkdir yêu cầu quyền đọc, ghi và thực thi cho cả ba. Cùng một mask được trừ khỏi hai yêu cầu khác nhau. Vì vậy, file được tạo bởi touch không bao giờ có quyền thực thi, bất kể mask chứa gì: bit thực thi chưa từng được yêu cầu, và mask không thể thêm lại một bit.
( umask a=rwx; touch open.file; stat -c '%a %A %n' open.file )Các dấu ngoặc đó chạy những command trong một subshell, nên thay đổi sẽ mất khi subshell kết thúc. Mask hiện không yêu cầu xóa bit nào, nhưng stat vẫn không báo bit thực thi trên file. Sau đó chạy lại umask thì giá trị ban đầu được khôi phục. Điều này cho thấy setting nằm trong process và được các process con kế thừa, thay vì được lưu ở đâu đó trên disk.
Thư mục là nơi việc xóa bit thực thi thể hiện rõ.
( umask a=rw; mkdir noexec.dir; cd noexec.dir )Với một user thông thường, cd sẽ fail với bash: cd: noexec.dir: Permission denied, vì mask đã xóa bit thực thi mà mkdir yêu cầu, và một thư mục không có bit thực thi thì không thể truy cập vào. root bỏ qua kiểm tra đó, nên vấn đề này chỉ xuất hiện trên account thông thường.
Vì sao root và user của bạn có umask khác nhau
Chạy cùng phép đo bằng một account khác, khởi động theo cách khác, rồi đọc hai kết quả cạnh nhau.
umask
sudo -i umasksudo -i khởi động login shell của root và chạy builtin bên trong shell đó. Vì vậy, đây là một account khác đi qua một startup path khác. Trên các image server Ubuntu và Debian mặc định, hai dòng này có thể in ra các giá trị khác nhau. Cả hai dòng đều đúng. Mỗi dòng cho biết startup path tương ứng đã tạo ra giá trị nào. Phần còn lại của bài viết này sẽ giải thích thành phần nào trong system đã tạo ra giá trị đó.
File nào trên image của bạn quyết định giá trị này
grep -nE '^(UMASK|USERGROUPS_ENAB)' /etc/login.defs
grep -rn pam_umask /etc/pam.d/
grep -rn umask /etc/profile /etc/profile.d/ /etc/bash.bashrc ~/.profile ~/.bashrc 2>/dev/null || echo 'no umask line in the shell startup files'Nếu lệnh grep đầu tiên không in gì, hãy chạy lại nhưng bỏ anchor ^: dòng đó có thể đã bị comment, và một dòng bị comment là tài liệu chứ không phải cấu hình. Lệnh grep thứ ba mới là phần khiến nhiều người bất ngờ. Trên Debian và Ubuntu, /etc/profile được cung cấp chủ yếu trỏ đến PAM thay vì tự đặt mask, nên file mà bạn cho là đang kiểm soát giá trị này thường không phải file thực sự có hiệu lực. grep trả về mã khác 0 khi không khớp dòng nào. Vì vậy dòng đó kết thúc bằng || echo: trên image không có file khởi động nào nhắc đến mask, bạn sẽ nhận được thông báo thay vì không có output, và chính thông báo đó là kết quả cần tìm.
/etc/login.defs chỉ công bố một giá trị, còn PAM mới áp dụng giá trị đó
Dòng UMASK trong /etc/login.defs là giá trị mà phần lớn hướng dẫn thường trích dẫn. Kernel và shell đều không đọc file đó. File này được pam_umask đọc. Đây là một module PAM (pluggable authentication modules), chạy khi một session được tạo. pam_umask lấy giá trị đầu tiên mà nó tìm thấy: mục umask= trong trường GECOS của người dùng, sau đó là tham số umask= được ghi ngay trên dòng pam_umask.so, rồi đến UMASK từ /etc/login.defs. Các bản phân phối có thể patch module này, vì vậy hãy chạy man pam_umask trên image của chính bạn và đọc thứ tự được in ra.
Vì vậy, /etc/login.defs có thể công bố một giá trị nhưng session của bạn lại kết thúc với giá trị khác, mà không có cảnh báo nào được in ra ở hai phía. Các lệnh grep cho biết bạn đang thuộc trường hợp nào. Nếu dòng pam_umask.so có tham số umask= riêng, thì giá trị trong dòng login.defs không còn được áp dụng.
USERGROUPS_ENAB và ngoại lệ của root
id -un
id -gnNếu hai lệnh này in ra cùng một tên, bạn đang dùng user private group: account được tạo cùng một group riêng có tên trùng với account. pam_umask có hành vi usergroups, được điều khiển bởi USERGROUPS_ENAB trong /etc/login.defs. Khi tùy chọn này bật, account không phải root và tên primary group trùng với tên user, module sẽ chép chữ số owner của mask vào chữ số group. Khi đó, session kết thúc với một mask giữ các bit group ở trạng thái mở cho mọi thứ account đó tạo ra. Module tự loại trừ root. Đây là lý do phổ biến nhất khiến hai shell trên cùng một server in ra các mask khác nhau.
Quy tắc này dựa trên việc private group chỉ có đúng một thành viên. Vì vậy, quyền ghi của group khi đó chỉ có nghĩa là owner có quyền ghi, không hơn. Điều này không còn đúng khi có người thêm thành viên thứ hai vào group. Từ thời điểm đó, thành viên mới có thể ghi vào mọi file mà account từng tạo, dù không có lệnh nào được chạy trên các file đó để cấp quyền này. Hãy cấp cho mỗi service một account user có đặc quyền tối thiểu riêng để group đó cố ý duy trì đúng một thành viên.
Shell đăng nhập, shell không đăng nhập và shell không tương tác
umask
bash -lc 'umask'
bash -c 'umask'PAM chạy khi một session được tạo: login trên console, sshd, su, sudo -i. PAM không chạy khi một shell khởi động shell khác. bash -l là login shell nên đọc /etc/profile và ~/.profile, nhưng không bao giờ gọi pam_umask vì không có session nào được tạo. bash -c không đọc file nào trong hai file đó và kế thừa umask của process đã khởi động nó. Cron job, git hook và chương trình do service manager khởi động đều thuộc trường hợp cuối, vì vậy umask của chúng là umask của process cha.
Đây là lý do câu “Tôi đã đặt trong /etc/profile nhưng service vẫn ghi file với mode sai” xuất hiện rất thường xuyên. Service không bao giờ đọc file đó.
Nên đặt ở đâu để giá trị vẫn được giữ nguyên
Đặt umask tại nơi workload thực sự khởi chạy, vì mỗi đường khởi chạy đọc một file khác nhau.
- Với các account đăng nhập:
UMASKtrong/etc/login.defs, được pam_umask áp dụng cho mọi session trên máy. Thiết lập này có phạm vi toàn máy, nên thay đổi đồng thời cho mọi account. - Với một account: tham số
umask=trên dòngpam_umask.socũng có phạm vi toàn máy, nên giá trị riêng cho từng user phải nằm trong trường GECOS của user đó, hoặc trong~/.profileđối với login shell và~/.bashrcđối với interactive shell. - Với daemon chạy dưới systemd:
UMask=trong phần[Service]của unit. Unit được service manager khởi chạy, nên/etc/profilekhông bao giờ được đọc và pam_umask không chạy. Trong các vị trí này, chỉ file unit được áp dụng cho daemon. - Với script do cron hoặc hook khởi chạy: đặt
umasktường minh ở dòng đầu tiên, trước khi script tạo bất kỳ thứ gì.
[Service]
UMask=<the octal mask you chose>Sau đó, hãy kiểm tra bằng cách khởi chạy mới từ chính đường khởi chạy đó, không kiểm tra trong shell nơi bạn đã sửa file. Shell hiện tại đã giữ umask riêng, và việc sửa file cấu hình không cập nhật ngược vào process đang chạy.
bash -lc 'umask'
sudo -i umaskVì sao chạy chmod sau đó không phải là cách sửa tương đương
chmod sửa các file đã tồn tại. umask quyết định mode của những file chưa tồn tại. Chạy chmod -R trên một thư mục không thay đổi gì ở thư mục đó; file tiếp theo do service ghi ra vẫn có mode cũ, vì mode này đến từ process tạo file.
Ngoài ra còn có một khoảng thời gian rủi ro. Từ lúc file được tạo đến lúc chmod chạy, file nằm trên disk với mode rộng hơn. Bất kỳ process nào có quyền đọc thư mục đều có thể mở file trong khoảng thời gian này. Với private key hoặc backup archive, đây chính là rủi ro cần loại bỏ.
Hãy đặt mode ngay khi tạo file. install -m u=rw,go= newfile /etc/app/newfile ghi destination với mode được chỉ định, còn mkdir -m làm điều tương tự với thư mục. Cả hai đều dùng mode bạn chỉ định và bỏ qua umask. ssh-keygen đặt mode cho private key mà nó ghi ra. Vì vậy, riêng file đó thường có mode đúng trên những máy mà các file khác thì không.
SSH thường là nơi phát sinh lỗi. Một ~/.ssh được tạo bằng mkdir thông thường, hoặc một authorized_keys được append bằng cat >>, sẽ nhận umask của shell. Khi StrictModes được bật, sshd từ chối đọc key file trong một thư mục cho phép group ghi. Client nhận được Permission denied (publickey), còn /var/log/auth.log trên server ghi lại nguyên nhân thực sự:
Authentication refused: bad ownership or modes for directory /home/deploy/.sshKiểm tra này là có chủ đích. Việc hardening SSH trên một VPS phụ thuộc vào việc kiểm tra này được giữ nguyên. Hãy đo umask trên server mới trước khi tạo các account sẽ sử dụng server đó, đồng thời kiểm tra 10 phút đầu tiên trên một VPS mới. Như vậy mode của mọi file do các account đó ghi ra sẽ được xác định ngay từ đầu.
Bản sao và bản lưu trữ bỏ qua mask
cp -p và rsync -a khôi phục mode được ghi trên file nguồn, nên mask không ảnh hưởng đến kết quả. tar cũng làm như vậy khi giải nén với root hoặc với user thường dùng -p. File được khôi phục từ bản backup giữ nguyên mode tại thời điểm tạo bản backup. Hãy kiểm tra điều này trước khi kết luận rằng mask đúng đang bị bỏ qua: với dữ liệu được khôi phục, mask chưa bao giờ được áp dụng.
FAQ
Vì sao cron job của tôi tạo file với mode khác với phiên SSH?
Cron job không phải là login session, nên pam_umask không chạy cho nó và nó không đọc /etc/profile hoặc ~/.profile. Nó kế thừa mask của process đã khởi chạy nó. Đặt umask rõ ràng ở dòng đầu tiên của script, trước khi script tạo bất kỳ thứ gì, rồi in mask một lần từ bên trong job để biết job đó thực sự có giá trị nào, thay vì chỉ biết shell của bạn có giá trị nào.
Vì sao /etc/login.defs quy định một giá trị nhưng shell của tôi lại in giá trị khác?
UMASK trong /etc/login.defs chỉ là giá trị fallback cuối cùng cho pam_umask. Module ưu tiên entry umask= trong trường GECOS của user, sau đó đến argument umask= trên dòng pam_umask.so trong /etc/pam.d/. Behaviour usergroups, được bật bằng USERGROUPS_ENAB, sau đó thay đổi digit group cho mọi account không phải root có primary group được đặt theo tên account đó. Chạy grep -rn pam_umask /etc/pam.d/ và id -un; id -gn để xem trường hợp nào đang áp dụng cho account của bạn.
Umask có thể làm cho file có quyền execute không?
Không. Mask chỉ có thể xóa các bit khỏi những quyền mà chương trình tạo file yêu cầu. touch không bao giờ yêu cầu bit execute, nên không có mask nào tạo ra file có quyền execute. Xác nhận điều này trong một thư mục tạm bằng ( umask a=rwx; touch f; stat -c '%a %A' f ). Muốn có bit execute, bạn cần chmod hoặc một chương trình như install -m yêu cầu bit đó khi tạo file.
Đặt umask cho systemd service ở đâu?
Đặt trong unit, bằng UMask= trong section [Service]. Service được service manager khởi chạy thay vì login session, nên các shell startup file không bao giờ được đọc và pam_umask không chạy cho service đó. Sau systemctl daemon-reload và khi restart unit, hãy xác nhận từ bên ngoài: để service tạo một file, rồi đọc kết quả bằng stat -c '%a %n'.
Mask mặc định cho phép group write có an toàn không?
Cấu hình này an toàn khi group chỉ có đúng một member. Đây là giả định của mô hình user private group. Nếu thêm account thứ hai vào group đó, mọi file mà account thứ nhất đã tạo sẽ lập tức cho phép member mới ghi, mà không cần chạy command nào trên các file đó. Chạy id -un và id -gn: nếu cùng in ra một tên, bạn đang dùng private group. Nếu dùng chung một group giữa các account, hãy đặt mask xóa bit group write, sau đó tạo một file và đọc stat -c '%a %n' để xác nhận thay đổi đã có hiệu lực.