最近在測試將 Pulsar
2.11.2 升級到 3.0.1
的過程中碰到一個鑑權問題,正好藉著這個問題充分了解下 Pulsar
的鑑權機制是如何運轉的。
Pulsar 支援 Namespace/Topic
級別的鑑權,在生產環境中往往會使用 topic
級別的鑑權,從而防止訊息洩露或者其他因為許可權管控不嚴格而導致的問題。
我們會在建立 topic
的時候為 topic
繫結一個應用,這樣就只能由這個應用傳送訊息,其他的應用嘗試傳送訊息的時候會遇到 401 鑑權的異常。
同理,對於訂閱者也可以關聯指定的應用,從而使得只有規定的應用可以消費訊息。
以上的兩個功能本質上都是通過 Pulsar
的 admin-API
實現的。
這裡關鍵的就是 role
,在我們的場景下通常是一個應用的 AppId
,只要是一個和專案唯一系結的 ID
即可。
這只是授權的一步,整個鑑權流程圖如下:
bin/pulsar tokens create-key-pair --output-private-key my-private.key --output-public-key my-public.key
將公鑰分發到 broker
的節點上,鑑權的時候 broker
會使用公鑰進行驗證。
而私鑰通常是管理員單獨儲存起來用於在後續的步驟為使用者端生成 token
之後我們便可以使用這個私鑰生成 token
了:
bin/pulsar tokens create --private-key file:///path/to/my-private.key \
--subject 123456
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9
其中的
subject
和本文長提到的role
相等
只是單純生成了 token
其實並沒有什麼作用,還得將 subject
(role) 與 topic
進行授權繫結。
也就是上圖的這個步驟。
這裡建立的
admin
使用者端也得使用一個superRole
角色的token
才有許可權進行授權。
superRole
使用在broker.conf
中進行設定。
PulsarClient client = PulsarClient.builder()
.serviceUrl("pulsar://broker.example.com:6650/")
.authentication(AuthenticationFactory.token("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY"))
.build();
使用剛才私鑰生成的 token 接入 broker 才能生產或者消費資料。
這些流程正常都沒啥問題,但直到我升級了 Pulsar3.0
後用戶端直接就連不上了。
在 broker
中看到了 WARN
的警告紀錄檔:
cannot specify originalPrincipal when connecting without valid proxy role
之後在 3.0 的升級紀錄檔中看到相關的 Issue。
從這個 PR 相關的程式碼和變更的檔案可以得知:
升級到 3.0 之後風險校驗等級提高了,proxyRole
這個欄位需要在 broker
中進行指定(之前的版本不需要強制填寫)。
因為我們使用了 Proxy 元件,所有的請求都需要從 proxy 中轉一次,這個 proxyRole 是為了告訴 broker:只有使用了 proxyRole
作為 token
的 Proxy
才能存取 broker,這樣保證了 broker
的安全。
superUserRoles: broker-admin,admin,proxy-admin
proxyRoles: proxy-admin
以上是我的設定,我的 Proxy 設定的也是 proxy-admin
這個 token,所以理論上是沒有問題的,但依然鑑權失敗了,檢視 broker 的紀錄檔後拿到以下紀錄檔:
Illegal combination of role [proxy-admin] and originalPrincipal [proxy-admin]: originalPrincipal cannot be a proxy role.
排查了許久依然沒有太多頭緒,所以我提了相關的 issue:
https://github.com/apache/pulsar/issues/21583
之後我諮詢了 Pulsar
的 PMC @Technoboy 在他的提示下發現我在測試的時候使用的是 proxy-admin
,正好和 proxyRoles
相等。
閱讀原始碼和這個 PR
的 comment
之後得知:
也就是說使用者端不能使用和 proxyRole
相同的角色進行連線,這個角色應當也只能給 Proxy
使用,這樣的安全性才會高。
所以這個 Comment 還在討論這是一個 breaking change?
還是一個增強修補程式。
因為合併這個 PR 後對沒有使用 proxyRole
的使用者端將無法連線,同時也可能出現我這種 proxyRole
就是使用者端使用的角色,這種情況也會鑑權失敗。
所以我換了一個 superRole 角色就可以了,比如換成了 admin
。
但其實即便是放到我們的生產系統,只要設定了
proxyRole
也不會有問題,因為我們應用所使用的 role 都是不這裡的superUserRole
,全部都是使用AppId
生成的。
但也有一個疑惑,我在換為存放在 configmap
中的 admin token 之前(測試環境使用的是 helm 安裝叢集,所以這些 token 都是存放在 configmap 中的),
為了驗證是否只要非 proxyRole
的 superRole
都可以使用,我就自己使用了私鑰重新生成了一個 admin
的 token
。
bin/pulsar tokens create --private-key file:///pulsar/private/private.key --subject admin
這樣生成的 token
也是可以使用的,但是我將 token 複製出來之後卻發現 helm 生成的 token
與我用 pulsar
命令列生成的 token
並不相同。
為了搞清楚為什麼 token 不同但鑑權依然可以通過的原因,之後我將 token decode之後知道了原因:
原來是 Header 不同從而導致最終的 token 不同,helm 生成的 token
中多了一個 typ 欄位。
之後我檢查了 helm 安裝的流程,發現原來 helm 的指令碼中使用的並不是 Java 的命令列工具:
${PULSARCTL_BIN} token create -a RS256 --private-key-file ${privatekeytmpfile} --subject ${role} 2&> ${tokentmpfile}
這個 PULSARCTL_BIN
是一個由 Go 寫的命令列工具,我檢視了其中的原始碼,才知道 Go 的 JWT 工具會自帶一個 header。
https://github.com/streamnative/pulsarctl
而 Java
是沒有這個邏輯的,但也只是加了 header
,payload
的值都是相同的。
這樣也就解釋了為什麼 token
不同但確依然能使用的原因。
作者: crossoverJie
歡迎關注博主公眾號與我交流。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出, 如有問題, 可郵件(crossoverJie#gmail.com)諮詢。