Xác thực trả lời "bạn là ai". ACL trả lời "bạn được làm gì". Bài này bật ACL trên một cụm thật rồi cấp quyền từng lớp một, và cả hai lỗi đầu tiên đều không nói đúng nguyên nhân.

Broker tự chặn chính mình, các lớp ACL, và lỗi gây hiểu nhầm

Bật ACL và broker chết

authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
super.users=User:admin
allow.everyone.if.no.acl.found=false

Broker không khởi động nổi:

ERROR [ControllerApis nodeId=1] Unexpected error handling request
      apiKey=BROKER_REGISTRATION ... principal=User:ANONYMOUS
RuntimeException: Received a fatal error while waiting for the controller

Nguyên nhân rất cụ thể: broker tự đăng ký với controller qua listener CONTROLLER, và listener đó chạy PLAINTEXT — không có xác thực, nên nó là User:ANONYMOUS. ACL từ chối ANONYMOUS, và broker bị chính luật của nó chặn.

super.users=User:admin;User:ANONYMOUS

Cách đúng hơn cho môi trường thật là cho listener CONTROLLER cũng chạy SASL và cấp cho danh tính đó quyền cụm. Nhưng khi bạn đang dựng lần đầu và broker chết với một thông báo nói về RuntimeException, biết rằng nguyên nhân là ANONYMOUS tiết kiệm rất nhiều thời gian.

Bài học chung: allow.everyone.if.no.acl.found=false áp cho cả lưu lượng nội bộ của Kafka, không chỉ cho client của bạn.

Cấp quyền từng lớp

Bốn bước, mỗi bước một phép đo:

Chưa cấp gì:

ghi -> TopicAuthorizationException
đọc -> TopicAuthorizationException

Cấp Write + Describe trên topic cho user app:

ghi -> THÀNH CÔNG
đọc -> GroupAuthorizationException      <- lớp thứ hai

Ghi chạy được ngay. Đọc thì đổi lỗi — không còn là Topic nữa mà là Group.

Cấp Read + Describe trên topic cho user ana:

đọc -> GroupAuthorizationException      vẫn chưa được

Cấp thêm Read trên nhóm consumer:

đọc -> đọc được 3 tin

Đọc cần hai ACL: Read trên topic và Read trên nhóm consumer. Đây là điều gây bối rối nhất khi mới bật ACL, vì thông báo lỗi ở bước ba không hề nhắc tới topic — bạn vừa cấp quyền topic xong và vẫn bị chặn, nên phản xạ đầu tiên là kiểm lại ACL topic.

Lý do có hai lớp: nhóm consumer là một tài nguyên chung. Không có ACL nhóm thì bất kỳ ai đọc được topic cũng có thể tham gia vào nhóm của người khác và cướp partition của họ.

Producer bị chặn nhận lỗi sai chỗ

User ana chỉ có Read. Thử ghi:

ClusterAuthorizationException: Cluster authorization failed.

Không phải TopicAuthorizationException. Lỗi nói về cụm, và không nhắc tới topic nào.

Nguyên nhân: từ Kafka 3.0, enable.idempotence bật mặc định (phần 6), và producer idempotent cần quyền IdempotentWritemức cụm trước khi làm bất cứ gì. Nó thất bại ở bước đó, trước cả khi tới bước kiểm tra topic.

Người gỡ lỗi đọc "Cluster authorization failed" sẽ đi tìm ACL cụm, trong khi thứ thiếu có thể là ACL topic — hoặc cả hai.

Bộ ACL đầy đủ cho một dịch vụ ghi:

kafka-acls.sh --bootstrap-server ka:9092 --command-config admin.props \
  --add --allow-principal User:app \
  --operation Write --operation Describe --topic don-hang

kafka-acls.sh --bootstrap-server ka:9092 --command-config admin.props \
  --add --allow-principal User:app --operation IdempotentWrite --cluster

Từ Kafka 3.0, Write trên topic ngầm cho IdempotentWrite trong nhiều trường hợp — nhưng khai tường minh thì không bao giờ sai, và nó tránh đúng thông báo lỗi khó hiểu ở trên.

Bộ ACL tối thiểu

Dịch vụ đọc:

--allow-principal User:ana --operation Read --operation Describe --topic don-hang
--allow-principal User:ana --operation Read --group nhom-cua-ana

Dịch vụ ghi:

--allow-principal User:app --operation Write --operation Describe --topic don-hang
--allow-principal User:app --operation IdempotentWrite --cluster

Ứng dụng Kafka Streams cần nhiều hơn cả hai cộng lại: đọc topic nguồn, ghi topic đích, toàn quyền trên các topic nội bộ nó tự tạo (phần 27). Dùng mẫu tiền tố:

--allow-principal User:streams --operation All \
  --topic <application-id> --resource-pattern-type prefixed

Điều ACL không làm

Describe là mức thấp nhất và nó lộ thông tin. Cấp Describe trên tất cả topic để tiện gỡ lỗi nghĩa là mọi user biết được danh sách topic và cấu trúc của chúng. Đó thường không phải điều bạn muốn.

ACL không giới hạn dung lượng. Một user có Write ghi được bao nhiêu tuỳ thích — đó là việc của hạn ngạch (phần 33). Hai cơ chế này bổ sung cho nhau, không thay thế nhau.

ACL theo client.id không tồn tại. Chỉ có theo principal, tức theo danh tính đã xác thực. Đó là lý do phần 33 nói hạn ngạch theo client.id chỉ là thoả thuận vận hành.

Từ chối thắng cho phép. Một ACL Deny áp cho một principal sẽ thắng mọi ACL Allow khác, kể cả Allow cụ thể hơn. Hữu ích để chặn nhanh một dịch vụ đang gây sự cố, và cũng dễ gây bối rối nếu ai đó thêm Deny rồi quên.

Chuyển đổi an toàn trên cụm đang chạy

Bật allow.everyone.if.no.acl.found=false trên cụm đang chạy sẽ chặn hết mọi thứ trừ những gì đã có ACL. Đó là một sự cố toàn phần trong vài giây.

Quy trình đúng có bốn bước:

  1. Bật authorizer với allow.everyone.if.no.acl.found=true. Mọi thứ vẫn chạy.
  2. Bật ghi nhật ký từ chối (authorizer.logger ở mức DEBUG) và thu thập xem ai đang làm gì.
  3. Thêm ACL cho từng dịch vụ dựa trên dữ liệu thật, không dựa trên tài liệu.
  4. Đổi cờ thành false và theo dõi sát.

Bước 2 là bước hay bị bỏ qua, và nó là bước quan trọng nhất — vì luôn có một dịch vụ mà không ai nhớ là nó đang đọc topic đó.

Thử ba mươi giây

Kiểm cụm của bạn có bật phân quyền không:

kafka-acls.sh --bootstrap-server kf:9092 --list 2>&1 | head -3

Nếu nó báo không có authorizer nào được cấu hình, mọi client kết nối được đều có toàn quyền: đọc mọi topic, ghi mọi topic, và xoá mọi topic. Với cụm dùng chung nhiều đội, đó là chỗ đáng sửa trước cả TLS — vì nó không tốn hiệu năng gì.

Phần sau đo sao chép giữa hai trung tâm dữ liệu: dữ liệu sang rất nhanh, còn offset thì không.