成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Istio 安全基礎,減輕針對你的數據、端點、通信和平臺的內外威脅

運維
Istio 為微服務提供了無侵入,可插拔的安全框架。應用不需要修改代碼,就可以利用 Istio 提供的雙向 TLS 認證實現服務身份認證,并基于服務身份信息提供細粒度的訪問控制。

安全是一個非常重要的話題,但也是平時容易被忽略的一個話題,我們在開發應用的時候,往往會忽略安全,但是當應用上線后,安全問題就會暴露出來,這時候就會造成很大的損失。Istio 通過在服務之間注入 Sidecar 代理,來實現對服務之間的流量進行控制和監控,從而實現服務之間的安全通信。

接下來我們將從證書管理、認證、授權等幾個方面來學習 Istio 的安全機制。

安全概述

將單一應用程序拆分為微服務可提供各種好處,包括更好的靈活性、可伸縮性以及服務復用的能力。但是,微服務也有特殊的安全需求:

  • 為了抵御中間人攻擊,需要流量加密。
  • 為了提供靈活的服務訪問控制,需要雙向 TLS 和細粒度的訪問策略。
  • 要確定誰在什么時候做了什么,需要審計工具。

Istio 嘗試提供全面的安全解決方案來解決所有這些問題,Istio 安全性可以減輕針對你的數據、端點、通信和平臺的內外威脅。

安全概述

Istio 為微服務提供了無侵入,可插拔的安全框架。應用不需要修改代碼,就可以利用 Istio 提供的雙向 TLS 認證實現服務身份認證,并基于服務身份信息提供細粒度的訪問控制。Istio 安全的高層架構如下圖所示:

安全架構

上圖展示了 Istio 中的服務認證和授權兩部分。服務認證是通過控制面和數據面一起實現的:

  • 控制面:Istiod 中實現了一個 CA (Certificate Authority,證書機構) 服務器。該 CA 服務器負責為網格中的各個服務簽發證書,并將證書分發給數據面的各個服務的邊車代理。
  • 數據面:在網格中的服務相互之間發起 plain HTTP/TCP 通信時,和服務同一個 pod 中的邊車代理會攔截服務請求,采用證書和對端服務的邊車代理進行雙向 TLS 認證并建立一個 TLS 連接,使用該 TLS 連接來在網絡中傳輸數據。

Istio 的授權功能為網格中的工作負載提供網格、命名空間和工作負載級別的訪問控制,這種控制層級提供了許多優點:

  • 工作負載到工作負載以及最終用戶到工作負載的授權。
  • 一個簡單的 API:它包括一個單獨的并且很容易使用和維護的 AuthorizationPolicy CRD。
  • 靈活的語義:運維人員可以在 Istio 屬性上定義自定義條件,并使用 DENY 和 ALLOW 動作。
  • 高性能:Istio 授權是在 Envoy 本地強制執行的。
  • 高兼容性:原生支持 HTTP、HTTPS 和 HTTP2,以及任意普通 TCP 協議。

授權策略對服務器端 Envoy 代理的入站流量實施訪問控制。每個 Envoy 代理都運行一個授權引擎,該引擎在運行時授權請求。當請求到達代理時,授權引擎根據當前授權策略評估請求上下文,并返回授權結果 ALLOW 或 DENY。運維人員可以通過 YAML 資源清單文件來指定 Istio 授權策略。

授權策略

證書簽發流程

默認情況下,Istio CA 生成自簽名根證書和密鑰,并使用它們來簽署工作負載證書。為了保護根 CA 密鑰,我們應該使用在安全機器上離線運行的根 CA(比如使用 Hashicorp Vault 進行管理),并使用根 CA 向每個集群中運行的 Istio CA 頒發中間證書。Istio CA 可以使用管理員指定的證書和密鑰對工作負載證書進行簽名,并將管理員指定的根證書作為信任根分發給工作負載。

CA

簽發證書

當我們有了 Istio CA 根證書后就可以使用它來簽發工作負載證書了,那么整個的證書簽發流程又是怎樣的呢?如下圖所示:

證書簽發流程

  • Envoy 向 pilot-agent 發起一個 SDS (Secret Discovery Service) 請求(啟動的時候),要求獲取自己的證書和私鑰。
  • pilot-agent 生成私鑰和 CSR 證書簽名請求,向 Istiod 發送證書簽發請求,請求中包含 CSR 和該 Pod 中服務的身份信息。
  • Istiod 提供 gRPC 服務以接受證書簽名請求,根據請求中服務的身份信息(如果是 Kubernetes 則使用 Service Account)為其簽發證書,將證書返回給 pilot-agent。
  • pilot-agent 將證書和私鑰通過 SDS 接口返回給 Envoy。
  • pilot-agent 還會監控工作負載證書的過期時間,上述過程會定期重復進行證書和密鑰輪換。

這個流程和我們自己用手動方式去進行證書簽發是一樣的,所以我們需要先了解下證書簽發的流程。Istio CA 根證書就是一個證書頒發機構,平時如果要為我們自己的網站申請 HTTPS 證書我們也是去一些正規的 CA 機構進行申請,而這個申請的信息就是生成的 CSR 證書簽名請求,然后我們將這個 CSR 和身份信息提交給 CA 機構,CA 機構根據這些信息為我們簽發證書,然后我們就可以使用這個證書了,只是我們這里在不同的組件上來執行這些操作,整體流程是一樣的。

身份認證

另外要通過服務證書來實現網格中服務的身份認證,必須首先確保服務從控制面獲取自身證書的流程是安全的。Istio 通過 Istiod 和 pilog-agent 之間的 gRPC 通道傳遞 CSR 和證書,因此在這兩個組件進行通信時,雙方需要先驗證對方的身份,以避免惡意第三方偽造 CSR 請求或者假冒 Istiod CA 服務器。Istio 中主要包含下面兩種認證方式:

  • Istiod 身份認證
  • Istiod 采用其內置的 CA 服務器為自身簽發一個服務器證書,并采用該服務器證書對外提供基于 TLS 的 gPRC 服務。
  • Istiod 調用 Kubernetes APIServer 生成一個名為 istio-ca-root-cert 的 ConfigMap 對象, 在該 ConfigMap 中放入了 Istiod 的 CA 根證書。
  • 該 ConfigMap 被 Mount 到 istio-proxy 容器中,被 pilot-agent 用于驗證 Istiod 的服務器證書。
  • 在 pilot-agent 和 Istiod 建立 gRPC 連接時,pilot-agent 采用標準的 TLS 服務器認證流程對 Istiod 的服務器證書進行認證。
  • pilot-agent 身份認證
  • 在 Kubernetes 中可以為每一個 Pod 關聯一個 ServiceAccount,以表明該 Pod 中運行的服務的身份信息。

  • Kubernetes 會為該 ServiceAccount 生成一個 jwt token,并將該 token 通過 secret 加載到 pod 中的一個文件。

  • pilot-agent 在向 Istiod 發送 CSR 時,將其所在 Pod 的 service account token 也隨請求發送給 Istiod。

  • Istiod 調用 Kube-apiserver 接口驗證請求中附帶的 service account token,以確認請求證書的服務身份是否合法。

這里需要注意的是不同版本的 Kubernetes 集群下面的 ServiceAccount Token 生成方式并不一樣,但最終都是通過改 Token 來進行身份認證的。

  • 1.20(含 1.20)之前的版本,在創建 sa 時會自動創建一個 secret,然后這個會把這個 secret 通過投射卷掛載到 pod 里,該 secret 里面包含的 token 是永久有效的。
  • 1.21~1.23 版本,在創建 sa 時也會自動創建 secret,但是在 pod 里并不會使用 secret 里的 token,而是由 kubelet 到 TokenRequest API 去申請一個 token,該 token 默認有效期為一年,但是 pod 每一個小時會更新一次 token。
  • 1.24 版本及以上,在創建 sa 時不再自動創建 secret 了,只保留由 kubelet 到 TokenRequest API 去申請 token。

認證

Istio 提供兩種類型的認證用于管控網格服務間的雙向 TLS 和終端用戶的身份認證。:

  • 對等認證:用于服務到服務的認證,以驗證建立連接的客戶端。Istio 提供雙向 TLS 作為傳輸認證的全棧解決方案,無需更改服務代碼就可以啟用它。這個解決方案:
  • 為每個服務提供代表其角色的強大身份,以實現跨集群和云的互操作性。
  • 確保服務間通信的安全。
  • 提供密鑰管理系統,以自動進行密鑰和證書的生成、分發和輪換。
  • 請求認證:用于終端用戶認證,以驗證附加到請求的憑據。Istio 使用 JSON Web Token(JWT)驗證啟用請求級認證,并使用自定義認證實現或任何 OpenID Connect 的認證實現來簡化的開發人員體驗。

對等認證

下面我們來創建幾個示例服務來對認證配置進行測試。這里我們將在 foo 和 bar 命名空間下各自創建帶有 Envoy 代理(Sidecar)的 httpbin 和 sleep 服務。還將在 legacy 命名空間下創建不帶 Envoy 代理(Sidecar)的 httpbin 和 sleep 服務:

$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
$ kubectl create ns bar
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n bar
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n bar
$ kubectl create ns legacy
$ kubectl apply -f samples/httpbin/httpbin.yaml -n legacy
$ kubectl apply -f samples/sleep/sleep.yaml -n legacy

現在可以在 foo、bar 或 legacy 三個命名空間下的任意 sleep Pod 中使用 curl 向 httpbin.foo、httpbin.bar 或 httpbin.legacy 發送 HTTP 請求來驗證部署結果,所有請求都應該成功并返回 HTTP 200。

例如驗證 sleep.bar 到 httpbin.foo 可達性如下:

$ kubectl exec "$(kubectl get pod -l app=sleep -n bar -o jsnotallow={.items..metadata.name})" -c sleep -n bar -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
200

也可以使用一行指令檢查所有可能的組合:

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsnotallow={.items..metadata.name})" -c sleep -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200

這樣保證了我們的服務之間是可以互相訪問的,接下來我們就來看下如何對服務進行認證。

默認情況下,在 Istio 網格內部的服務之間的所有流量都是通過雙向 TLS 進行加密的,不需要做額外的操作,當使用雙向 TLS 時,代理會將 X-Forwarded-Client-Cert 這個 Header 頭注入到后端的上游請求,這個頭信息的存在就是啟用雙向 TLS 的證據。例如:

$ kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsnotallow={.items..metadata.name})" -c sleep -n foo -- curl -s http://httpbin.foo:8000/headers -s
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.foo:8000",
    "User-Agent": "curl/7.81.0-DEV",
    "X-B3-Parentspanid": "a59be7609a15c41f",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "034af52cfc2286b4",
    "X-B3-Traceid": "eb07847c5c14c17fa59be7609a15c41f",
    "X-Envoy-Attempt-Count": "1",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=bb907e90c93bc3f1dd22763f952746e7d2b8c5ad7903ecbcc64324f3b5e55179;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/sleep"
  }
}

可以看到上面我們的請求中包含了 X-Forwarded-Client-Cert 這個 Header 頭,這就是啟用雙向 TLS 的證據,得到的這個值是一個 spiffe:// 開頭的字符串,這個字符串就是 SPIFFE ID,這個 SPIFFE ID 就是用來表示服務的身份的,后面我們會詳細介紹 SPIFFE。

零信任架構下,需要嚴格區分工作負載的識別和信任,而簽發 X.509 證書是推薦的一種認證方式,在 Kubernetes 集群中,服務間是通過 DNS 名稱互相訪問的,而網絡流量可能被 DNS 欺騙、BGP/路由劫持、ARP 欺騙等手段劫持,為了將服務名稱(DNS 名稱)與服務身份強關聯起來,Istio 使用置于 X.509 證書中的安全命名(Secure naming)機制。

SPIFFE 是 Istio 所采用的安全命名的規范,它也是云原生定義的一種標準化的、可移植的工作負載身份規范。Secure Production Identity Framework For Everyone (SPIFFE) 是一套服務之間相互進行身份識別的標準,主要包含以下內容:

  • SPIFFE ID 標準,SPIFFE ID 是服務的唯一標識,具體實現使用 URI 資源標識符。
  • SPIFFE Verifiable Identity Document (SVID) 標準,將 SPIFFE ID 編碼到一個加密的可驗證的數據格式中。
  • 頒發與撤銷 SVID 的 API 標準。

SPIFFE ID 規定了形如 spiffe://<trust domain>/<workload identifier> 的 URI 格式,作為工作負載(Workload)的唯一標識。Istio 使用形如 spiffe://<trust_domain>/ns/<namespace>/sa/<service_account> 格式的 SPIFFE ID 作為安全命名,注入到 X.509 證書的 subjectAltName 擴展中。其中的 trust domain 參數通過 Istiod 環境變量 TRUST_DOMAIN 注入,用于在多集群環境中交互,比如我們這里就是 cluster.local,所以其實最終在 Envoy 的配置中可以看到匹配證書的 subjectAltName 值也是這個格式:

{
  "combined_validation_context": {
    "default_validation_context": {
      "match_subject_alt_names": [
        {
          "exact": "spiffe://cluster.local/ns/istio-system/sa/istiod"
        }
      ]
    },
    "validation_context_sds_secret_config": {
      "name": "ROOTCA",
      "sds_config": {
        "api_config_source": {
          "api_type": "GRPC",
          "grpc_services": [
            {
              "envoy_grpc": {
                "cluster_name": "sds-grpc"
              }
            }
          ],
          "set_node_on_first_message_only": true,
          "transport_api_version": "V3"
        },
        "initial_fetch_timeout": "0s",
        "resource_api_version": "V3"
      }
    }
  }
}

當服務器沒有 Sidecar 時,X-Forwarded-Client-Cert 這個 Header 頭將不會存在,這意味著請求是明文的,比如我們請求 httpbin.legacy 服務:

kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsnotallow={.items..metadata.name})" -c sleep -n foo -- curl http://httpbin.legacy:8000/headers -s | grep X-Forwarded-Client-Cert

全局嚴格 mTLS 模式

事實上當 Istio 自動將代理和工作負載之間的所有流量升級到雙向 TLS 時,工作負載仍然可以接收明文流量,如果想要禁用非 mTLS 的通信流量,我們可以使用一個 PeerAuthentication 資源對象來進行配置,只需要將整個網格的對等認證策略設置為 STRICT 模式,作用域為整個網格范圍的對等認證策略不設置 selector 即可,這種認證策略必須應用于根命名空間(istiod 所在的命名空間),例如:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication # 對等認證策略
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT # STRICT 模式表示只允許 mTLS

對等認證策略指定 Istio 對目標工作負載實施的雙向 TLS 模式。支持以下模式:

  • PERMISSIVE:工作負載接受雙向 TLS 和純文本流量,也就是所謂的寬容模式。此模式在遷移因為沒有 Sidecar 而無法使用雙向 TLS 的工作負載的過程中非常有用。一旦工作負載完成 Sidecar 注入的遷移,應將模式切換為 STRICT。
  • STRICT:工作負載僅接收雙向 TLS 流量。
  • DISABLE:禁用雙向 TLS。從安全角度來看,除非提供自己的安全解決方案,否則請勿使用此模式。

這個對等認證策略將工作負載配置為僅接受 mTLS 加密的請求。由于未對 selector 字段指定值,因此該策略適用于網格中的所有工作負載。

直接應用上面這個對等認證策略后,我們再次發送請求來進行測試:

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsnotallow={.items..metadata.name})" -c sleep -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200

我們可以發現從 sleep.legacy 到 httpbin.foo 和 httpbin.bar 的請求都失敗了,其他依然是成功的,這是因為我們現在配置了 STRICT 嚴格要求使用 mTLS,由于 sleep.legacy 沒有 Envoy Sidecar,所以無法滿足這一要求,所以要訪問網格內部的工作負載是不被允許的。那為什么可以訪問 httpbin.legacy 呢?這是因為我們在 legacy 命名空間下的 httpbin 服務沒有 Envoy Sidecar,所以它不會被 Istio 管理,也就不會被強制要求使用 mTLS 了,所以我們可以直接訪問它。

命名空間級別策略

上面我們是在根命名空間(istiod 所在的命名空間)下配置的對等認證策略,這樣會影響到整個網格,如果我們只想對某個命名空間下的服務進行配置,那么我們可以使用命名空間級別的對等認證策略,該策略的規范與整個網格級別的規范相同,但是可以在 metadata 字段指定具體的命名空間的名稱。比如我們要在 foo 命名空間上啟用嚴格的雙向 TLS 對等策略,可以創建如下所示的資源對象:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: foo # 命名空間級別
spec:
  mtls:
    mode: STRICT

直接應用上面的資源對象,然后再次發送請求來進行測試:

# 首先刪除上面創建的全局對等認證策略
$ kubectl delete peerauthentication -n istio-system default
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsnotallow={.items..metadata.name})" -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200

由于這些策略只應用于命名空間 foo 中的服務,正常我們會看到只有從沒有 Sidecar 的客戶端(sleep.legacy)到有 Sidecar 的客戶端(httpbin.foo)的請求會失敗,其余都是成功的。

為每個工作負載啟用雙向 TLS

要為特定工作負載設置對等認證策略,我們就必須配置 selector 字段指定與所需工作負載匹配的標簽。比如我們只想要為 httpbin.bar 服務啟用嚴格模式的 mTLS,則可以創建如下所示的資源對象:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "httpbin"
  namespace: "bar"
spec:
  selector:
    matchLabels:
      app: httpbin # 匹配 httpbin 應用的標簽
  mtls:
    mode: STRICT

上面的資源對象將為 bar 命名空間中的 httpbin 應用啟用嚴格模式的 mTLS,其他工作負載不受影響。直接應用上面的資源對象,然后再次發送請求來進行測試:

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsnotallow={.items..metadata.name})" -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200

跟預期一樣,從 sleep.legacy 到 httpbin.bar 的請求因為同樣的原因失敗。

除此之外我們還可以為每個端口配置不同的對等認證策略,例如,以下對等認證策略要求在除 80 端口以外的所有端口上都使用雙向 TLS:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "httpbin"
  namespace: "bar"
spec:
  selector:
    matchLabels:
      app: httpbin
  mtls:
    mode: STRICT
  portLevelMtls:
    80:
      mode: DISABLE

上面的資源對象中我們配置了一個 portLevelMtls 字段,該字段用于配置端口級別的對等認證策略,這里我們配置了 80 端口的對等認證策略為 DISABLE,即禁用雙向 TLS,其他端口的對等認證策略為 STRICT,即啟用雙向 TLS,也就是說我們只允許 httpbin 應用的 80 端口接收明文流量,其他端口都必須使用雙向 TLS。直接應用上面的資源對象,然后再次發送請求來進行測試:

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsnotallow={.items..metadata.name})" -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200

可以看到現在我們從 sleep.legacy 到 httpbin.bar 的請求成功了,因為我們禁用了 80 端口的雙向 TLS,所以 sleep.legacy 可以訪問到 httpbin.bar 的服務了。

測試完成后記得刪除上面創建的對等認證策略:

$ kubectl delete peerauthentication default -n foo
$ kubectl delete peerauthentication httpbin -n bar

請求認證

Istio 的請求認證用于終端用戶認證,以驗證附加到請求的憑據。Istio 使用 JWT 驗證啟用請求級認證,并使用自定義認證實現或任何 OpenID Connect 的認證實現來進行認證簡化。

要在 Istio 中進行請求認證,可以通過一個 RequestAuthentication 資源對象來進行配置,如果請求包含無效的認證信息,它將根據配置的認證規則拒絕該請求。不包含任何認證憑證的請求將被接受,但不會有任何認證的身份。

JWK 與 JWKS 概述

Istio 使用 JWT 對終端用戶進行身份驗證,Istio 要求提供 JWKS 格式的信息,用于 JWT 簽名驗證。因此這里得先介紹一下 JWK 和 JWKS。

JWK 即 JSON Web Key,是 JWT 的秘鑰,它描述了一個加密密鑰(公鑰或私鑰)的值及其各項屬性。JWKS 描述一組 JWK 密鑰,JWKS 的 JSON 文件格式如下:

{
"keys": [
  <jwk-1>,
  <jwk-2>,
  ...
]}

Istio 使用 JWK 描述驗證 JWT 簽名所需要的信息。在使用 RSA 簽名算法時,JWK 描述的應該是用于驗證的 RSA 公鑰。一個 RSA 公鑰的 JWK 描述如下:

{
    "alg": "RS256",  # 算法「可選參數」
    "kty": "RSA",    # 密鑰類型
    "use": "sig",    # 被用于簽名「可選參數」
    "kid": "DHFxxxxx_-envvQ",  # key 的唯一 id
    "n": "xAExxxxMQ", 公鑰的指數(exponent)
    "e": "AQAB"  # 公鑰的模數(modulus)
}

那么需要如何生成 JWK 呢?我們可以使用 https://github.com/lestrrat-go/jwx 這個命令行工具,這是一個用 Go 語言開發的命令行工具,內置了對各種 JWx(JWT, JWK, JWA, JWS, JWE) 的支持。

我們可以使用下面的命令來安裝 jwx 命令行工具:

$ export GOPROXY="https://goproxy.io"
$ git clone https://github.com/lestrrat-go/jwx.git
$ cd jwx
$ make jwx
go: downloading github.com/lestrrat-go/jwx/v2 v2.0.11
go: downloading github.com/urfave/cli/v2 v2.24.4
# ......
go: downloading github.com/russross/blackfriday/v2 v2.1.0
go: downloading golang.org/x/sys v0.8.0
Installed jwx in /root/go/bin/jwx

下面我們使用 jwx 命令行工具生成一個 JWK,通過模板指定 kid 為 youdianzhishi-key:

$ jwx jwk generate --keysize 4096 --type RSA  --template '{"kid":"youdianzhishi-key"}' -o rsa.jwk
$ cat rsa.jwk
{
  "d": "AxxxwBw6Jok",
  "dp": "j3xxxuvQ",
  "dq": "zzxxxqQ",
  "e": "AQAB",
  "kid": "youdianzhishi-key",
  "kty": "RSA",
  "n": "5sxxxwV8",
  "p": "-yxxxQ",
  "q": "6zkC_xxxxKw",
  "qi": "LExxxTw"
}

然后從 rsa.jwk 中提取 JWK 公鑰:

$ jwx jwk fmt --public-key -o rsa-public.jwk rsa.jwk
$ cat rsa-public.jwk
{
  "e": "AQAB",
  "kid": "youdianzhishi-key",
  "kty": "RSA",
  "n": "5sxxxV8"
}

上面生成的 JWK 其實就是 RSA 公鑰私鑰換了一種存儲格式而已,我們可以使用下面的命令將它們轉換成 PEM 格式的公鑰和私鑰:

$ jwx jwk fmt -I json -O pem rsa.jwk
-----BEGIN PRIVATE KEY-----
MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCym3O0Ik5QGZ8i
......
-----END PRIVATE KEY-----

$ jwx jwk fmt -I json -O pem rsa-public.jwk
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsptztCJOUBmfIqSE8LR5
......
-----END PUBLIC KEY-----

接下來我們就可以使用 jwx 命令行簽發 JWT Token 并驗證其有效性了:

jwx jws sign --key rsa.jwk --alg RS256 --header '{"typ":"JWT"}' -o token.txt - <<EOF
{
  "iss": "testing@secure.istio.io",
  "sub": "cnych001",
  "iat": 1700648397,
  "exp": 1700656042,
  "name": "Yang Ming"
}
EOF

然后查看生成的 Token 文件內容:

$ cat token.txt
eyJhbGciOiJSUzI1NiIsImtpZCI6InlvdWRpYW56aGlzaGkta2V5In0......

上面生成 JWT Token 實際上是由下面的算法生成的:

base64url_encode(Header) + '.' + base64url_encode(Claims) + '.' + base64url_encode(Signature)

我們可以將該 Token 粘貼到 jwt.io 網站上來解析:

jwt

先看一下 Headers 部分,包含了一些元數據:

  • alg: 所使用的簽名算法,這里是 RSA256
  • kid: JWK 的 kid

然后是 Payload(Claims) 部分,payload 包含了這個 token 的數據信息,JWT 標準規定了一些字段,另外還可以加入一些承載額外信息的字段。

  • iss: issuer,token 是誰簽發的
  • sub: token 的主體信息,一般設置為 token 代表用戶身份的唯一 id 或唯一用戶名
  • exp: token 過期時間,Unix 時間戳格式
  • iat: token 創建時間, Unix 時間戳格式

最后看一下簽名 Signature 信息,簽名是基于 JSON Web Signature (JWS) 標準來生成的,簽名主要用于驗證 token 是否有效,是否被篡改。簽名支持很多種算法,這里使用的是 RSASHA256,具體的簽名算法如下:

RSASHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  <rsa-public-key>,
  <rsa-private-key>

最后可以使用 RSA Public Key 驗證 JWT Token 的有效性:

$ jwx jws verify --alg RS256 --key rsa-public.jwk token.txt
{
  "iss": "testing@secure.istio.io",
  "sub": "cnych001",
  "iat": 1700648397,
  "exp": 1700656042,
  "name": "Yang Ming"
}

配置 JWT 終端用戶認證

上面我們了解了 JWT、JWK、JWKS 這些概念,接下來我們來配置 Istio 的認證策略使用我們自己創建的 JWKS。

為了方便訪問,我們這里通過 Ingress 網關來暴露 httpbin.foo 服務,為其創建一個 Gateway 對象:

kubectl apply -f samples/httpbin/httpbin-gateway.yaml -n foo

可以通過如下命令獲取 HTTP 的訪問端口:

export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsnotallow='{.spec.ports[?(@.name=="http2")].nodePort}')

然后獲取集群中任意一個節點的 IP 地址:

export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsnotallow='{.items[0].status.hostIP}')

然后可以通過下面的命令來測試 httpbin.foo 服務是否可以正常訪問:

curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"

如果上面命令返回 200,則表示 httpbin.foo 服務可以正常訪問。

接下來我們就可以添加一個請求認證策略對象,該策略要求 Ingress 網關指定終端用戶的 JWT。

apiVersion: "security.istio.io/v1"
kind: RequestAuthentication
metadata:
  name: jwt-example
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
    - issuer: "testing@secure.istio.io" # 簽發者,需要和 JWT payload 中的 iss 屬性完全一致。
      # forwardOriginalToken: true
      jwks: |
        {
            "keys": [
              {
                "e": "AQAB",
                "kid": "youdianzhishi-key",
                "kty": "RSA",
                "n": "5sbkUCkQDuM3hiw9UTxmxO2wUYOT69IZje7M6O_R-ApJ3KkrhQS1C2SJelBTLgbaWhAsO4jBYOmFfWGLBA-XxrhoB9KxWCGA4EXf6fukp0ljGTYE6Th6r393jIJGDFUt8vQCjj5ivmBAQHLjwmnWiG6I93mrTQhoNHQWAde21O7yYNpg6fvjVJgRFqeAtpieA-5f2sQ8fBkefM0RFgQTqWPGfLHse5nqRWY4hG_gb23GzCo_Ti2h9wJZNuTfdK8hitahOq3eLlDVVvCu8hx-8BEs5APCj54gtqePswHeRXZi_03ccNii5CnW7Y1rHiL8LHKNHhY5tD2iZByh4YrnhkUWD-CXNqyUKx90de0R9H1ON6pqsmdEx4iAMx2xvnZ0S9NbKlk3glw_AvudjJUHa41xx7qy9OZ7QV6cB_ntwLtw513lk5Tfm-RDlVgyU-EO2jKXbOeiDpnb8kgRNBMKDqY9mgLfISW54N-LBjyVwVVHOvICWo0oPJypRgPRWD8f25wHqzjlsB8nIqJkj_e9p2c5WnAGiZWuZjm6t94IfFq9lWYUsSn-JtJvh3ATsv7ptDKFz2Ko82r1uD3446mr4I0J56C-7WOHGchlSOrDWKErwkIGxyrQ_3GEkUhkSxfArAv0bajmcMCu1_j8Eekqk7Fnm5QqytCFmFevzIJkwV8"
              }
            ]
        }

在上面的資源對象中我們通過 selector 匹配了 istio-ingressgateway 服務,因為我們要為 Ingress 網關添加請求認證,具體的請求認證規則通過 jwtRules 來進行配置,這里我們配置了一個 issuer 字段,該字段用于指定 JWT 的 Issuer 發行人,然后配置了一個 jwks 字段,該字段用于指定 JWT 的公鑰集數據,我們也可以通過 jwksUri 來指向一個公鑰集地址。對同一個 issuers(jwt 簽發者),可以設置多個公鑰,以實現 JWT 簽名密鑰的輪轉。JWT 的驗證規則是:

  • JWT 的 payload 中有 issuer 屬性,首先通過 issuer 匹配到對應的 istio 中配置的 jwks。
  • JWT 的 header 中有 kid 屬性,第二步在 jwks 的公鑰列表中,中找到 kid 相同的公鑰。
  • 使用找到的公鑰進行 JWT 簽名驗證。

配置中的 spec.selector 可以省略,這樣會直接在整個命名空間中生效,比如在 istio-system 命名空間,該配置將在全集群的所有 sidecar/ingressgateway 上生效!

默認情況下,Istio 在完成了身份驗證之后,會去掉 Authorization 請求頭再進行轉發。這將導致我們的后端服務獲取不到對應的 Payload,無法判斷終端用戶的身份。因此我們需要啟用 Istio 的 Authorization 請求頭的轉發功能,只需要在上面的資源對象中添加一個 forwardOriginalToken: true 字段即可。

直接應用上面的資源對象,然后再次發送請求來進行測試:

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
200

可以看到現在依然可以正常訪問,但是如果我們請求的時候帶上一個無效的 JWT Token,則會返回 401 錯誤:

$ curl --header "Authorization: Bearer abcdef" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
401

要想正常訪問,我們需要使用上面生成的 JWT Token 來進行訪問:

$ TOKEN=$(cat token.txt)
$ curl --header "Authorization: Bearer $TOKEN" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
200

可以看到就可以正常訪問了。

設置強制認證規則

從上面的測試可以看出 Istio 的 JWT 驗證規則,默認情況下會直接忽略不帶 Authorization 請求頭(即 JWT)的流量,因此這類流量能直接進入網格內部。通常這是沒問題的,因為沒有 Authorization 的流量即使進入到內部,也會因為無法通過 payload 判別身份而被拒絕操作。但是如果我們需要禁止不帶 JWT 的流量,那么可以通過一個 AuthorizationPolicy 對象來進行配置了。

比如拒絕任何 JWT 無效的請求,則可以創建如下 d 資源對象:

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-requests-without-authorization
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY # 拒絕
  rules:
    - from:
        - source:
            notRequestPrincipals: ["*"] # 不存在任何請求身份(Principal)的 requests

上面的資源對象中我們配置的 action: DENY 表示拒絕,然后通過 rules 字段來配置拒絕的規則,這里我們配置了一個 from 字段,該字段用于指定請求的來源,這里我們配置了一個 notRequestPrincipals 字段,該字段用于指定請求的身份,這里我們配置為 *,表示任何請求身份都不允許。

應用上面的資源對象后,重新發送沒有令牌的請求,請求失敗并返回錯誤碼 403:

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
403

如果僅希望強制要求對部分 path 的請求必須帶有 Authorization Header,可以這樣設置:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-requests-without-authorization
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY # 拒絕
  rules:
    - from:
        - source:
            notRequestPrincipals: ["*"] # 不存在任何請求身份的 requests
      # 僅強制要求如下 host/path 相關的請求,必須帶上 JWT token
      to:
        - operation:
            hosts: ["another-host.com"]
            paths: ["/headers"]

需要注意的是 RequestsAuthentication 和 AuthorizationPolicy 這兩個對象返回的錯誤碼是不同的:

  • RequestsAuthentication 驗證失敗的請求,Istio 會返回 401 狀態碼。
  • AuthorizationPolicy 驗證失敗的請求,Istio 會返回 403 狀態碼。

到這里我們就實現了對 JWT 的驗證,當然除了驗證之外,我們還需要授權,這個我們在下面的章節中來實現。

參考文檔

  • https://www.zhaohuabing.com/post/2020-05-25-istio-certificate/。
  • https://istio.io/latest/docs/concepts/security/。
  • https://github.com/YaoZengzeng/KubernetesResearch/blob/master/Istio%E5%AE%89%E5%85%A8%E6%A1%86%E6%9E%B6%E8%A7%A3%E6%9E%90.md。
責任編輯:姜華 來源: k8s技術圈
相關推薦

2021-03-08 16:59:55

數據安全勒索病毒攻擊

2021-03-12 09:47:52

數據管理

2023-06-15 14:45:29

2022-03-03 10:18:02

物聯網安全漏洞

2017-11-01 06:29:59

2018-02-23 10:07:04

2020-04-02 11:06:56

網站安全HTTPS加密

2015-12-21 10:08:53

數據中心IT安全威脅

2016-04-07 08:56:06

2023-06-13 07:17:12

2016-01-26 10:51:50

2015-12-18 10:24:08

2022-06-14 07:17:43

Wazuh開源

2011-08-19 14:48:18

2012-12-27 14:12:23

2018-07-04 13:14:35

2021-12-17 14:06:55

云計算安全工具

2020-10-26 10:37:25

邊緣計算

2019-07-02 05:54:27

網絡安全攻擊安全威脅

2011-08-19 15:04:21

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品国产色综合久久 | 一二区视频 | 色视频在线免费观看 | 国产成人99久久亚洲综合精品 | 国产成人精品免费 | 日韩无 | 波多野结衣av中文字幕 | 激情小视频 | 精品欧美一区二区三区久久久 | 天堂网中文 | 1000部精品久久久久久久久 | 日日夜夜天天综合 | 亚洲一区二区 | 久久精品免费一区二区 | 日韩日韩日韩日韩日韩日韩日韩 | 国产一区91精品张津瑜 | 欧美成人精品 | 欧洲一级毛片 | 中文字字幕一区二区三区四区五区 | av日韩精品 | 一级做受毛片免费大片 | 亚洲国产欧美国产综合一区 | 国产精品久久久久久一区二区三区 | 久草青青 | 黄色精品 | 在线观看黄色电影 | 精品国产一区二区三区久久 | 情侣酒店偷拍一区二区在线播放 | 欧美精品一区二区三区四区 在线 | 久久成人一区 | 成年视频在线观看 | 在线亚洲电影 | av网站免费看 | 国产精品高潮呻吟久久 | 青青久在线视频 | 免费的色网站 | 青青草原综合久久大伊人精品 | 国产激情精品视频 | 精品少妇一区二区三区日产乱码 | ww 255hh 在线观看 | 不卡在线视频 |