Least Privilege trong thực tế: câu hỏi không phải là “có cấp sudo hay không?”
Một engineer cần vào server để xem log, kiểm tra container và đôi khi restart service. Cấp full sudo thì nhanh, nhưng đó chưa chắc là quyền họ thật sự cần.
Có một bài toán mình từng gặp trong công việc Infrastructure: một engineer bên ngoài cần SSH vào một nhóm server để kiểm tra application, xem log, inspect container và thỉnh thoảng restart một service.
Yêu cầu hoàn toàn hợp lý. Câu hỏi là họ có cần quyền root trên toàn bộ server hay không.
Cách nhanh nhất là tạo account rồi cấp sudo đủ rộng. Vận hành thuận tiện, nhưng nếu credential bị lộ hoặc một command được chạy nhầm thì phạm vi ảnh hưởng cũng rộng tương ứng.
Mình đổi cách đặt câu hỏi thành:
Người này cần thực hiện những operation nào để hoàn thành công việc?
Bastion không chỉ là máy trung gian để SSH tiếp
Nếu access path được thiết kế rõ ràng — user → Bastion → target được phép — Bastion trở thành chỗ dễ kiểm soát hơn cho authentication, source path và network policy.
Điều mình thích ở mô hình này là nó làm câu chuyện access dễ giải thích: ai đi vào từ đâu, đi đến đâu và được phép làm gì. Không cần biến Bastion thành một platform quá phức tạp mới có được giá trị đó.
SSH key-only giải quyết một phần của bài toán
Với kiểu access này, mình ưu tiên SSH key thay cho password login. Nó giúp tránh password reuse và việc revoke theo từng user cũng rõ hơn.
Nhưng private key vẫn là credential. Nếu người dùng bảo vệ key kém thì hệ thống vẫn có rủi ro. Vì vậy key-only là một lớp kiểm soát, không phải lý do để những lớp còn lại trở nên rộng hơn.
Từ “sudo” sang những operation cụ thể
Nếu công việc chỉ cần xem container đang chạy, đọc log, inspect trạng thái và restart đúng service được phê duyệt, account đó không cần khả năng sửa firewall, tạo user mới, đổi SSH configuration hay cài package tùy ý.
Khi nhìn theo operation, sudoers không còn là câu hỏi nhị phân “có sudo / không sudo”. Nó trở thành cách ánh xạ nhu cầu vận hành thành permission cụ thể.
Một điểm mình thấy hữu ích là tách read-only operation khỏi change operation.
Xem trạng thái, đọc log hay inspect metadata thường có mức rủi ro khác với restart. Restart làm thay đổi trạng thái hệ thống. Khi tách hai nhóm, application owner và Infrastructure team cũng dễ thống nhất scope hơn.
Quyền quá chặt cũng có thể làm vận hành tệ đi
Nếu một engineer được giao trách nhiệm vận hành application nhưng mỗi lần xem log hay restart service đều phải mở ticket chờ một team khác, control đó bắt đầu tạo friction không cần thiết.
Khi friction đủ lớn, con người thường tìm workaround. Vì vậy mục tiêu của mình không phải cấp ít quyền nhất bằng mọi giá, mà là cấp đủ để họ tự làm phần việc nằm trong scope, còn phần ngoài scope thì không mở sẵn “phòng khi sau này cần”.
Một file sudoers nhỏ có thể nói rất rõ access model
Để câu chuyện không dừng ở nguyên tắc, có thể hình dung một ví dụ trên Ubuntu. Thay vì full sudo, mình tách operation thành nhóm read và change, rồi ánh xạ vào hai role khác nhau. Operator chủ yếu quan sát; DevOps có thêm một số operation thay đổi trạng thái đã được phê duyệt.
# /etc/sudoers.d/devops-operator
#
# Ví dụ Least Privilege:
# - DevOps: xem Docker + restart container + quản lý Docker daemon
# - Operator: chỉ quan sát, không được restart
Cmnd_Alias DOCKER_READ = \
/usr/bin/docker ps, \
/usr/bin/docker logs *, \
/usr/bin/docker inspect *, \
/usr/bin/docker stats *
Cmnd_Alias DOCKER_CHANGE = \
/usr/bin/docker restart *
Cmnd_Alias DOCKER_SERVICE_READ = \
/usr/bin/systemctl status docker, \
/usr/bin/journalctl -u docker *, \
/usr/bin/journalctl -u docker.service *
Cmnd_Alias DOCKER_SERVICE_CHANGE = \
/usr/bin/systemctl restart docker
Cmnd_Alias SYSTEM_READ = \
/usr/bin/ss *, \
/usr/bin/df *, \
/usr/bin/du *, \
/usr/bin/free *, \
/usr/bin/ps *, \
/usr/bin/ls *
%devops ALL=(root) NOPASSWD: \
DOCKER_READ, \
DOCKER_CHANGE, \
DOCKER_SERVICE_READ, \
DOCKER_SERVICE_CHANGE, \
SYSTEM_READ
%operator ALL=(root) NOPASSWD: \
DOCKER_READ, \
DOCKER_SERVICE_READ, \
SYSTEM_READĐiểm đáng chú ý không phải là copy nguyên file này vào production. Ý chính là không có một rule full sudo cho role. Mỗi nhóm quyền bắt đầu từ operation mà công việc cần, và hai role không mặc nhiên có cùng capability.
Một chi tiết cần để ý: wildcard * trong sudoers vẫn phải được review cẩn thận. Khi cần chặt hơn, có thể giới hạn theo container, service, argument hoặc approved path cụ thể.
Trước khi áp dụng, mình luôn muốn kiểm tra syntax như một phần của change:
Sau đó xác nhận lại account thực sự được phép làm gì:
Một file trong /etc/sudoers.d/ có thể rất nhỏ, nhưng nó buộc mình trả lời một câu khá quan trọng: quyền này tồn tại để phục vụ công việc nào? Nếu không trả lời được, có lẽ permission đang rộng hơn mức cần thiết.
Ghi lại từ những trải nghiệm thực tế của mình trong quá trình làm Infrastructure — những câu chuyện vẫn còn tiếp tục.
Đọc trước ← High Availability không bắt đầu từ thiết bị thứ haiĐọc tiếp → Migration không bắt đầu ở Cutover