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 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.
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.