Hình dung vhost như các căn hộ trong cùng một chung cư: căn nào cũng có thể có "Phòng 101", nhưng đồ trong phòng 101 nhà này không liên quan gì phòng 101 nhà kia — tên trùng, thực thể khác hẳn. Chỉ có điều tất cả vẫn xài chung một bể nước, một đường điện. Bài này đi qua ba lớp an toàn của RabbitMQ — vhost, phân quyền, TLS — và đo cái giá của từng thứ. Phần 2 mở đầu sê-ri bằng một phát hiện khó chịu: image Docker chính thức cố ý mở tài khoản guest ra toàn mạng. Đây là bài đóng lại chỗ đó.
vhost tách biệt hoàn toàn
Tạo cùng một tên hàng đợi ở hai vhost:
vhost / : hàng đợi 'chung-ten' có 5 thông điệp
vhost /thu-nghiem : hàng đợi 'chung-ten' có 0 thông điệp
Cùng tên, hai thực thể không liên quan gì nhau. vhost là không gian tên đầy đủ: hàng đợi, exchange, binding, policy, quyền — tất cả đều riêng.
Đây là cách rẻ nhất để tách môi trường hoặc tách đội trên cùng một broker, và nó rẻ theo nghĩa đen: không tốn tiến trình riêng, không tốn cổng riêng. Nhưng nhớ là nó không tách tài nguyên — cùng bộ nhớ, cùng đĩa, cùng CPU. Một vhost bị dồn một triệu thông điệp như phần 32 sẽ ăn đĩa của mọi vhost khác.
Ba cột quyền chặn đúng cái gì
set_permissions nhận ba biểu thức chính quy theo thứ tự configure – write – read. Tôi tạo bốn user, mỗi user tắt một cột, rồi thử bốn thao tác:
| Quyền (configure/write/read) | Khai hàng đợi | Gửi | Đọc | Xoá hàng đợi |
|---|---|---|---|---|
.* / .* / .* |
được | được | được | được |
^$ / .* / .* |
cấm | được | được | cấm |
.* / ^$ / .* |
được | cấm | được | được |
.* / .* / ^$ |
được | được | cấm | được |
Đọc ra ba luật:
configure gộp cả tạo lẫn xoá. Không có cách tách "được tạo nhưng không được xoá" — với một dịch vụ chỉ nên dùng topology có sẵn thì để ^$ là đúng, và phần 27 cho thấy nên như vậy: một nơi duy nhất khai báo topology.
write là quyền trên exchange, không phải trên hàng đợi. Thông báo lỗi nói rõ:
403 ACCESS_REFUSED - write access to exchange 'amq.default' in vhost '/' refused for user 'u-nowr'
Gửi vào hàng đợi bằng tên thực ra là gửi qua default exchange, và phần 12 đo được rằng trong hệ phân quyền nó mang tên amq.default. Đây chính là cách đóng cánh cửa sau mà phần đó chỉ ra: bỏ amq.default khỏi regex write là không ai tiêm thẳng vào hàng đợi bằng tên được nữa.
read là quyền trên hàng đợi — chặn basicGet và basicConsume, không ảnh hưởng gì tới việc gửi.
Bộ quyền thực dụng cho một dịch vụ chỉ gửi sự kiện của miền mình:
rabbitmqctl set_permissions -p / dv-don-hang "^$" "^don-hang\." "^$"
# cfg write read
Không tạo được gì, chỉ gửi được vào exchange bắt đầu bằng don-hang., không đọc được của ai. Và vì regex không khớp amq.default, nó cũng không tiêm thẳng vào hàng đợi nào được.
TLS tốn bao nhiêu
Bật listener TLS trên 5671 với chứng chỉ tự ký, rồi đo hai chiều:
| Thông lượng (20 000 thông điệp) | Thời gian mở một connection | |
|---|---|---|
| Không TLS | 136 771 msg/s | 1,8 ms |
| Có TLS | 130 145 msg/s (95%) | 4,8 ms |
Con số đó nói thẳng cách dùng đúng, và nó lặp lại kết luận của phần 3: giữ connection mà dùng lại. Ứng dụng mở một connection rồi sống với nó thì TLS gần như miễn phí; ứng dụng mở connection cho mỗi lần gửi thì TLS biến một lỗi thiết kế sẵn có thành lỗi đắt gấp ba.
Cấu hình tối thiểu để bật:
listeners.ssl.default = 5671
ssl_options.cacertfile = /etc/rabbitmq/tls/ca.pem
ssl_options.certfile = /etc/rabbitmq/tls/server.pem
ssl_options.keyfile = /etc/rabbitmq/tls/server-key.pem
ssl_options.verify = verify_none
Dòng cuối là dòng phải đổi trước khi lên sản xuất: verify_peer cộng ssl_options.fail_if_no_peer_cert = true mới bắt client trình chứng chỉ. Với verify_none thì TLS chỉ mã hoá đường truyền, không xác thực ai đang gọi — vẫn tốt hơn không có gì, nhưng đừng nhầm nó với xác thực.
Danh sách tối thiểu
Xoá guest. Phần 2 đo được rằng image chính thức mở nó ra toàn mạng, và đặt RABBITMQ_DEFAULT_USER là nó biến mất hẳn.
Mỗi dịch vụ một user, mỗi user một bộ ba regex. Không dùng chung tài khoản — mất hết khả năng truy vết, và userId mà phần 7 đo được cũng thành vô nghĩa.
Mỗi môi trường một vhost, và đừng bao giờ để dev với prod chung một broker chỉ vì vhost trông giống một hàng rào — bảng đầu bài cho thấy nó là hàng rào tên gọi, không phải hàng rào tài nguyên.
Bật TLS, và đọc lại bảng ở trên trước khi ai đó đổ lỗi cho nó về hiệu năng.
Muốn biết ngay mình đang hở chỗ nào thì hỏi thẳng broker xem có tài khoản nào toàn quyền không:
docker exec -u rabbitmq rmq rabbitmqctl -q list_permissions
docker exec -u rabbitmq rmq rabbitmqctl -q list_users
Bất kỳ dòng nào có .* ở cả ba cột là một tài khoản làm được mọi thứ với mọi thứ. Nếu ứng dụng của bạn đang dùng tài khoản đó, phân quyền của bạn hiện đang không tồn tại.
Mẫu số chung
Một ranh giới chỉ cô lập đúng một chiều, và lẫn lộn chiều nào với chiều nào là cái bẫy an toàn kinh điển. vhost cô lập không gian tên — hàng đợi, exchange, quyền — nhưng không cô lập tài nguyên: bộ nhớ, đĩa, CPU vẫn dùng chung, nên một vhost phình to bóp nghẹt mọi vhost khác. Đúng cái nhầm lẫn ấy lặp ở khắp nơi: namespace của Kubernetes tách tên chứ không tách sức máy (phải thêm ResourceQuota/limit mới có); schema của CSDL tách tên bảng chứ không tách I/O; container tách không gian tiến trình nhưng xài chung nhân hệ điều hành. Trước khi tin một hàng rào, hỏi nó ngăn chiều nào — và đừng cho rằng nó ngăn luôn những chiều còn lại.
Điều thứ hai: đặc quyền tối thiểu chỉ bảo vệ được bạn khi bạn biết mỗi quyền thật sự gác cái gì. Ba cột configure/write/read là ba trục vuông góc — cùng một tinh thần với rwx của Unix, GRANT SELECT/INSERT/UPDATE của SQL, policy của IAM, scope của OAuth — và luật chung là mặc định từ chối (^$), mỗi dịch vụ một danh tính riêng với đúng phần tối thiểu. Nhưng cái bẫy nằm ở chỗ quyền gắn vào đối tượng thật chứ không vào cái bạn tưởng: write hoá ra là quyền trên exchange (amq.default), không phải trên hàng đợi — không biết điều đó thì bạn khoá nhầm cửa và tưởng đã khoá. Cùng họ với cú verify_none nghe như đã bật bảo mật mà thật ra chỉ mã hoá đường truyền chứ không xác thực ai gọi — một nút an toàn làm ít hơn cái tên nó gợi ra, đúng loại niềm-tin-giả đã gặp ở basicQos-dưới-autoAck. Kiểm cái quyền thật sự chặn gì, đừng kiểm cái tên của nó.
Bài sau: giám sát — con số nào thật sự đáng đặt cảnh báo.