sudo-rs trên Ubuntu 26.04: thay đổi quy tắc sudoers
Ubuntu 26.04 chuyển sang sudo-rs mặc định. Các quy tắc sudoers chứa wildcard sẽ không còn hoạt động như cũ. Bài viết hướng dẫn cách cập nhật cấu hình để tránh lỗi phân quyền.
Những thay đổi của sudo-rs trên Ubuntu
Ubuntu 26.04 LTS sử dụng sudo-rs làm sudo mặc định, vì vậy lệnh sudo trên một server mới chạy phiên bản viết lại bằng Rust thay vì chương trình C gốc. Hầu hết các file sudoers vẫn hoạt động bình thường. Quy tắc bị lỗi là quy tắc có chứa wildcard trong đối số của lệnh, vì sudo-rs không khớp các mẫu glob với văn bản đối số.
Ubuntu 25.10 là bản phát hành đầu tiên thực hiện thay đổi này và 26.04 LTS tiếp tục duy trì. Ubuntu 24.04 LTS không bị ảnh hưởng vì nó vẫn sử dụng sudo gốc trừ khi bạn tự cài đặt sudo-rs. Vấn đề này trở nên quan trọng khi bạn nâng cấp từ Ubuntu 24.04 lên 26.04, hoặc khi bạn khởi tạo một máy chủ mới trên bản phát hành mới hơn. Nếu bạn cũng chạy các bản phát hành trung gian, sự khác biệt giữa các bản phát hành LTS và trung gian của Ubuntu trên server sẽ giải thích máy chủ nào sẽ gặp phải thay đổi này trước.
Kiểm tra phiên bản sudo thực tế đang chạy trên máy chủ
Đừng dựa vào số hiệu bản phát hành để xác định. Hãy hỏi trực tiếp hệ thống.
sudo --version
update-alternatives --config sudo
dpkg -l 'sudo*'Hãy tin tưởng sudo --version trên máy của bạn hơn bất kỳ bảng phiên bản nào trên internet, bao gồm cả trang này. update-alternatives --config sudo là phần còn lại của câu trả lời: nó liệt kê mọi nhà cung cấp /usr/bin/sudo đã cài đặt và đánh dấu phiên bản đang được chọn. Một gói phần mềm được cài đặt không có nghĩa là nó đang được chọn, vì vậy hãy đọc mục được chọn, đừng đọc danh sách gói.
Cả hai bản cài đặt đều được đóng gói trong giai đoạn chuyển đổi. Bản viết bằng Rust là sudo-rs, ở phiên bản 0.2.13 trong 26.04 tính đến tháng 8 năm 2026. Bản gốc, do Todd C. Miller duy trì, được đóng gói dưới tên sudo.ws, và các chương trình của nó mang hậu tố .ws: sudo.ws và visudo.ws.
Tại sao Ubuntu chuyển sang sudo-rs
sudo là một binary setuid root. Bất kỳ người dùng nào trên máy cũng có thể chạy nó, và nó khởi chạy với toàn quyền, vì vậy một lỗi bộ nhớ bên trong nó chính là một lỗ hổng local root exploit. CVE-2021-3156 chính xác là trường hợp đó: một lỗi heap buffer overflow mà bất kỳ người dùng cục bộ nào cũng có thể khai thác, và nó đã tồn tại trong code được phát hành suốt mười năm. Rust ngăn chặn loại lỗi này ngay tại thời điểm biên dịch, đó là lý do chính cho việc viết lại này.
Lý do thứ hai là phạm vi tính năng, và đây là điều ảnh hưởng trực tiếp đến cấu hình của bạn. sudo bản gốc đã tích hợp một tập hợp tính năng khổng lồ qua ba thập kỷ, và mỗi tính năng lại là thêm code chạy dưới quyền root. sudo-rs cố tình chỉ triển khai một tập con. Bất cứ thứ gì tác giả đánh giá là quá chuyên biệt hoặc gây hại đều bị loại bỏ, vì vậy một cấu trúc sudoers đã hoạt động nhiều năm có thể đơn giản là không còn tồn tại. Quy tắc wildcard của bạn là một trong số đó.
Tính an toàn bộ nhớ loại bỏ một lớp lỗi. Nó không làm cho chương trình hoàn toàn không có lỗi, và sudo-rs cũng đã có những bản vá bảo mật kể từ khi trở thành mặc định. Hãy patch nó như bất kỳ phần mềm nào khác.
Những quy tắc sudoers nào vẫn hoạt động
Tệp tin này vẫn là tệp tin cũ. sudo-rs đọc /etc/sudoers và các tệp tin drop-in trong /etc/sudoers.d/, đồng thời hỗ trợ các cấu hình thông thường mà người vận hành máy chủ hay sử dụng:
deploy ALL=(ALL:ALL) ALL, và các dạng nhóm như%sudo ALL=(ALL:ALL) ALL- các thẻ
NOPASSWD:vàPASSWD: User_Alias,Runas_Alias,Host_AliasvàCmnd_Alias- một lệnh với danh sách đối số chính xác, ví dụ
/usr/bin/systemctl restart app-api - một lệnh theo sau bởi
"", cho phép chạy lệnh đó chỉ khi không có bất kỳ đối số nào - một lệnh theo sau bởi
*là đối số cuối cùng, cho phép bất kỳ đối số nào đi kèm phía sau - một đường dẫn thư mục kết thúc bằng
/, cho phép chạy bất kỳ lệnh nào trong thư mục đó !để loại trừ một lệnh khỏi danh sách- một tập hợp con hữu ích của
Defaults, bao gồmsecure_path,env_keep,env_check,timestamp_timeout,passwd_tries,editor,umask,targetpw,rootpwvàuse_pty
Có hai thiết lập mặc định hoạt động khác biệt và thường gây nhầm lẫn. env_reset không thể tắt trong sudo-rs: nó luôn luôn bật. use_pty được bật theo mặc định, vì vậy lệnh sẽ chạy trong một pseudo-terminal riêng của nó.
Tại sao quy tắc sudoers sử dụng wildcard của bạn không còn khớp
Wildcard vẫn được cho phép ở một vị trí: tên tệp của lệnh. Một quy tắc như %ops ALL = /sbin/fsck* vẫn cho phép sudo fsck và sudo fsck_exfat, vì * là một phần của đường dẫn đang được so khớp với hệ thống tệp.
Bên trong danh sách đối số, sudo-rs chỉ chấp nhận hai dạng đặc biệt và không dạng nào là pattern. "" nghĩa là không có đối số. * ở cuối nghĩa là bất kỳ đối số nào theo sau. Mọi đối số khác đều được so sánh như văn bản thuần túy. Vì vậy, %ops ALL = /sbin/service ntp * là hợp lệ, vì ntp là văn bản thuần túy và * nằm ở cuối. Tuy nhiên, một quy tắc như thế này sẽ không cấp quyền như bạn dự định:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app-*app-* là một pattern nằm ở giữa đối số. sudo-rs không mở rộng nó, vì vậy quy tắc này không bao gồm systemctl restart app-api và sudo sẽ từ chối lệnh đó. Hai lệnh sau đây sẽ cho bạn biết sự thật về bất kỳ quy tắc nào trên máy chủ của bạn: sudo -l -U deploy, chạy với quyền root, sẽ in ra những gì tài khoản đó thực sự có thể chạy, và sudo visudo -c cho bạn biết liệu tệp có được phân tích cú pháp thành công hay không. Hãy chạy chúng trước khi bạn bắt đầu chỉnh sửa một cách tùy tiện.
Quy tắc wildcard luôn là một lỗ hổng
Trong sudo nguyên bản, các đối số bạn nhập được nối thành một chuỗi duy nhất và so khớp với chuỗi đối số của quy tắc bằng glob. Một glob khớp với cả khoảng trắng. Đây là phần mà hầu như ai cũng bỏ sót.
Tài liệu của sudo-rs đưa ra minh họa rõ ràng nhất. Một quy tắc /bin/rm *.txt cũng cho phép sudo rm -rf /home .txt, vì một * sẽ nuốt trọn -rf /home và chuỗi đã nối vẫn kết thúc bằng .txt. Quy tắc này được hiểu là "chỉ các file văn bản". Thực tế nó có nghĩa là "bất kỳ đối số nào, miễn là dòng kết thúc bằng .txt".
Điều tương tự áp dụng cho ví dụ systemctl. Vì các đối số được so sánh như một chuỗi nối liền, một pattern ở cuối cũng khớp với bất cứ thứ gì bạn thêm vào sau nó, vì vậy restart app-* bao hàm cả restart app-api cộng với bất kỳ đối số nào khác mà người gọi thêm vào. Một pattern bên trong một đối số sẽ làm lộ các đối số xung quanh nó, và đối số chính là nơi chứa sức mạnh của một lệnh. sudo-rs từ chối cấu trúc này thay vì cố gắng làm cho nó an toàn, bởi vì không có dạng tổng quát nào an toàn cho nó cả.
Thay thế wildcard bằng danh sách lệnh cụ thể
Hầu hết các quy tắc wildcard tồn tại vì người dùng không muốn gõ bốn dòng lệnh. Hãy gõ đủ bốn dòng đó.
Cmnd_Alias APP_RESTART = /usr/bin/systemctl restart app-api, /usr/bin/systemctl restart app-worker
Cmnd_Alias APP_STATUS = /usr/bin/systemctl status app-api, /usr/bin/systemctl status app-worker
deploy ALL=(root) NOPASSWD: APP_RESTART, APP_STATUSXác định đúng đường dẫn. Một quy tắc chỉ định /bin/systemctl trên hệ thống mà file binary thực tế nằm tại /usr/bin/systemctl sẽ không bao giờ khớp, và lỗi này trông giống hệt lỗi phân quyền. Hãy xác nhận bằng command -v systemctl và dán kết quả đầu ra.
Đặt quy tắc vào file drop-in riêng thay vì sửa trực tiếp /etc/sudoers, để việc nâng cấp gói không ghi đè lên cấu hình của bạn:
sudo visudo -f /etc/sudoers.d/90-deploy
sudo visudo -c
sudo -l -U deployĐặt tên file không chứa dấu chấm và không có dấu ngã ở cuối. Sudo mặc định bỏ qua các file trong sudoers.d có tên chứa dấu chấm, vì vậy 90-deploy.conf là một lỗi phổ biến khiến quy tắc không hoạt động mà không báo lỗi, và việc tuân thủ quy ước đặt tên không gây tốn kém gì.
Sử dụng một wrapper do root sở hữu khi danh sách trở nên quá dài
Khi tập hợp các lệnh được phép quá lớn để liệt kê, hãy chuyển quyết định ra khỏi sudoers và đưa vào một chương trình nhỏ do root sở hữu.
sudo tee /usr/local/sbin/app-restart >/dev/null <<'EOF'
#!/bin/sh
set -eu
case "${1:-}" in
app-api|app-worker) ;;
*) echo "app-restart: not allowed: ${1:-}" >&2; exit 1 ;;
esac
exec /usr/bin/systemctl restart "$1"
EOF
sudo chown root:root /usr/local/sbin/app-restart
sudo chmod 0755 /usr/local/sbin/app-restart
ls -l /usr/local/sbin/app-restartPhía sudoers sau đó chỉ định một lệnh duy nhất:
deploy ALL=(root) NOPASSWD: /usr/local/sbin/app-restart *Ký tự * ở cuối là chấp nhận được trong trường hợp này vì script, chứ không phải sudo, mới là bên quyết định những gì được phép. Điều này chỉ đúng khi script do root sở hữu và không ai khác có quyền ghi. Nếu deploy có thể ghi vào file, deploy có thể thay thế nội dung của nó và chạy bất kỳ thứ gì dưới quyền root, điều này còn tệ hơn cả quy tắc wildcard mà bạn đã loại bỏ. Kiểm tra mode bằng ls -l, và nếu kết quả không rõ ràng với bạn, việc đọc chuỗi quyền drwxr-xr-x chỉ mất năm phút để học. Quy tắc tương tự áp dụng cho thư mục: /usr/local/sbin cũng không được phép để tài khoản đó có quyền ghi, vì một thư mục có quyền ghi đồng nghĩa với việc file có thể bị thay thế hoàn toàn.
Cấp tài khoản riêng cho tác vụ thay vì dùng quy tắc sudo
Câu hỏi hợp lý hơn thường là tại sao lệnh đó lại cần quyền root. Một service chạy dưới tài khoản người dùng riêng có thể được quản lý bởi chính người dùng đó mà không cần cấu hình trong sudoers. Đối với các unit của systemd, systemd đã ủy quyền quyết định này cho polkit, vì vậy một quy tắc có thể chỉ định một unit và một người vận hành cụ thể:
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "app-api.service" &&
subject.user == "deploy") {
return polkit.Result.YES;
}
});Lưu file đó dưới dạng /etc/polkit-1/rules.d/50-app-api.rules và deploy có thể chạy systemctl restart app-api mà không cần sudo. Hãy kiểm tra từ chính ngữ cảnh sẽ thực thi lệnh, vì một quy tắc hoạt động trong phiên SSH của bạn cần được xác nhận từ cron trước khi bạn tin tưởng sử dụng nó. Dù bằng cách nào, tài khoản thực hiện công việc chỉ nên tồn tại cho công việc đó, đây cũng là lập luận tương tự đằng sau các tài khoản người dùng có đặc quyền tối thiểu trên VPS.
Những tính năng khác mà sudo-rs không hỗ trợ
sudo -E chưa được triển khai. Hãy đặt tên các biến bạn cần bằng Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY" thay thế, và nhớ rằng env_reset luôn ở trạng thái bật, vì vậy bất kỳ thứ gì không được giữ lại đều sẽ bị xóa.
Tính năng lưu trữ sudoers tập trung trong LDAP đã bị loại bỏ. sudoers.ldap và cvtsudoers không được triển khai, và gói sudo-ldap đã bị xóa trong phiên bản 26.04. Xác thực LDAP thông qua PAM hoặc SSSD vẫn hoạt động bình thường. Phần chính sách lưu trữ trong thư mục (policy-in-a-directory) là nằm ngoài phạm vi hỗ trợ.
INTERCEPT, tính năng từng cố gắng ngăn chặn việc thoát shell từ một lệnh được cho phép, không được triển khai. Dù sao thì nó cũng chưa bao giờ ngăn cản được một người dùng quyết tâm. Nếu một quy tắc cho phép ai đó chạy trình soạn thảo hoặc trình thông dịch với quyền root, họ đã có quyền root, và không tùy chọn sudo nào thay đổi được điều đó.
Tính năng ghi lại phiên làm việc (session recording) không được triển khai, vì vậy không có log I/O và không có sudoreplay. Việc ghi log chỉ được thực hiện tới syslog, và không có tùy chọn logfile để chuyển hướng nó đến nơi khác, do đó các thông báo của sudo sẽ được ghi vào nơi mà hệ thống của bạn đã cấu hình để gửi syslog.
Bạn có nên chuyển ngược lại sudo.ws không?
Bạn có thể làm vậy, và trong chu kỳ 26.04, phiên bản gốc vẫn được đóng gói chính xác vì lý do này.
sudo apt install sudo.ws
update-alternatives --config sudo
sudo update-alternatives --set sudo /usr/bin/sudo.wsHãy sao chép chính xác các đường dẫn từ đầu ra của --config thay vì sao chép từ trang này, vì đó là danh sách mà hệ thống của bạn sẽ chấp nhận. Việc quay lại sudo-rs sau này đồng nghĩa với việc thiết lập alternative trỏ về đường dẫn binary sudo-rs từ cùng danh sách đó.
Hãy giữ một phiên SSH thứ hai luôn mở, đã đăng nhập và ở trạng thái chờ, trước khi bạn thực hiện bất kỳ thay đổi nào ảnh hưởng đến sudo. Một file sudoers không thể phân tích cú pháp, hoặc một alternative trỏ đến một binary không tồn tại, có thể khiến bạn mất khả năng trở thành root trên một máy chủ từ xa. Thói quen đó nên được áp dụng cùng với mọi việc khác bạn làm trong mười phút đầu tiên trên một VPS mới.
Hãy coi việc chuyển đổi ngược lại là một thời hạn chót thay vì một giải pháp sửa lỗi. Nó giúp bạn có thêm một tuần để viết lại các quy tắc một cách đúng đắn, và việc viết lại này rất đáng giá, vì mỗi quy tắc wildcard mà bạn xóa bỏ trước đây đều đang cấp quyền nhiều hơn những gì người tạo ra nó từng nghĩ.
FAQ
Tại sao quy tắc wildcard trong sudoers của tôi không còn hoạt động trên Ubuntu 26.04?
Vì Ubuntu 26.04 LTS chọn sudo-rs làm sudo mặc định, và sudo-rs không khớp các mẫu wildcard bên trong đối số của lệnh. Nó cho phép wildcard trong tên file của lệnh, "" để chỉ không có đối số, và một * duy nhất làm đối số cuối cùng. Một quy tắc như /usr/bin/systemctl restart app-* đặt một mẫu vào giữa đối số, nên nó không cấp quyền gì cả và lệnh bị từ chối. Chạy sudo -l -U deploy với quyền root để xem tài khoản thực sự có quyền gì, sau đó thay thế quy tắc đó bằng các lệnh chính xác hoặc bằng một wrapper script thuộc sở hữu của root.
Làm thế nào để chuyển về sudo gốc trên Ubuntu 26.04?
Bản gốc được đóng gói dưới tên sudo.ws. Cài đặt nó bằng sudo apt install sudo.ws, sau đó trỏ alternative vào nó bằng sudo update-alternatives --set sudo /usr/bin/sudo.ws. Chạy update-alternatives --config sudo trước để đọc các đường dẫn chính xác mà hệ thống của bạn cung cấp, và hãy giữ một phiên SSH thứ hai mở trong khi bạn thay đổi. Việc này không mang lại sudo-ldap, vốn đã bị loại bỏ khỏi 26.04 bất kể bạn chọn cài đặt nào.
sudo-rs có đọc cùng file /etc/sudoers không?
Có. sudo-rs đọc /etc/sudoers và các file drop-in trong /etc/sudoers.d/, với cùng cú pháp cho người dùng, nhóm, alias, đặc tả run-as và thẻ NOPASSWD. Nó triển khai một tập con của ngôn ngữ sudoers, vì vậy các khác biệt xuất hiện dưới dạng các cấu trúc bị thiếu thay vì các cấu trúc hoạt động khác đi. Chỉnh sửa bằng sudo visudo, sau đó xác thực bằng sudo visudo -c trước khi bạn đóng phiên làm việc.
Cái gì thay thế sudo -E trong sudo-rs?
sudo -E không được triển khai, và nó vốn đã không được khuyến khích trong sudo gốc, vì việc cung cấp cho một tiến trình root một môi trường mà người gọi có thể kiểm soát là một cách phổ biến để thay đổi cách tiến trình đó hoạt động. Thay vào đó, hãy liệt kê các biến bạn thực sự cần trong sudoers, với một dòng như Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY". env_reset luôn được bật trong sudo-rs và không thể tắt, vì vậy mọi biến bạn không giữ lại đều sẽ bị xóa.