背景
最近在測試將 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
之後我們便可以使用這個私鑰生成 token
了:
bin/pulsar tokens create --private-key file:///path/to/my-private.key \
--subject 123456
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9
其中的
subject
和本文長提到的role
相等
使用 subject 授權
只是單純生成了 token
其實並沒有什麼作用,還得將 subject
(role) 與 topic
進行授權繫結。
也就是上圖的這個步驟。
這裡建立的
admin
客戶端也得使用一個superRole
角色的token
才有許可權進行授權。
superRole
使用在broker.conf
中進行配置。
客戶端使用 token 接入 broker
PulsarClient client = PulsarClient.builder()
.serviceUrl("pulsar://broker.example.com:6650/")
.authentication(AuthenticationFactory.token("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY"))
.build();
使用剛才私鑰生成的 token 接入 broker 才能生產或者消費資料。
originalPrincipal cannot be a proxy role
這些流程正常都沒啥問題,但直到我升級了 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
生成的。
token 不一致
但也有一個疑惑,我在換為存放在 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
不同但確依然能使用的原因。