本教學已加入 Istio 系列:https://istio.whuanle.cn
本章的內容主要是講解服務間通訊的安全和叢集外部存取內部服務的 jwt token 驗證。
Istio 提供兩種型別的認證,一種是服務間認證 Peer Authentication,一種是使用者端請求認證 Request Authentication。
Peer Authentication
Peer authentication 用於服務到服務的認證,在零信任網路中,Envoy 給服務之間的通訊加密,只有服務雙方才能看到請求內容和響應結果。
在 Istio 中,預設情況下,服務之間的通訊不會被加密或進行身份驗證。比如說, A 服務通過 http 請求 B 服務,流量經過 Envoy A 時,Envoy A 直接將流量傳送到 Envoy B 中,流量不會進行加密處理,也就是明文請求。
Istio 的 Peer Authentication 主要解決以下問題:
Request Authentication
Request authentication 用於外部請求的使用者認證, Istio 使用 JWT(JSON Web Token) 來驗證使用者端的請求,並使用自定義認證實現或任何 OpenID Connect 的Request authentication 認證實現來簡化的開發人員體驗。
支援以下認證型別:
Istio 的 PeerAuthentication 是一種安全策略,用於對服務網格內的工作負載之間的通訊進行雙向 TLS(mTLS)驗證。
通過 PeerAuthentication 在 Envoy 間啟用 mTLS,以確保工作負載之間的通訊在傳輸過程中是加密和安全的。
PeerAuthentication 可以設定為整個叢集或只在名稱空間中起作用,但是隻能有一個網格範圍的 Peer 認證策略,每個名稱空間也只能有一個名稱空間範圍的 Peer 認證策略。
下面是一個簡單的 PeerAuthentication 範例:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: my-peer-authentication
namespace: my-namespace
spec:
selector:
matchLabels:
app: my-app
mtls:
mode: STRICT
selector
: 標籤選擇器,用於選擇應用 PeerAuthentication 策略的工作負載。例如:selector:
matchLabels:
app: my-app
如果省略選擇器,PeerAuthentication 策略將應用於名稱空間中的所有工作負載。
mtls: 定義雙向 TLS 的模式,有三種模式。
STRICT
: 強制執行 mTLS,要求使用者端和伺服器使用 TLS 進行通訊。這需要使用者端和伺服器具有有效的證書。
PERMISSIVE
: 允許使用者端使用TLS或純文字進行通訊。這對於逐步遷移到 mTLS 的場景非常有用。
DISABLE
: 禁用 mTLS,不要求使用者端和伺服器使用 TLS 進行通訊。
只能有一個網格範圍的 Peer 認證策略,每個名稱空間也只能有一個名稱空間範圍的 Peer 認證策略。當同一網格或名稱空間設定多個網格範圍或名稱空間範圍的 Peer 認證策略時,Istio 會忽略較新的策略。當多個特定於工作負載的 Peer 認證策略匹配時,Istio 將選擇最舊的策略。
我們繼續服用前面使用的 bookinfo 微服務,給 bookinfo 名稱空間啟用 mTLS。
kubectl apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "bookinfo-policy"
namespace: "bookinfo"
spec:
mtls:
mode: STRICT
EOF
然後再次請求 productpage 服務,可以使用以下命令製作大量的模擬請求。
for i in `seq 1 1000`; do curl -s -o /dev/null http://192.168.3.150:30666/productpage; done
然後在 kiali 面板中的 Display 選項中下拉選擇 Security。
Istio 的 RequestAuthentication 是一種安全策略,用於驗證和授權使用者端存取Istio服務網格中的服務。
RequestAuthencation 需要搭配一個 AuthorizationPolicy來 使用。RequestAuthentication 和 AuthorizationPolicy 這兩個策略用於驗證和授權使用者端存取服務網格中的服務。
RequestAuthentication 負責驗證使用者端提供的 JWT,而 AuthorizationPolicy 負責基於角色的存取控制(RBAC),允許定義細粒度的許可權以限制對特定服務、方法和路徑的存取。
下面是一個完整的 RequestAuthentication 範例:
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: my-request-authentication
namespace: my-namespace
spec:
jwtRules:
- issuer: "https://accounts.google.com"
audiences:
- "my-audience-1"
- "my-audience-2"
jwksUri: "https://www.googleapis.com/oauth2/v3/certs"
jwtHeaders:
- "x-jwt-assertion"
- "x-jwt-assertion-original"
jwtParams:
- "access_token"
forward: true
如果只針對名稱空間中的部分應用,可以使用:
selector: matchLabels: app: my-app
在 RequestAuthentication 中,jwtRules 是一個設定項,用於定義如何驗證和處理 JWT。
一個典型的 jwtRules 設定可能包括以下幾個部分:
issuer
: 發行者,表示JWT的發行方,例如:https://accounts.google.com
。這個欄位用於驗證JWT的iss(發行者)宣告。audiences
: 受眾列表,表示接受JWT的一組實體。這個欄位用於驗證JWT的aud(受眾)宣告。例如:["my-audience-1", "my-audience-2"]
。jwksUri
: JSON Web Key Set(JWKS)的URL,用於獲取JWT簽名公鑰。Istio會從這個URL下載公鑰,用於驗證JWT的簽名。例如:https://www.googleapis.com/oauth2/v3/certs
。jwtHeaders
: 一個字串陣列,表示可以從HTTP請求頭中獲取JWT的頭名稱。預設情況下,Istio會從"Authorization"頭中獲取令牌。例如:["x-jwt-assertion", "x-jwt-assertion-original"]
。jwtParams
: 一個字串陣列,表示可以從HTTP請求引數中獲取JWT的引數名稱。例如:["access_token"]
。forward
: 一個布林值,表示是否將JWT轉發給上游服務。預設值為false
,表示JWT令牌不會轉發給上游服務。如果設定為true
,則Istio會將令牌新增到請求頭中,並轉發給上游服務。通過正確設定 jwtRules,Istio 可以對請求中的 JWT 進行驗證,確保使用者端存取服務網格中的服務時具有適當的授權。
Istio 的 AuthorizationPolicy 是一種安全策略,用於控制在Istio服務網格中誰可以存取哪些服務。它提供了基於角色的存取控制(RBAC),允許定義細粒度的許可權,以限制對特定服務、方法和路徑的存取。AuthorizationPolicy 使用 Istio 的 Envoy 代理攔截並檢查傳入的請求,以確保它們滿足定義的存取策略。
AuthorizationPolicy 的範例如下:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-policy
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- to:
- operation:
paths: ["/delay/*"]
AuthorizationPolicy 的主要屬性包括:
action
: 定義在規則匹配時要執行的操作。它可以是ALLOW
(允許存取),DENY
(拒絕存取)或CUSTOM
(自定義操作,與自定義擴充套件外掛一起使用)。rules
: 定義一組存取策略規則。每個規則可以包括以下屬性:
from
: 包含一個或多個源規範,用於定義允許存取的來源。可以包括principals
(發起請求的主體,例如使用者或服務帳戶)和namespaces
(發起請求的名稱空間)。to
: 包含一個或多個目標規範,用於定義允許存取的操作。可以包括methods
(允許的HTTP方法,例如GET或POST)和paths
(允許存取的路徑,可以是精確路徑或萬用字元路徑)。when
: 包含一組條件,用於定義規則生效的附加約束。例如,您可以使用key
和values
定義請求頭匹配。RequestAuthentication 的作用物件是 Kubernetes Service,主要有兩種形式,一種是繫結 ingressgateway。
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: ingress-jwt
namespace: bookinnfo
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: "[email protected]"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.17/security/tools/jwt/samples/jwks.json"
一種是繫結 Pod。
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: frontend
namespace: default
spec:
selector:
matchLabels:
app: frontend
jwtRules:
- issuer: "[email protected]"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.5/security/tools/jwt/samples/jwks.json"
考慮到一般不會在 istio-ingressgateway 這個入口閘道器上操作,所以下面我們使用第二種形式做實驗。
首先是這個 YAML 檔案中的 jwksUri
,裡面包含了一個 jwksjson 地址,裡面包含了用於驗證 token 是否有效的公鑰。
在 C# 中,可以這樣生成一個 jwksjson。
using System;
using System.IO;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace JWKSGenerator
{
class Program
{
static void Main(string[] args)
{
using var rsa = RSA.Create(2048);
var jwk = new RsaSecurityKey(rsa);
jwk.KeyId = Guid.NewGuid().ToString();
var jsonWebKey = JsonWebKeyConverter.ConvertFromRSASecurityKey(jwk);
var jwkJson = JsonConvert.SerializeObject(jsonWebKey);
var jwksJson = "{\"keys\": [" + jwkJson + "]}";
Console.WriteLine(jwksJson);
}
}
}
考慮到官網範例中給出的 jwks.json 需要FQ才能存取,我們可以直接將 jwks.json 放在 YAML 檔案中。
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: httpbin-jwt
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
jwtRules:
- issuer: "[email protected]"
forwardOriginalToken: true
jwks: |
{
"keys": [
{
"e": "AQAB",
"kid": "DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ",
"kty": "RSA",
"n": "xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"
}
]
}
或者繼續使用官方的 jwksUri。
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: httpbin-jwt
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
jwtRules:
- issuer: "[email protected]"
forwardOriginalToken: true
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.5/security/tools/jwt/samples/jwks.json"
然後部署一個 AuthorizationPolicy,對 /delay/*
地址進行全放通,那麼其它地址都需要進行驗證才能放行。
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-policy
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- to:
- operation:
paths: ["/delay/*"]
執行命令之後,你可以使用以下命令檢視是否正常:
kubectl logs -n istio-system -l app=istiod
然後檢視策略規則物件:
kubectl get requestauthentication -n bookinfo
kubectl get authorizationpolicy -n bookinfo
最後通過 istio-ingressgateway 的節點埠來存取 /status
和 /delay
,會發現 /status
在沒有 token 的情況下返回 403,而 /delay
可以正常存取。
如果我們需要驗證,當 token 中的 issuer 為 example-issuer 才能存取時,可以使用:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-policy
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- to:
- operation:
paths: ["/delay/*"]
when:
- key: request.auth.claims[iss]
values: ["example-issuer"]
AuthorizationPolicy 的規則有很多,可以通過這些規則限制不同服務的存取策略。
所以 istio 這裡一般做驗證 jwt 是否有效,或者做路由地址的策略存取,但是如果有數十個上百個路由,使用 istio 設定就會好麻煩。但是依然不是我們想要的,因為在 istio 中設定不同應用存取許可權和檢驗 token 比較繁瑣,而且業務系統大多數情況下需要給使用者單獨設定各種 API 的存取許可權。