通過 Istio 重新實現微服務 (五):認證和授權

斷路器和艙壁模式

在微服務架構中,有兩個重要的模式,它們能夠讓服務實現自愈的效果。

斷路器模式(Circuit Breake)能夠阻止請求發送到不健康的服務實例上,這樣的話,服務能夠進行恢復,同時,客戶端的請求將會轉發到服務的健康實例上(增加了成功率)。

艙壁模式會隔離失敗,避免整個系統宕機,舉例來說,服務B處於有問題的狀態,另外一個服務(服務B的客戶端)往服務B發送請求將會導致客戶端耗盡線程池,從而不能爲其他請求提供服務(即便其他的請求與服務B毫無關係也是如此)。

這裏我不再展示這些模式的實現了,因爲我迫不及待想要分享認證和授權的功能,這些模式的實現你可以參考官方文檔

Istio中的認證和授權

我從來沒有想到有一天我會對認證和授權感到如此興奮。在技術領域,Istio到底做了什麼能夠讓我對這樣恐怖的話題感到興奮呢,更重要的是它爲什麼能夠讓你也爲此感到興奮呢?

這是因爲,Istio將這些責任從我們的服務中剝離了出去,並將其委託給了Envoy代理,這意味着當請求抵達我們的服務時,它們已經經過了認證和授權,我們只需要編寫提供業務價值的代碼就可以了。

讓我們來深入瞭解一下!

使用Auth0進行認證

我們會使用Auth0作爲身份識別和訪問管理的服務器,它有一個試用方案,對我來說非常便利,我非常喜歡它!這也就是說,相同的理念可以用於任意的OpenID Connect實現,比如KeyCloak、IdentityServer等等。

首先,使用預設賬號導航至Auth0 Portal,在Applications > Default App下創建一個租戶,並記住Domain,如下所示:

圖22 Auth0管理門戶中的Default App

更新resource-manifests/istio/security/auth-policy.yaml文件,以便於使用該Domain:

apiVersion: authentication.istio.io/v1alpha1 
kind: Policy
metadata:
  name: auth-policy
spec:
  targets:
  - name: sa-web-app
  - name: sa-feedback
  origins:
  - jwt:
		issuer: "https://{YOUR_DOMAIN}/"
		jwksUri: "https://{YOUR_DOMAIN}/.well-known/jwks.json" 
	principalBinding: USE_ORIGIN

藉助該資源,Pilot將會配置envoy在將請求轉發至sa-web-app和sa-feedback服務之前進行認證。同時,它不會將該規則應用於服務sa-frontend的envoy,這樣的話,前端能夠不用進行認證。爲了應用該策略,我們需要執行如下命令:

$ kubectl apply -f resource-manifests/istio/security/auth-policy.yaml
policy.authentication.istio.io "auth-policy" created

回到頁面併發送一個請求,我們將會看到401 Unauthorized,現在,我們讓來自前端的用戶使用Auth0進行認證。

使用Auth0認證請求

要認證終端用戶的請求,我們需要在Auth0中創建一個API,它代表了經過認證的服務,比如評論、詳情和評級。爲了創建這樣的API,我們需要導航至Auth0 Portal > APIs > Create API,如下圖所示:

圖23 創建API

這裏重要的信息是Identifier,在隨後的腳本中,我們會以如下的形式進行使用:

  • Audience: {YOUR_AUDIENCE}

其餘所需的細節在Auth0 Portal的Applications下進行配置,選擇自動創建的Test Application,其名稱與API相同。

這裏需要注意:

  • Domain: {YOUR_DOMAIN}
  • Client Id: {YOUR_CLIENT_ID}

在Test Application中向下滾動至Allowed Callback URLs文本域,在這裏我們可以指定在認證完成後要轉發到哪個URL,在我們的場景中,應該是:

http://{EXTERNAL_IP}/callback

Allowed Logout URLs中添加如下的URL:

http://{EXTERNAL_IP}/logout

接下來,我們更新前端。

更新前端

切換至倉庫的auth0分支。在這個分支中,前端包含了將用戶轉發至Auth0進行認證的代碼變更,並在發送至其他服務的請求中包含了JWT Token,如下所示:

analyzeSentence() {
    fetch('/sentiment', {
        method: 'POST',
        headers: {
				'Content-Type': 'application/json',
				'Authorization': `Bearer ${auth.getAccessToken()}` // Access Token 
		},
		body: JSON.stringify({ sentence: this.textField.getValue() }) 
	})
	.then(response => response.json()) 
	.then(data => this.setState(data));
}

爲了更新前端以便於使用租戶的詳細信息,我們導航至sa-frontend/src/services/Auth.js文件並替換如下的值,也就是在前文中我提醒注意的地方:

const Config = {
	clientID: '{YOUR_CLIENT_ID}',
	domain:'{YOUR_DOMAIN}',
	audience: '{YOUR_AUDIENCE}',
	ingressIP: '{EXTERNAL_IP}' // Used to redirect after authentication
}

應用已經就緒了,在如下的命令指定docker user id,然後構建和部署變更:

$ docker build -f sa-frontend/Dockerfile \
	-t {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0 sa-frontend

$ docker push {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0
$ kubectl set image deployment/sa-frontend \ 
	sa-frontend={DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0

我們可以嘗試運行一下應用。訪問的時候,你會被導航至Auth0,在這裏必要要登錄(或註冊),再次之後會重新轉發會頁面,這時候就可以發送認證後的請求了。如果你使用之前的curl命令的話,將會遇到401狀態碼,表明請求沒有經過授權。

接下來,我們更進一步,對請求進行授權。

使用Auth0進行授權

認證能夠讓我們知道用戶是誰,但是我們需要授權信息才能得知他們能夠訪問哪些內容。Istio也提供了這樣的工具。

作爲樣例,我們會創建兩組用戶(如圖24所示):

  • Users:只能訪問SA-WebApp和SA-Frontend服務;
  • Moderators:能夠訪問所有的三個服務。

圖24 授權的概念

爲了創建用戶組,我們需要使用Auth0 Authorization擴展,隨後藉助Istio,我們爲它們提供不同級別的訪問權限。

安裝和配置Auth0授權

在Auth0 Portal中,切換至Extensions,並安裝“Auth0 Authorization”擴展。安裝之後,切換至Authorization Extension並點擊右上角你的租戶並選擇“Configuration”選項進行配置。啓用組並點擊“Publish rule”按鈕。

圖25 在Token Contents中激活組

創建Group

在Authorization Extension中,導航至Groups並創建Moderators組。我們會將所有已認證過的用戶視爲普通的User,這樣的話,就沒有必要額外再建立一個組了。

選擇Moderators組並點擊Add Members,然後添加你的主賬號。請將一些用戶不放到任何的組中,以便於檢驗禁止他們訪問(你可以通過Auth0 Portal > Users > Create User手動註冊新用戶)。

添加Group Claim到Access Token

用戶現在已經添加到組中了,但是這些信息還沒有反映到Access Token中。爲了確保OpenID Connect的一致性,同時又能返回組,我們需要在token中添加自定義的命名空間聲明。這可以通過Auth0規則來實現。

要在Auth0 Portal中創建規則,我們需要導航至Rules,點擊“Create Rule”,並從模板中選擇一個empty rule

圖26 Rules截屏

粘貼如下的代碼並將規則命名爲“Add Group Claim”。

function (user, context, callback) { 
	context.accessToken['https://sa.io/group'] = user.groups[0]; 
	return callback(null, user, context);
}

注意: 這個規則會獲取Authorization Extension中定義的第一個用戶組,並將其添加到access token中,作爲自定義命名空間的聲明。

回到Rules頁面,確保我們按照如下順序有了兩個角色:

  • auth0-authorization-extension
  • Add Group Claim

這裏的順序是非常重要的,因爲組的字段會由auth0-authorization-extension異步獲取,然後由第二個規則將其添加爲命名空間聲明,這將會形成如下的access token:

{
"https://sa.io/group": "Moderators",
"iss": "https://bookinfo.eu.auth0.com/", 
"sub": "google-oauth2|196405271625531691872"
// [shortened for brevity]
}

現在,我們必須配置Envoy代理,從而能夠在返回的access token中抽取https://sa.io/group聲明的組。這是下一部分的主題,我們馬上來看一下。

在Istio中配置授權

爲了實現授權,我們爲Istio啓用RBAC。爲了實現這一點,要將如下的配置用到Mesh中:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: RbacConfig
metadata:
  name: default
spec:
	mode: 'ON_WITH_INCLUSION'  # 1
	inclusion:
		services:               # 2
		- "sa-frontend.default.svc.cluster.local"
		- "sa-web-app.default.svc.cluster.local"
		- "sa-feedback.default.svc.cluster.local"

  1. 僅爲“Inclusion”字段中的服務和命名空間啓用RBAC;
  2. 包含如下服務。

執行如下命令,應用該配置:

$ kubectl apply -f resource-manifests\istio\security\enable-rbac.yaml
rbacconfig.rbac.istio.io "default" created

現在,所有的服務都需要基於角色的訪問控制(Role-Based Access Control),換句話說,訪問所有的服務都會遭到拒絕並且響應中會包含“RBAC: access denied”。我們下一節的主題就是啓用對授權用戶的訪問。

配置常規的用戶訪問

所有的用戶應該都能訪問SA-FrontendSA-WebApp服務,這是通過如下的Istio資源實現的:

  • ServiceRole:指定用戶具有的權限;
  • ServiceRoleBinding:指定ServiceRole應用到哪些用戶上。

對於常規用戶,我們允許訪問指定的服務:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRole
metadata:
	name: regular-user 
	namespace: default
spec: 
	rules:
	- services:
		- "sa-frontend.default.svc.cluster.local"
		- "sa-web-app.default.svc.cluster.local" 
		paths: ["*"]
		methods: ["*"]

然後,藉助regular-user-binding,我們將該ServiceRole應用到頁面的所有訪問者上:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRoleBinding
metadata:
	name: regular-user-binding
	namespace: default 
spec:
  subjects:
  - user: "*"
  roleRef:
    kind: ServiceRole
    name: "regular-user"

這裏所說的所有用戶,是不是意味着未認證的用戶也能訪問SA WebApp呢?並非如此,該策略依然會檢查JWT token的合法性。

通過如下命令,應用該配置:

$ kubectl apply -f resource-manifests/istio/security/user-role.yaml
servicerole.rbac.istio.io/regular-user created
servicerolebinding.rbac.istio.io/regular-user-binding created

配置Moderator用戶訪問

對於Moderator,我們想要啓用對所有服務的訪問:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRole
metadata:
	name: mod-user
	namespace: default 
spec:
	rules:
	- services: ["*"]
		paths: ["*"]
  		methods: ["*"]

但是,我們只想要綁定Access Token中https://sa.io/group聲明的值爲Moderators的用戶。

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRoleBinding
metadata:
	name: mod-user-binding
	namespace: default 
spec:
	subjects:
	- properties:
		request.auth.claims[https://sa.io/group]: "Moderators"
	roleRef:
		kind: ServiceRole
		name: "mod-user"

要應用該配置,需要執行下述命令:

$ kubectl apply -f resource-manifests/istio/security/mod-role.yaml
servicerole.rbac.istio.io/mod-user unchanged servicerolebinding.rbac.istio.io/mod-user-binding unchanged

Envoy中會有緩存,所以授權規則可能需要幾分鐘之後才能生效,在此之後,我們就可以校驗Users和Moderators會有不同級別的訪問權限。

坦白講,你見過像這樣毫不費力就能構建可擴展的認證和授權嗎,還有比這更簡單的過程嗎?反正我是沒有見過!接下來,我們總結一下得到的結論。

結論

想象不到吧?我們將其稱爲怪獸(Beast-io),因爲它確實太強大了。藉助它,我們能夠:

  • 實現服務的可觀察性,藉助Kiali,我們能夠回答哪個服務在運行、它們的表現如何以及它們是如何關聯在一起的;
  • 藉助PrometheusGrafana,能夠實現指標收集和可視化,完全是開箱即用的
  • 使用Jaeger(德語中獵人的意思)實現請求跟蹤;
  • 對網絡流量的完整且細粒度控制,能夠實現金絲雀部署、A/B測試和影子鏡像
  • 很容易實現重試、超時和斷路器
  • 極大地減少了爲集羣引入新服務的開銷;
  • 爲各種語言編寫的微服務添加了認證和授權功能,服務端代碼無需任何修改。

圖27 Beast-io!

有個這個Beastio,我們就能讓團隊真正地提供業務價值,並將資源聚焦在領域問題上,不用再擔心服務的開銷,讓服務真正做到“微型化”。

我是Rinor Maloku,非常感謝Istio之旅。你可以在Twitter 關注我,也可以訪問我的主頁 rinormaloku.com

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章