Hướng dẫn · 03/10/2026

Systemd credentials thực chiến: Giao secret an toàn cho service

Đặt mật khẩu database trong Environment=, file EnvironmentFile= hoặc tham số ExecStart= rất tiện, nhưng dễ làm secret xuất hiện trong cấu hình, diagnostic, process metadata hoặc bị kế thừa ngoài ý muốn. Systemd credentials cung cấp một đường giao secret dưới dạng file riêng cho từng service, tồn tại theo vòng đời unit và có thể được mã hóa khi lưu trữ.

Systemd credentials thực chiến: Giao secret an toàn cho service

Đặt mật khẩu database trong Environment=, file EnvironmentFile= hoặc tham số ExecStart= rất tiện, nhưng dễ làm secret xuất hiện trong cấu hình, diagnostic, process metadata hoặc bị kế thừa ngoài ý muốn. Systemd credentials cung cấp một đường giao secret dưới dạng file riêng cho từng service, tồn tại theo vòng đời unit và có thể được mã hóa khi lưu trữ.

Bài viết này triển khai từ LoadCredential= đến LoadCredentialEncrypted=, tích hợp ứng dụng, mã hóa bằng systemd-creds, xoay vòng, sandboxing và kiểm thử. Nội dung được đối chiếu với tài liệu System and Service Credentials, systemd.exec và systemd-creds.

1. Vì sao không nên truyền giá trị secret qua environment?

Environment phù hợp với cấu hình không nhạy cảm, nhưng có các đặc tính bất lợi cho secret:

  • biến được kế thừa xuống process con theo mặc định;
  • thư viện debug, crash reporter hoặc script có thể dump toàn bộ environment;
  • dữ liệu nhị phân và kích thước lớn khó biểu diễn;
  • rotation thường buộc thay đổi file/unit rồi restart mà không có contract rõ;
  • đưa secret trực tiếp vào command line còn có thể lộ qua công cụ quan sát process.

Systemd credential không làm ứng dụng bị compromise trở nên vô hại, nhưng thu hẹp cách secret được phân phối: giá trị nằm trong file mà kernel kiểm tra quyền mỗi lần truy cập, thay vì là chuỗi được phát tán trong process environment.

2. Mô hình vòng đời của credential

Khi unit kích hoạt, service manager thu thập credential, giải mã nếu cần và tạo một directory runtime riêng. Ứng dụng nhận đường dẫn qua $CREDENTIALS_DIRECTORY; mỗi credential là một file có tên ngắn do unit khai báo.

  • Credential được acquire lúc activation và release khi service dừng.
  • Nội dung immutable trong thời gian service đang chạy.
  • Quyền truy cập được giới hạn cho user của service.
  • Với filesystem namespacing, directory credential của unit không nhìn thấy từ service khác.
  • Systemd cố đặt dữ liệu trong memory không swap khi quyền và nền tảng cho phép.

Tổng kích thước credential cho mỗi service hiện có giới hạn, tài liệu systemd nêu mức 1 MB. Đây là kênh cho key, token, certificate và tham số nhỏ, không phải storage blob.

3. Bắt đầu với LoadCredential

Giả sử secret được provision tại /etc/myapp/db-password với quyền root-only:

[Service]
User=myapp
Group=myapp
LoadCredential=db.password:/etc/myapp/db-password
ExecStart=/usr/local/bin/myapp
PrivateMounts=yes
NoNewPrivileges=yes

Ứng dụng đọc:

cred_dir = os.environ["CREDENTIALS_DIRECTORY"]
secret = read_file(cred_dir + "/db.password")

Credential ID không nên chứa dữ liệu tenant hoặc secret. Không hard-code /run/credentials/<unit>; đường dẫn đó không portable cho user service và các môi trường khác.

4. Truyền đường dẫn, không truyền giá trị

Nếu daemon đã hỗ trợ option đọc secret từ file, có thể dùng specifier %d:

[Service]
LoadCredential=api.token:/etc/myapp/api-token
ExecStart=/usr/local/bin/myapp --token-file=%d/api.token

Command line chỉ chứa đường dẫn runtime, không chứa token. Với phần mềm nhận đường dẫn qua environment:

Environment=MYAPP_TOKEN_FILE=%d/api.token

Đây vẫn là biến môi trường, nhưng giá trị chỉ là path. Ứng dụng nên mở file, đặt giới hạn kích thước, đọc một lần, đóng FD và xóa buffer khi thư viện/ngôn ngữ cho phép. Không log nội dung hay trả nó trong health endpoint.

5. SetCredential không dành cho private secret

[Service]
SetCredential=region:ap-southeast-1

SetCredential= đặt literal trong unit. Unit file và dữ liệu D-Bus liên quan có thể đọc được rộng, nên chỉ dùng cho thông tin không nhạy cảm như public key, certificate công khai, feature parameter hoặc tên vùng. Không đặt password, private key hoặc bearer token ở đây.

Nếu cần nhúng ciphertext vào unit, dùng SetCredentialEncrypted=. Ciphertext có thể công khai, nhưng khả năng giải mã vẫn phụ thuộc thiết kế key và trust của host.

6. Mã hóa credential bằng systemd-creds

Tạo blob mã hóa, gắn credential name vào authenticated envelope:

sudo systemd-creds encrypt \
  --name=db.password \
  /root/db-password.txt \
  /etc/credstore.encrypted/db.password.cred

Sau đó cấu hình:

[Service]
LoadCredentialEncrypted=db.password:/etc/credstore.encrypted/db.password.cred
ExecStart=/usr/local/bin/myapp

Mặc định, hệ thống phù hợp có thể kết hợp key từ TPM2 và secret cục bộ trong /var/lib/systemd/credential.secret. Điều này thường khóa blob vào phần cứng và installation hiện tại. Hãy chọn --with-key= theo yêu cầu recovery/provisioning và kiểm tra trên đúng phiên bản systemd; portability và khả năng khôi phục thay đổi theo loại key.

7. Tại sao --name quan trọng?

Tên credential có thể được xác thực cùng ciphertext. Nếu blob tạo cho db.password bị gắn nhầm thành api.token, decryption nên thất bại thay vì âm thầm đưa đúng secret vào sai consumer. Đây là biện pháp chống credential substitution.

Chuẩn hóa naming theo service và mục đích:

myapp.db.password
myapp.oauth.client-key
myapp.tls.private-key

Không nhét environment name vào nội dung secret nếu pipeline deployment đã tách rõ staging/production. Metadata tên vẫn có thể lộ; đừng chứa identifier nhạy cảm.

8. ImportCredential và credential store

ImportCredential= tìm và import credential theo tên/pattern từ system credential hoặc các credential store chuẩn. Các đường dẫn tìm kiếm gồm biến thể của:

/etc/credstore/
/run/credstore/
/usr/lib/credstore/
/etc/credstore.encrypted/
/run/credstore.encrypted/
/usr/lib/credstore.encrypted/

/usr/lib phù hợp default do vendor cung cấp, /etc cho cấu hình administrator, còn /run cho dữ liệu runtime/provisioning. Không commit plaintext production secret vào image. Dùng tên credential ổn định để unit không phụ thuộc backend provision cụ thể.

9. Xoay vòng secret đúng cách

Credential immutable trong một activation. Thay source file hoặc encrypted blob không sửa directory của process đang chạy. Quy trình rotation nên là:

  1. tạo secret mới trong secret manager;
  2. ghi file/blob mới bằng thao tác atomic và quyền đúng;
  3. kiểm tra decrypt/name trên host đích mà không in plaintext vào log;
  4. restart hoặc reload service theo capability của ứng dụng;
  5. chạy readiness/functional check;
  6. revoke secret cũ sau cửa sổ rollback.

Nếu daemon hỗ trợ reload và tự mở lại credential path, cần xác nhận systemd có tạo activation mới hay không. Với contract mặc định, restart là ranh giới đơn giản và dễ kiểm toán hơn.

10. Hardening unit dùng credential

[Service]
User=myapp
DynamicUser=yes
PrivateMounts=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
NoNewPrivileges=yes
RestrictSUIDSGID=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
LoadCredentialEncrypted=api.token:/etc/credstore.encrypted/api.token.cred

Không copy nguyên block hardening nếu ứng dụng cần ghi filesystem, JIT hoặc namespace đặc biệt. Dùng systemd-analyze security myapp.service, test integration và thêm quyền theo nhu cầu nhỏ nhất. Nhiều directive sandbox đã ngầm bật mount namespacing; PrivateMounts=yes làm intent rõ hơn.

11. Test với transient service

Kiểm tra một credential plaintext mà không sửa unit lâu dài:

sudo systemd-run -P --wait \
  -p LoadCredential=test:/etc/hosts \
  systemd-creds cat test

Trong CI hoặc smoke test production, không dùng cat cho secret thật. Thay vào đó kiểm tra file tồn tại, owner/mode, kích thước hợp lệ hoặc so sánh fingerprint một chiều đã được phê duyệt. Kiểm tra cả đường lỗi: thiếu credential, blob sai tên, decryption fail và permission sai phải làm service fail closed.

12. Những gì credentials không bảo vệ

  • Root hoặc process có quyền debug phù hợp vẫn có thể tấn công service.
  • Ứng dụng đã bị compromise có thể đọc credential của chính nó.
  • Secret có thể lộ nếu code log, core dump hoặc gửi nhầm qua network.
  • Mã hóa at rest không thay thế disk encryption, Secure Boot, TPM policy và quản trị host.
  • Credential không tự fetch/renew dynamic secret từ Vault/KMS.
  • Nó không cung cấp audit backend, lease, revocation hoặc rotation orchestration hoàn chỉnh.

Hãy xem systemd credentials là lớp delivery cục bộ tốt. Secret manager vẫn chịu trách nhiệm nguồn gốc, policy, rotation và audit ở cấp tổ chức.

13. Migration từ EnvironmentFile

  1. Liệt kê biến nào thực sự nhạy cảm; giữ cấu hình thường trong environment.
  2. Sửa ứng dụng để hỗ trợ *_FILE hoặc trực tiếp đọc CREDENTIALS_DIRECTORY.
  3. Chuyển từng secret sang LoadCredential=.
  4. Thêm sandboxing và kiểm tra service user.
  5. Chuyển source at rest sang encrypted credential nếu phù hợp.
  6. Xóa secret khỏi drop-in, deployment log, history và artifact cũ theo retention policy.
  7. Rotate vì secret từng xuất hiện trong kênh cũ phải được xem là có khả năng đã lộ.

Checklist production

  • Ứng dụng đọc file từ CREDENTIALS_DIRECTORY hoặc path %d.
  • Không đưa giá trị secret vào Environment, ExecStart hoặc unit literal.
  • SetCredential chỉ chứa dữ liệu không nhạy cảm.
  • Dùng encrypted credential với key policy đã kiểm thử recovery.
  • Credential name ổn định và được authenticate khi mã hóa.
  • Bật mount namespacing và hardening phù hợp.
  • Không log, cat hoặc hash yếu secret trong CI/monitoring.
  • Rotation dùng atomic replace, restart, verify rồi revoke.
  • Test fail-closed khi thiếu hoặc không giải mã được credential.
  • Giữ secret manager/KMS làm source of truth khi hệ thống cần lease và audit tập trung.

Kết luận

Systemd credentials tạo một contract tốt hơn cho secret trên Linux host: service nhận file riêng lúc activation, dùng path ổn định, không phát tán giá trị qua environment và có thể giải mã blob ngay trước khi khởi chạy. Khi kết hợp naming rõ ràng, mount namespace, unit hardening và rotation có kiểm chứng, cơ chế này giảm đáng kể bề mặt rò rỉ mà không buộc ứng dụng tích hợp trực tiếp với mọi backend secret.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.