SSD Nodes Learn
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-07-23

Cách chạy service bằng user không có đặc quyền

Tránh chạy service bằng root để hạn chế rủi ro chiếm quyền điều khiển server. Bạn nên dùng system user riêng hoặc dùng DynamicUser của systemd để bảo mật.

Tại sao không nên chạy mọi thứ bằng root

Root có thể làm bất cứ điều gì trên máy: đọc mọi file, thay đổi mọi thiết lập, hoặc xóa toàn bộ hệ thống. Khi bạn chạy một service bằng root, bạn đang giao toàn bộ quyền lực đó cho service đó. Nếu service có lỗi mà attacker có thể khai thác, họ không chỉ chiếm được service, họ chiếm luôn root, và root chính là toàn bộ server. Chạy bằng một user không có đặc quyền (unprivileged user) sẽ giúp khoanh vùng thiệt hại. Một lỗi trong một service chạy bằng account hạn chế chỉ cho phép attacker tiếp cận những gì account đó có quyền chạm vào, vốn dĩ gần như không có gì.

Đây là nguyên tắc đặc quyền tối thiểu (least privilege): cấp cho mỗi thành phần của hệ thống chính xác những quyền truy cập cần thiết để thực hiện công việc của nó, và không hơn. Đây là thói quen hiệu quả nhất để giới hạn phạm vi ảnh hưởng (blast radius) khi bị xâm nhập, và trên một server hiện đại, việc áp dụng nó gần như không tốn chi phí.

Một account riêng biệt cho mỗi service

Cách tiếp cận truyền thống là tạo một system user riêng cho mỗi service, một user chỉ sở hữu các file của service đó và không thể đăng nhập. Một system account cho một web app có thể trông như thế này:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin appsvc

Mỗi flag đều quan trọng. --system biến nó thành một service account thay vì một login cho người dùng. --no-create-home bỏ qua home directory không cần thiết. --shell /usr/sbin/nologin có nghĩa là ngay cả khi attacker bằng cách nào đó chiếm được account, họ cũng không thể mở shell bằng nó. Account này tồn tại chỉ để sở hữu một process và các file của nó.

Sau đó, chỉ cấp cho user đó những file cần thiết, không hơn:

sudo chown -R appsvc:appsvc /opt/myapp

Giờ đây, service chỉ đọc và ghi trong directory của chính nó và không có quyền hạn gì ở bất kỳ nơi nào khác trên disk. Nếu nó bị khai thác, các file mà attacker có thể thay đổi chỉ giới hạn trong /opt/myapp; account vẫn có thể đọc bất cứ thứ gì mà thế giới có quyền đọc (world-readable), nhưng không thể sửa đổi phần còn lại của hệ thống.

Để systemd chạy nó bằng user đó

Sau khi account đã tồn tại, hãy yêu cầu systemd chạy service bằng account đó. Trong unit file, chỉ cần một dòng:

[Service]
ExecStart=/opt/myapp/bin/server
User=appsvc
Group=appsvc

User=appsvc có nghĩa là process sẽ bắt đầu với các đặc quyền hạn chế của account đó thay vì root. Đây là cách thông thường và chuẩn mực để chạy một ứng dụng dưới systemd, và bạn nên làm điều này cho mọi service mà bạn viết unit.

Hoặc bỏ qua hoàn toàn account bằng DynamicUser

systemd có thể tiến xa hơn một bước và tạo một user tạm thời cho bạn, một user chỉ tồn tại trong khi service đang chạy. Thiết lập DynamicUser=yes và bạn sẽ không cần quản lý account nữa:

[Service]
ExecStart=/opt/myapp/bin/server
DynamicUser=yes
StateDirectory=myapp

Khi bắt đầu, systemd cấp một user ID chưa được sử dụng; khi dừng, nó sẽ giải phóng ID đó. Service cũng nhận được một /tmp riêng tư, một view chỉ đọc (read-only) của hầu hết filesystem, và một state directory có thể ghi dưới /var/lib/myappStateDirectory= thiết lập và bàn giao cho nó. Đối với một service độc lập chỉ cần state directory của riêng nó, DynamicUser=yes là cách ít tốn công nhất để có được sự cô lập (isolation) mạnh mẽ, vì không có account tồn tại lâu dài để attacker nhắm tới.

Việc viết unit bằng tay khá rắc rối, và việc thiết lập đúng các chỉ thị hardening mới là phần quan trọng nhất. Công cụ generator trong hướng dẫn service và timer của systemd có thể điền các tùy chọn này cho bạn để unit chính xác ngay từ lần đầu.

Cách điều này kết hợp với các thành phần khác

Đặc quyền tối thiểu là một lớp bảo mật, và nó hoạt động kết hợp với các lớp khác thay vì thay thế chúng. Một firewall default-deny kiểm soát những gì có thể tiếp cận service; chạy service bằng user không đặc quyền kiểm soát những gì service có thể làm nếu bị xâm nhập; và SSH hardened ngăn chặn attacker xâm nhập vào máy ngay từ đầu. Không có lớp đơn lẻ nào là đủ, và khi kết hợp lại, chúng đảm bảo rằng một lỗi trong một service không trở thành một vụ xâm nhập toàn bộ server.

Trước khi tiếp tục, hãy chạy qua một danh sách kiểm tra hardening cho toàn bộ máy và tạo một bản sao cá nhân để làm việc:

ToolVPS hardening checklist

FAQ

Tại sao tôi không nên chạy một service bằng root?

Vì root có thể làm bất cứ điều gì trên máy, một service chạy bằng root nếu bị khai thác sẽ giao toàn bộ server cho attacker, chứ không chỉ là service đó. Hãy dành root cho việc quản trị, và chạy mọi service chạy lâu dài bằng một user bị hạn chế.

Làm thế nào để tôi tạo một user không thể đăng nhập?

Chạy sudo useradd --system --no-create-home --shell /usr/sbin/nologin NAME. Shell nologin có nghĩa là account không thể mở một session tương tác ngay cả khi thông tin đăng nhập bị đánh cắp, --system đánh dấu nó là một service account, và --no-create-home bỏ qua home directory không cần thiết. Chỉ cấp quyền sở hữu các file của chính nó cho user đó bằng chown.

systemd DynamicUser là gì?

DynamicUser=yes yêu cầu systemd tạo một user tạm thời cho service, user này chỉ tồn tại khi service đang chạy, vì vậy bạn không bao giờ phải quản lý một account tồn tại lâu dài. Nó cũng cung cấp cho service một /tmp riêng tư, một view filesystem hầu hết là chỉ đọc, và một state directory được quản lý. Đây là cách ít tốn công nhất để chạy một service độc lập dưới một danh tính tạm thời, đặc quyền thấp.

Chạy bằng user không phải root có thay thế firewall không?

Không. Chúng bảo vệ những thứ khác nhau. Chạy bằng user không đặc quyền giới hạn những gì một service có thể làm nếu bị xâm nhập, trong khi firewall giới hạn những gì có thể tiếp cận service. Hãy sử dụng cả hai, cùng với SSH hardened, để mỗi lớp bảo vệ những gì lớp khác không thể.

Service user nên sở hữu những file nào?

Chỉ những file mà service thực sự cần, không hơn. Hãy cấp cho account quyền sở hữu working directory và dữ liệu của nó, và để mọi thứ khác thuộc sở hữu của root. Một mô hình tốt là sudo chown -R svc-app:svc-app /opt/svc-app cho application directory, trong khi cấu hình dưới /etc vẫn thuộc sở hữu của root và chỉ có service mới có quyền đọc. Mục tiêu là nếu process bị xâm nhập, các file mà nó có thể thay đổi chỉ giới hạn trong dữ liệu của chính nó, không phải phần còn lại của hệ thống.

#bảo mật#least-privilege#systemd#users#hardening#linux