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. Bài này đóng lại chỗ đó — vhost, phân quyền, TLS — và đo cái giá của từng thứ.

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 basicGetbasicConsume, 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
Truyền dữ liệu qua TLS gần như không tốn gì — 5%, nằm sát ranh giới nhiễu. Nhưng bắt tay thì đắt gấp 2,7 lần, vì đó là chỗ có trao đổi khoá và kiểm chứng chỉ.

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à userIdphầ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.

Bài sau: giám sát — con số nào thật sự đáng đặt cảnh báo.

Thử ba mươi giây

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.