1. 程式人生 > 其它 >Kubernetes 監控:Service Mesh 實踐 -- Istio 安全管控

Kubernetes 監控:Service Mesh 實踐 -- Istio 安全管控

除了流量管理、可觀測性之外,Istio 服務網格中還可以對網格進行安全管控。Istio 的安全功能主要分為三個部分的實現:

  • 雙向 TLS 支援
  • 基於黑白名單的訪問控制
  • 基於角色的訪問控制
  • JWT 認證支援

安全閘道器

Istio 的身份和證書管理是通過 SDS(安全發現服務)來實現的,如下圖所示:

其執行的流程如下所示:

  • 首先 Istio 提供一個 gRPC 服務來接受證書籤名請求(CSRs)
  • Envoy 通過 SDS API 傳送證書和金鑰請求
  • 在收到 SDS 請求後,istio-agent 建立私鑰和 CSR,然後將 CSR 及其憑據傳送到 Istiod 中的 CA 服務進行簽名
  • CA 驗證 CSR 中攜帶的憑據並簽署 CSR 以生成證書
  • istio-agent 通過 Envoy SDS API 將私鑰與從 Istio CA 收到的證書傳送給 Envoy
  • 上述 CSR 過程會週期性地重複,以處理證書和金鑰輪換

這的 istio-agent 並不是指的單獨的一個容器,而是指 sidecar 容器中的 pilot-agent 程序。

接下來我們可以通過 SDS 來為 Istio Ingress Gateway 配置 TLS 安全認證,配置 TLS Ingress Gateway,讓它從 Ingress Gateway 代理通過 SDS 獲取憑據。Ingress Gateway 代理和 Ingress Gateway 在同一個 Pod 中執行,監聽 Ingress Gateway 所在名稱空間中新建的 Secret。在 Ingress Gateway 中啟用 SDS 具有如下好處:

  • Ingress Gateway 無需重啟,就可以動態的新增、刪除或者更新金鑰/證書對以及根證書。
  • 無需掛載 Secret 卷,建立了 kubernetes Secret 之後,這個 Secret 就會被 Gateway 代理捕獲,並以金鑰/證書對和根證書的形式傳送給 Ingress Gateway。
  • Gateway 代理能夠監視多個金鑰/證書對,只需要為每個主機名建立 Secret 並更新 Gateway 定義就可以了

下面我們通過一個簡單的示例來說明如何啟用 Istio Ingress Gateway 的 TLS 認證。

單一主機

首先使用 OpenSSL 生成證書和金鑰,使用如下命令建立根證書和私鑰來為你的服務簽名證書:

$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt

然後使用上面的根證書和私鑰為 httpbin.example.com 域名進行簽名,首先生成證書籤名請求:

$ openssl req -out httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout httpbin.example.com.key -subj "/CN=httpbin.example.com/O=some organization"

然後使用上面的證書籤名請求來請求籤發證書:

$ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in httpbin.example.com.csr -out httpbin.example.com.crt
Signature ok
subject=CN = httpbin.example.com, O = some organization
Getting CA Private Key

簽名成功後會得到如下所示的幾個證書和金鑰:

$ ls -la
total 56
drwxr-xr-x  9 ych  staff   288 Jul 25 15:51 .
drwxr-xr-x  7 ych  staff   224 Jul 25 15:18 ..
-rw-r--r--  1 ych  staff  1180 Jul 25 15:50 example.com.crt
-rw-------  1 ych  staff  1708 Jul 25 15:50 example.com.key
-rw-r--r--  1 ych  staff  1050 Jul 25 15:51 httpbin.example.com.crt
-rw-r--r--  1 ych  staff   944 Jul 25 15:50 httpbin.example.com.csr
-rw-------  1 ych  staff  1704 Jul 25 15:50 httpbin.example.com.key

由於 Ingress Gateway 是通過監聽同一名稱空間下面的 Secret 物件來進行安全服務發現的,所以我們需要將上面生成的證書建立在 istio-system 名稱空間之下:

$ kubectl create secret tls httpbin-tls --cert=httpbin.example.com.crt --key=httpbin.example.com.key -n istio-system
secret/httpbin-tls created

然後建立 httpbin 示例應用,應用資源清單檔案如下所示:

# httpbin.yaml
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 8000
  selector:
    app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/citizenstig/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 8000

使用如下所示命令直接安裝即可:

$ kubectl apply -f <(istioctl kube-inject -f httpbin.yaml)
service/httpbin created
deployment.apps/httpbin created
$ kubectl get pods -l app=httpbin
NAME                       READY   STATUS            RESTARTS   AGE
httpbin-56db79f4f5-b2vp9   2/2     Running           0          8m35s
$ kubectl get svc -l app=httpbin
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
httpbin   ClusterIP   10.106.18.14   <none>        8000/TCP   8m47s

然後接下來建立一個 Gateway,其 servers 欄位的埠為 443,設定 credentialName 的值為上面建立的 Secret 物件 httpbin-tls,並將 TLS 模式設定為 SIMPLE。

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # 使用預設的 ingress gatewaygateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "httpbin-tls" # 必須使用上面建立的同名的Secret物件
    hosts:
    - "httpbin.example.com"
EOF

這樣就成功建立了一個 TLS 的安全閘道器,接下來為 httpbin 應用配置路由規則,建立一個 VirtualService 物件,如下所示:

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "httpbin.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF

這樣我們就可以通過 HTTPS 協議安全的訪問 httpbin 應用了,由於我們這裡的 Istio Ingressgateway 是通過 NodePort 暴露的服務,所以我們在測試的時候應該使用 443 埠對應的 nodePort 埠:

$ kubectl get svc -n istio-system -l istio=ingressgateway
NAME                   TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
istio-ingressgateway   NodePort   10.102.120.128   <none>        15020:31093/TCP,80:32193/TCP,443:30951/TCP,31400:31871/TCP,15443:31367/TCP   46d

從上面可以看出我們的閘道器安全訪問埠是 30951,閘道器地址是任意的一個節點 IP,我們這裡使用 master 節點的地址 k8s.qikqiak.com,當然還要記得將域名 httpbin.example.com 也要解析到閘道器的節點上,我們可以執行如下所示的命令:

$ curl -v -HHost:httpbin.example.com --cacert example.com.crt https://httpbin.example.com:30951/status/418
*   Trying 123.59.188.12...
* TCP_NODELAY set
* Connected to httpbin.example.com (123.59.188.12) port 30951 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: example.com.crt
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=httpbin.example.com; O=some organization
*  start date: Jul 25 07:51:14 2020 GMT
*  expire date: Jul 25 07:51:14 2021 GMT
*  common name: httpbin.example.com (matched)
*  issuer: O=example Inc.; CN=example.com
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7feb00008a00)
> GET /status/418 HTTP/2
> Host:httpbin.example.com
> User-Agent: curl/7.64.1
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!
< HTTP/2 418 
< server: istio-envoy
< date: Sat, 25 Jul 2020 08:46:02 GMT
< access-control-allow-credentials: true
< access-control-allow-origin: *
< x-more-info: http://tools.ietf.org/html/rfc2324
< content-length: 135
< x-envoy-upstream-service-time: 10
< 

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`
* Connection #0 to host httpbin.example.com left intact
* Closing connection 0

通過上面的資訊可以看到輸出了正確的結果,通過 -v 引數還可以看到 TLS 握手建立連線的完整過程。

多個主機

上面是將單個主機配置到一個 Ingress Gateway 中,同樣我們還可以把多個主機名配置到同一個 Ingress Gateway 上,例如 httpbin.example.com 和 hello.example.com 兩個主機名配置到同一個閘道器上,Ingress Gateway 會為每個 credentialName 獲取一個唯一的憑據。

同樣先部署 helloworld 示例應用,對應的資源清單檔案如下所示:

# helloworld-v1.yaml
apiVersion: v1
kind: Service
metadata:
  name: helloworld-v1
  labels:
    app: helloworld-v1
spec:
  ports:
  - name: http
    port: 5000
  selector:
    app: helloworld-v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld-v1
      version: v1
  template:
    metadata:
      labels:
        app: helloworld-v1
        version: v1
    spec:
      containers:
      - name: helloworld
        image: istio/examples-helloworld-v1
        resources:
          requests:
            cpu: "100m"
        imagePullPolicy: IfNotPresent #Always
        ports:
        - containerPort: 5000

使用如下所示命令直接安裝即可:

$ kubectl apply -f <(istioctl kube-inject -f helloworld-v1.yaml)
service/helloworld-v1 created
deployment.apps/helloworld-v1 created
$ kubectl get pods -l app=helloworld-v1
NAME                       READY   STATUS            RESTARTS   AGE
httpbin-56db79f4f5-b2vp9   2/2     Running           0          8m35s
$ kubectl get svc -l app=helloworld-v1
NAME            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
helloworld-v1   ClusterIP   10.104.3.71   <none>        5000/TCP   30s

然後同樣使用上面我們建立的根證書來對域名 hello.example.com 進行簽名,首先生成證書籤名請求:

$ openssl req -out hello.example.com.csr -newkey rsa:2048 -nodes -keyout hello.example.com.key -subj "/CN=hello.example.com/O=some organization"

然後使用上面的證書籤名請求來請求籤發證書:

$ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in hello.example.com.csr -out hello.example.com.crt
Signature ok
subject=CN = hello.example.com, O = some organization
Getting CA Private Key

同樣使用上面生成的證書和金鑰在 istio-system 名稱空間下面為 Ingress Gateway 新建一個 Secret 物件:

$ kubectl create secret tls hello-tls --cert=hello.example.com.crt --key=hello.example.com.key -n istio-system
secret/hello-tls created

然後重新定義上面的 Gateway 閘道器,其中包含了兩個 server,都開放了 443 埠。兩個 credentialName 欄位分別賦值為 httpbin-tls 和 hello-tls,設定 TLS 的 mode 為 SIMPLE 。

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # 使用 istio 預設的 ingress gateway
  servers:
  - port:
      number: 443
      name: https-httpbin
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "httpbin-tls"
    hosts:
    - "httpbin.example.com"
  - port:
      number: 443
      name: https-hello
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "hello-tls"
    hosts:
    - "hello.example.com"
EOF

由於上面我們已經為 httpbin 應用配置了路由規則,所以接下來只需要為 helloworld 應用配置流量路由即可:

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: hello
spec:
  hosts:
  - "hello.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        exact: /hello
    route:
    - destination:
        host: helloworld-v1
        port:
          number: 5000
EOF

建立完成後將域名 hello.example.com 解析到閘道器的地址,然後對其傳送 HTTPS 請求:

$ curl -v -HHost:hello.example.com --cacert example.com.crt https://hello.example.com:30951/hello
......
< HTTP/2 200 
< content-type: text/html; charset=utf-8
< content-length: 59
< server: istio-envoy
< date: Sat, 25 Jul 2020 08:49:42 GMT
< x-envoy-upstream-service-time: 313
< 
......

同樣傳送 HTTPS 請求到 httpbin.example.com,還是會看到正確的結果:

$ curl -v -HHost:httpbin.example.com --cacert example.com.crt https://httpbin.example.com:30951/status/418
......
< HTTP/2 418 
< server: istio-envoy
< date: Sat, 25 Jul 2020 08:50:53 GMT
< access-control-allow-credentials: true
< access-control-allow-origin: *
< x-more-info: http://tools.ietf.org/html/rfc2324
< content-length: 135
< x-envoy-upstream-service-time: 5
< 

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`
* Connection #0 to host httpbin.example.com left intact
* Closing connection 0

mTLS 認證

在 Istio 中主要有兩種型別的認證:

  • 對等認證(PeerAuthentication):用於服務到服務的認證,以驗證進行連線的客戶端,Istio 提供雙向 TLS 作為傳輸認證的解決方案,無需更改服務程式碼就可以啟用它。
  • 請求認證(RequestAuthentication):用於終端使用者認證,以驗證附加到請求的憑據,Istio 使用 JSON Web Token(JWT)驗證啟用請求級認證。

Istio 中的認證策略範圍主要包括如下三個方面:

  • 網格
  • 名稱空間
  • 特定服務

此外,Istio 的認證機制支援相容模式(permissive mode),以幫助我們瞭解安全策略或者服務遷移。

接下來我們來為服務網格內部的服務開啟自動 mTLS(雙向TLS認證),實現客戶端、服務端都驗證雙方身份。前面我們已經在 default 名稱空間下面部署了 httpbin 應用:

$ kubectl get pods -l app=httpbin
NAME                       READY   STATUS    RESTARTS   AGE
httpbin-68dbf6ccd9-npn6w   2/2     Running   0          11d
$ kubectl get svc -l app=httpbin
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
httpbin   ClusterIP   10.106.18.14   <none>        8000/TCP   11d

現在我們來測試下名稱空間範圍內的認證,部署 sleep 應用到 mtls 名稱空間下面:

$ kubectl create ns mtls
namespace/mtls created
$ kubectl apply -f samples/sleep/sleep.yaml -n mtls
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created
$ kubectl get pods -n mtls
NAME                    READY   STATUS    RESTARTS   AGE
sleep-f8cbf5b76-kfqzv   1/1     Running   0          95s

當應用部署完成後,以 sleep 應用為客戶端來訪問 default 名稱空間下面的 httpbin 應用,命令如下所示:

$ kubectl exec -it sleep-f8cbf5b76-kfqzv -n mtls -- curl http://httpbin.default:8000/ip
{
  "origin": "127.0.0.1"
}

可以看到能夠正常訪問,這是因為通訊的雙方沒有任何的 TLS 認證方式,所以預設情況下是可以訪問的。然後我們建立一個對等認證的策略:

# mtls-demo.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: mtls-demo
  namespace: default  # 去掉namespace就是全域性的,加上就是名稱空間級別的
spec:
  selector:  # 如果配置了selector,則只會對下面匹配的服務生效(針對特定的服務),如果您沒有為 selector 欄位提供值,則 Istio 會將策略與策略儲存範圍內的所有工作負載進行匹配
    matchLabels:
      app: httpbin
  mtls:
    mode: PERMISSIVE  # 相容模式:可以同時使用明文和加密的方式訪問
    # mode: STRICT   # 嚴格模式:雙方必須使用 mTLS 去訪問 
    # DISABLE:禁用雙向 TLS,從安全形度來看,除非您提供自己的安全解決方案,否則請勿使用此模式

裡我們建立了一個基於 default 這個名稱空間的對等認證策略,通過配置 mode: PERMISSIVE 指定使用相容模式進行認證,該模式下面可以同時使用明文和加密的方式訪問服務,此外還可以通過 labelSelector 去針對特定的服務生效。直接建立上面的資源物件即可:

$ kubectl apply -f mtls-demo.yaml
peerauthentication.security.istio.io/mtls-demo created

這個時候我們重新訪問 httpbin 應用可以看到仍然可以訪問,因為我們配置的是相容的寬鬆模式:

$ kubectl exec -it sleep-f8cbf5b76-kfqzv -n mtls -- curl http://httpbin.default:8000/ip
{
  "origin": "127.0.0.1"
}

接下來我們將上面的 mode 改成嚴格模式 STRICT,重新更新後,再次訪問 httpbin 應用:

$ kubectl exec -it sleep-f8cbf5b76-kfqzv -n mtls -- curl http://httpbin.default:8000/ip
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56

可以看到請求失敗了,證明我們配置的嚴格模式生效了。那麼正確訪問 httpbin 服務的方式是怎樣的呢?其實也非常簡單,只需要將我們的客戶端 sleep 應用加入到網格中來即可:

$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n mtls
serviceaccount/sleep unchanged
service/sleep unchanged
deployment.apps/sleep configured
$ kubectl get pods -n mtls
NAME                     READY   STATUS     RESTARTS   AGE
sleep-776fd7c979-jvpbt   2/2     Running    0          15s
$ kubectl exec -it sleep-776fd7c979-jvpbt -c sleep -n mtls -- curl http://httpbin.default:8000/ip
{
  "origin": "127.0.0.1"
}

我們只是在網格中建立了一個對等認證策略的資源物件,就完成了 mTLS 認證,這是因為 Istio 已經實現了自動的 mTLS,會幫我們自動完成證書和金鑰的管理,直接注入即可。

基於 JWT 的認證和授權

JWT(JSON Web Token)是一種多方傳遞可信 JSON 資料的方案,一個 JWT token 由.分隔的三部分組成:{Header}.{Payload}.{Signature},其中 Header 是 Base64 編碼的 JSON 資料,包含令牌型別、簽名演算法以及祕鑰 ID 等資訊;Payload 是需要傳遞的 claims 資料,也是 Base64 編碼的 JSON 資料,其中有些欄位是 JWT 標準已有的欄位如:exp、iat、iss、sub 等,也可以根據需求新增自定義欄位;Signature 是對前兩部分的簽名,防止資料被篡改,以此確保 token 資訊是可信的。

Istio 中驗籤所需公鑰由 RequestAuthentication 請求認證資源的 jwks 配置提供。JWT 授權則是對終端使用者的訪問控制,比如某個內部服務需要管理員才能夠訪問,這時候就需要驗證終端使用者的角色是否為管理員,可以在 JWT claims 中帶有管理員角色資訊,然後在授權策略中對該角色進行授權。

要使用 JWT 授權的當然就需要有效的 JWT 終端身份認證,所以在使用 JWT 授權前首先要為服務新增終端身份認證即 RequestAuthentication。

Request 認證策略指定驗證 JWT 所需的值,這些值包括:

  • token 在請求中的位置
  • 請求的 issuer
  • 公共 JSON Web Key Set(JWKS)

Istio 會根據 request 認證策略中的規則檢查提供的令牌(如果已提供),並拒絕令牌無效的請求。當請求不帶有令牌時,預設情況下將接受它們。要拒絕沒有令牌的請求,請提供授權規則。

現在我們來驗證基於 JWT 的認證功能,同樣使用 sleep 和 httpbin 應用為例,將 sleep 部署到名為 jwtest 的名稱空間:

$ kubectl create ns jwtest
namespace/jwtest created
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n jwtest
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created
$ kubectl get pods -n jwtest
NAME                     READY   STATUS    RESTARTS   AGE
sleep-776fd7c979-7jrv2   2/2     Running   0          76s

現在預設情況下我們是可以正常訪問 httpbin 應用的:

$ kubectl exec -it sleep-776fd7c979-7jrv2 -c sleep -n jwtest -- curl http://httpbin.default:8000/ip
{
  "origin": "127.0.0.1"
}

然後我們建立一個如下所示的請求認證物件:

# jwt-demo.yaml
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication  # 請求認證
metadata:
  name: jwt-demo
  namespace: default
spec:
  selector:  # 針對特定的服務
    matchLabels:
      app: httpbin
  jwtRules:
  - issuer: "[email protected]"
    jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.6/security/tools/jwt/samples/jwks.json"
    # jwks
    # fromHeaders
    # fromParams

其中最重要的就是 jwtRules 屬性下面的配置,我們這裡通過 jwksUri 來指定所需的認證資訊,直接建立:

$ kubectl apply -f jwt-demo.yaml
requestauthentication.security.istio.io/jwt-demo created

這個請求認證策略使得 httpbin 應用接收 Issuer 為 [email protected] 的 JWT 令牌。比如我們通過一個無效的 Token 來訪問應用:

$ kubectl exec -it sleep-776fd7c979-7jrv2 -c sleep -n jwtest -- curl http://httpbin.default:8000/headers -s -o /dev/null -H "Authorization: Bearer invalidToken" -w "%{http_code}\n"
401

因為我們指定了 Token,所以就需要使用我們配置的 JWT Token 來進行認證。如果我們不帶上 Token,則可以正常訪問,這是因為我們並沒有配置任何授權策略:

$ kubectl exec -it sleep-776fd7c979-7jrv2 -c sleep -n jwtest -- curl http://httpbin.default:8000/headers -s -o /dev/null -w "%{http_code}\n"
200

接下來我們針對 httpbin 應用配置一個授權策略,要求所有發往 httpbin 應用的請求都要包含一個將 requestPrincipal 設定為 [email protected]/[email protected] 的有效 JWT。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy  # 授權策略
metadata:
  name: auth-policy-demo
  namespace: default
spec:
  selector:
    matchLabels:
      app: httpbin
  action: ALLOW  # ALLOW、DENY
  rules:
  - from:
    - source:
        requestPrincipals: ["[email protected]/[email protected]"]

Istio 會使用 / 連線 JWT 的 iss 和 sub 來組成 requestPrincipal 欄位,比如我這裡的 iss 和 sub 都為 [email protected],這樣 Istio 生成的 requestPrincipal 屬性值為 [email protected]/[email protected],我們可以通過如下命令獲取測試的 Token 以及 JSON 資訊:

$ JWT_JSON=$(curl https://raw.githubusercontent.com/istio/istio/release-1.6/security/tools/jwt/samples/demo.jwt -s) && echo $JWT_JSON | cut -d '.' -f2 - | base64 --decode -
{"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"[email protected]","sub":"[email protected]"}
$ TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.6/security/tools/jwt/samples/demo.jwt -s)

直接建立上面的認證策略:

$ kubectl apply -f auth-policy-demo.yaml
authorizationpolicy.security.istio.io/auth-policy-demo created

建立完成後我們使用上面的有效的 TOKEN 來訪問 httpbin 應用:

$ kubectl exec -it sleep-776fd7c979-7jrv2 -c sleep -n jwtest -- curl "http://httpbin.default:8000/headers" -s -o /dev/null -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n"
200

可以看到可以正常訪問,我們重新使用沒有 JWT Token 的 Header 來請求 httpbin 應用:

istio-1.6.1]# kubectl exec -it sleep-776fd7c979-7jrv2 -c sleep -n jwtest -- curl "http://httpbin.default:8000/headers" -s -o /dev/null -w "%{http_code}\n"
403

可以看到是 403 錯誤,這是因為當前的請求不符合上面我們配置的授權策略。關於安全相關的更多資訊或者資源物件屬性欄位可以檢視官方文件:https://istio.io/latest/zh/docs/concepts/security/。