Bài trước đã đo rằng một bí mật lỡ vào git thì rất khó dọn. Câu hỏi tiếp theo là: vậy để bí mật ở đâu cho đúng? Có bốn chỗ hay gặp — viết thẳng vào mã, để trong biến môi trường, để trong file .env, hoặc gắn vào một file riêng quyền chặt. Người ta hay xếp chúng thành thang "tệ → tốt", nhưng ít khi nói rõ đo bằng cái gì.

Bài này đo bằng một câu hỏi duy nhất, cụ thể và kiểm chứng được: ai đọc được bí mật? Bề mặt rò rỉ càng rộng — càng nhiều người, nhiều tiến trình, nhiều nơi chạm tới được — thì cách lưu càng tệ. Tôi dựng từng cách trong container dùng một lần với bí mật giả, thử đọc trộm bằng đúng những đường mà kẻ tấn công hay dùng, đo 3 lần rồi tự dọn. Không nhắm vào hệ thống của ai khác. Và có một chỗ tôi đo sai ở lượt đầu — sẽ kể ở phần 2.

Cách 1: hardcode trong mã nguồn

Viết thẳng API_TOKEN = "..." vào file rồi commit. Đo:

nam trong git? CO
ai doc duoc: bat ky ai co repo / clone / lich su

Bề mặt rộng nhất có thể. Không chỉ người trong nhóm mà bất cứ ai từng clone repo, và như bài trước đã đo, bí mật còn ở lại trong lịch sử kể cả sau khi xoá. Đây là đáy của thang. Không có cấu hình nào cứu được cách này ngoài việc đừng làm.

Cách 2: biến môi trường

Đây là lời khuyên phổ biến nhất — "đưa bí mật ra biến môi trường". Nó tốt hơn hardcode vì bí mật không nằm trong mã. Nhưng bề mặt của nó rộng hơn nhiều người tưởng. Đo hai đường:

tien trinh con (khong duoc truyen gi) doc duoc: tok_live_FAKEsecret123456
doc /proc/<pid>/environ tu ben ngoai -> thay API_TOKEN? CO

Điểm thứ nhất: mọi tiến trình con thừa kế biến môi trường. Tôi chạy một tiến trình con mà không truyền gì cho nó, thế mà nó vẫn đọc được API_TOKEN — vì con thừa hưởng toàn bộ môi trường của cha. Nghĩa là mỗi lần code của bạn gọi một lệnh ngoài (git, curl, một thư viện gọi subprocess), bí mật đi theo. Một thư viện độc hại chỉ cần đọc os.environ là thấy hết.

Điểm thứ hai là chỗ tôi đo sai lần đầu, và tôi giữ lại đây vì nó đúng là loại bẫy dễ mắc. Lượt đầu tôi kiểm /proc/self/environ của chính shell và thấy khôngAPI_TOKEN, suýt kết luận "biến môi trường không lộ qua /proc". Sai. Lý do: /proc/<pid>/environ chụp môi trường lúc tiến trình được exec, và không cập nhật khi sau đó biến mới được export. Shell của tôi đã chạy trước khi tôi đặt biến, nên bản chụp của nó không có. Khi đo lại cho đúng — khởi động một tiến trình với biến đã đặt sẵn rồi đọc /proc/<pid>/environ của nó từ bên ngoài — bí mật hiện ra ngay, cả 3 lần. Bài học của cái sai đó: một tiến trình thật (ứng dụng của bạn) chạy với bí mật trong môi trường thì bất kỳ ai cùng quyền hoặc root đọc /proc là thấy.

Cộng thêm: biến môi trường lộ qua docker inspect, qua log khi ứng dụng in cả environ lúc gặp lỗi, qua các trang lỗi debug. Bề mặt của "cách tốt hơn hardcode" hoá ra vẫn khá rộng.

Điều này không có nghĩa là bỏ hẳn biến môi trường — chúng vẫn tiện và tránh được cách 1. Nhưng nó nói rõ giới hạn: đừng để ứng dụng in cả environ vào log lúc gặp lỗi, đừng chuyển biến chứa bí mật cho tiến trình con nếu con không cần (một số ngôn ngữ cho phép lọc môi trường khi spawn), và nhớ rằng trên một máy có nhiều người, quyền đọc /proc là chuyện cần siết. Biến môi trường hợp với bí mật đời sống ngắn được cấp lúc chạy, hơn là bí mật lâu dài nằm mãi trong một file khởi động.

Cách 3: file .env trên đĩa

Để bí mật trong một file .env, ứng dụng đọc lúc khởi động. Ở đây quyền file quyết định tất cả. Tôi tạo file rồi thử đọc bằng một user khác (nobody):

quyen 644: -rw-r--r--  ->  user 'nobody' doc duoc? CO
quyen 600: -rw-------  ->  user 'nobody' doc duoc? khong

Với quyền 644 (mặc định hay gặp), mọi user trên máy đọc được — trên một máy chủ dùng chung, đó là bề mặt rộng đáng lo. Đổi sang 600 thì chỉ chủ sở hữu đọc được, và nobody bị chặn ngay. Con số này nói rõ: .env không tự nó an toàn hay không an toàn; nó an toàn đúng bằng quyền bạn đặt.

Nhưng .env có một cạm bẫy riêng, và nó nối thẳng với bài trước: .env rất dễ bị commit nhầm. Nó là file thường trong thư mục dự án, và .gitignore không tự có sẵn dòng chặn nó — bạn phải tự thêm \.env. Quên một dòng đó là quay lại đúng cách 1: bí mật vào git, kèm lịch sử. Nhiều vụ lộ khoá lớn bắt đầu từ đúng một file .env bị git add . nuốt vào.

Cách 4: file quyền 400 trên tmpfs

Cách chặt nhất trong bốn cách: bí mật nằm trong một file riêng, quyền 400 (chỉ chủ sở hữu đọc, không ai ghi), gắn qua tmpfs — tức chỉ tồn tại trong RAM, không ghi xuống đĩa lâu dài. Đo:

loai mount: tmpfs (chi trong RAM)
quyen 400: -r--------  ->  user 'nobody' doc duoc? khong

Bề mặt hẹp nhất: chỉ chủ sở hữu đọc; không vào git (nó không phải file mã nguồn); không thừa kế sang tiến trình con như biến môi trường; và vì ở tmpfs, nó biến mất khi tắt máy thay vì nằm lại trên ổ đĩa chờ ai đó khôi phục. Đây chính là mô hình mà các dịch vụ quản lý bí mật (secret manager, vault) và cơ chế "mounted secret" của Kubernetes dùng: cấp bí mật dưới dạng file tạm quyền chặt, thay vì nhét vào môi trường.

Ghép lại

Sơ đồ bốn cách lưu bí mật và bề mặt rò rỉ

Xếp theo bề mặt rò rỉ, từ rộng đến hẹp:

  1. Hardcode — mọi người xem code + toàn bộ lịch sử git. Đáy thang.
  2. Biến môi trường — mọi tiến trình con, /proc/<pid>/environ, docker inspect, log crash. Tốt hơn hardcode nhưng đừng tưởng nó kín.
  3. File .env — an toàn đúng bằng quyền của nó (644 = cả máy đọc, 600 = chỉ chủ), và rất dễ bị commit nhầm.
  4. File 400 trên tmpfs / secret manager — chỉ chủ sở hữu, không vào git, không thừa kế, mất khi tắt máy. Hẹp nhất.

Điểm chung của cả Series đúng ở đây một lần nữa: không có nút "an toàn" để bật, chỉ có bề mặt rộng hay hẹp mà bạn đo được. Và cái sai của tôi ở phần 2 là lời nhắc thật: đừng tin một phép đo âm tính ("không thấy rò rỉ") cho tới khi chắc mình đo đúng chỗ — tôi suýt tuyên bố biến môi trường kín, chỉ vì đọc nhầm bản chụp môi trường của một tiến trình cũ.

Thử ba mươi giây

Trên một tiến trình của chính bạn đang chạy với bí mật trong môi trường:

# Tim PID ung dung cua ban, roi (voi quyen phu hop) doc environ cua no
tr '\0' '\n' < /proc/<PID>/environ | grep -iE 'token|secret|key|password'

Nếu bí mật hiện ra, đó không phải lỗi — đó là bản chất của biến môi trường: ai đọc được /proc của tiến trình đều thấy. Nếu điều đó làm bạn không thoải mái, đã đến lúc chuyển bí mật từ môi trường sang một file quyền 400, hoặc sang một dịch vụ quản lý bí mật đưa nó vào dưới dạng file tạm.