安全閘道

控制入口流量任務說明如何配置入口閘道,以將 HTTP 服務公開給外部流量。此任務說明如何使用簡單或相互 TLS 公開安全 HTTPS 服務。

開始之前

  • 請依照安裝指南中的說明設定 Istio。

  • 啟動 httpbin 範例

    壓縮
    $ kubectl apply -f @samples/httpbin/httpbin.yaml@
    
  • 對於 macOS 使用者,請確認您使用的 curl 是使用 LibreSSL 函式庫編譯的

    $ curl --version | grep LibreSSL
    curl 7.54.0 (x86_64-apple-darwin17.0) libcurl/7.54.0 LibreSSL/2.0.20 zlib/1.2.11 nghttp2/1.24.0
    

    如果先前的命令輸出如圖所示的 LibreSSL 版本,則您的 curl 命令應能正確執行此任務中的指示。否則,請嘗試使用不同的 curl 實作,例如在 Linux 機器上。

產生用戶端和伺服器憑證及金鑰

此任務需要幾組憑證和金鑰,這些憑證和金鑰將在以下範例中使用。您可以使用您偏好的工具來建立它們,或使用以下命令,以 openssl 來產生它們。

  1. 建立根憑證和私密金鑰,以簽署您服務的憑證

    $ mkdir example_certs1
    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs1/example.com.key -out example_certs1/example.com.crt
    
  2. httpbin.example.com 產生憑證和私密金鑰

    $ openssl req -out example_certs1/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
    $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 0 -in example_certs1/httpbin.example.com.csr -out example_certs1/httpbin.example.com.crt
    
  3. 建立第二組相同種類的憑證和金鑰

    $ mkdir example_certs2
    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs2/example.com.key -out example_certs2/example.com.crt
    $ openssl req -out example_certs2/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs2/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
    $ openssl x509 -req -sha256 -days 365 -CA example_certs2/example.com.crt -CAkey example_certs2/example.com.key -set_serial 0 -in example_certs2/httpbin.example.com.csr -out example_certs2/httpbin.example.com.crt
    
  4. helloworld.example.com 產生憑證和私密金鑰

    $ openssl req -out example_certs1/helloworld.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/helloworld.example.com.key -subj "/CN=helloworld.example.com/O=helloworld organization"
    $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/helloworld.example.com.csr -out example_certs1/helloworld.example.com.crt
    
  5. 產生用戶端憑證和私密金鑰

    $ openssl req -out example_certs1/client.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/client.example.com.key -subj "/CN=client.example.com/O=client organization"
    $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/client.example.com.csr -out example_certs1/client.example.com.crt
    

為單一主機配置 TLS 入口閘道

  1. 為入口閘道建立一個密鑰

    $ kubectl create -n istio-system secret tls httpbin-credential \
      --key=example_certs1/httpbin.example.com.key \
      --cert=example_certs1/httpbin.example.com.crt
    
  2. 設定入口閘道

首先,定義一個閘道,其中包含 port 443 的 servers: 區段,並為 credentialName 指定值為 httpbin-credential。這些值與密鑰的名稱相同。TLS 模式的值應為 SIMPLE

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: httpbin-credential # must be the same as secret
    hosts:
    - httpbin.example.com
EOF

接下來,透過定義對應的虛擬服務,設定閘道的入口流量路由

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
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

最後,按照這些指示設定 INGRESS_HOSTSECURE_INGRESS_PORT 變數,以存取閘道。

  1. 傳送 HTTPS 請求以透過 HTTPS 存取 httpbin 服務

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    I'm a teapot!
    ...
    

    httpbin 服務將回傳 418 I’m a Teapot 代碼。

  2. 透過刪除閘道的密鑰,然後使用不同的憑證和金鑰重新建立它,來變更閘道的憑證

    $ kubectl -n istio-system delete secret httpbin-credential
    $ kubectl create -n istio-system secret tls httpbin-credential \
      --key=example_certs2/httpbin.example.com.key \
      --cert=example_certs2/httpbin.example.com.crt
    
  3. 使用新的憑證鏈,以 curl 存取 httpbin 服務

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs2/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    I'm a teapot!
    ...
    
  4. 如果您嘗試使用先前的憑證鏈存取 httpbin,則現在嘗試會失敗

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    * 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 (OUT), TLS alert, Server hello (2):
    * curl: (35) error:04FFF06A:rsa routines:CRYPTO_internal:block type is not 01
    

為多個主機配置 TLS 入口閘道

您可以為多個主機設定入口閘道,例如 httpbin.example.comhelloworld.example.com。入口閘道會設定與每個主機對應的唯一憑證。

  1. 透過刪除並使用原始憑證和金鑰重新建立密鑰,從先前的範例還原 httpbin 憑證

    $ kubectl -n istio-system delete secret httpbin-credential
    $ kubectl create -n istio-system secret tls httpbin-credential \
      --key=example_certs1/httpbin.example.com.key \
      --cert=example_certs1/httpbin.example.com.crt
    
  2. 啟動 helloworld-v1 範例

    壓縮壓縮
    $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l service=helloworld
    $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l version=v1
    
  3. 建立 helloworld-credential 密鑰

    $ kubectl create -n istio-system secret tls helloworld-credential \
      --key=example_certs1/helloworld.example.com.key \
      --cert=example_certs1/helloworld.example.com.crt
    
  4. 使用主機 httpbin.example.comhelloworld.example.com 設定入口閘道

定義一個閘道,其中包含兩個 port 443 的 server 區段。將每個連接埠上的 credentialName 值分別設定為 httpbin-credentialhelloworld-credential。將 TLS 模式設定為 SIMPLE

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https-httpbin
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: httpbin-credential
    hosts:
    - httpbin.example.com
  - port:
      number: 443
      name: https-helloworld
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: helloworld-credential
    hosts:
    - helloworld.example.com
EOF

透過定義對應的虛擬服務,設定閘道的流量路由。

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

    $ curl -v -HHost:helloworld.example.com --resolve "helloworld.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt "https://helloworld.example.com:$SECURE_INGRESS_PORT/hello"
    ...
    HTTP/2 200
    ...
    
  2. 傳送 HTTPS 請求至 httpbin.example.com,並仍然收到 HTTP 418 回應

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    server: istio-envoy
    ...
    

配置相互 TLS 入口閘道

您可以擴充閘道的定義以支援雙向 TLS

  1. 透過刪除入口閘道的密鑰並建立新的密鑰來變更其憑證。伺服器會使用 CA 憑證來驗證其用戶端,而我們必須使用金鑰 ca.crt 來保留 CA 憑證。

    $ kubectl -n istio-system delete secret httpbin-credential
    $ kubectl create -n istio-system secret generic httpbin-credential \
      --from-file=tls.key=example_certs1/httpbin.example.com.key \
      --from-file=tls.crt=example_certs1/httpbin.example.com.crt \
      --from-file=ca.crt=example_certs1/example.com.crt
    
  2. 設定入口閘道

變更閘道的定義,將 TLS 模式設定為 MUTUAL

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: MUTUAL
      credentialName: httpbin-credential # must be the same as secret
    hosts:
    - httpbin.example.com
EOF
  1. 嘗試使用先前的方法傳送 HTTPS 請求,並查看其失敗的方式

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    * TLSv1.3 (OUT), TLS handshake, Client hello (1):
    * TLSv1.3 (IN), TLS handshake, Server hello (2):
    * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
    * TLSv1.3 (IN), TLS handshake, Request CERT (13):
    * TLSv1.3 (IN), TLS handshake, Certificate (11):
    * TLSv1.3 (IN), TLS handshake, CERT verify (15):
    * TLSv1.3 (IN), TLS handshake, Finished (20):
    * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
    * TLSv1.3 (OUT), TLS handshake, Certificate (11):
    * TLSv1.3 (OUT), TLS handshake, Finished (20):
    * TLSv1.3 (IN), TLS alert, unknown (628):
    * OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0
    
  2. 將用戶端憑證和私密金鑰傳遞至 curl,然後重新傳送請求。使用 --cert 旗標傳遞您的用戶端憑證,並使用 --key 旗標將您的私密金鑰傳遞至 curl

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt --cert example_certs1/client.example.com.crt --key example_certs1/client.example.com.key \
      "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    server: istio-envoy
    ...
    I'm a teapot!
    ...
    

更多資訊

金鑰格式

Istio 支援讀取幾種不同的密鑰格式,以支援與各種工具(例如 cert-manager)的整合

  • 具有金鑰 tls.keytls.crt 的 TLS 密鑰,如上所述。對於雙向 TLS,可以使用 ca.crt 金鑰。
  • 具有金鑰 keycert 的通用密鑰。對於雙向 TLS,可以使用 cacert 金鑰。
  • 具有金鑰 keycert 的通用密鑰。對於雙向 TLS,則使用名為 <secret>-cacert 的單獨通用密鑰,並帶有 cacert 金鑰。例如,httpbin-credential 具有 keycert,而 httpbin-credential-cacert 則具有 cacert
  • cacert 金鑰值可以是包含串連的個別 CA 憑證的 CA 套件。

SNI 路由

HTTPS Gateway 將在轉送請求之前,針對其設定的主機執行 SNI 比對,這可能會導致某些請求失敗。如需詳細資訊,請參閱設定 SNI 路由

疑難排解

  • 檢查 INGRESS_HOSTSECURE_INGRESS_PORT 環境變數的值。請根據下列命令的輸出,確認它們具有有效的值

    $ kubectl get svc -n istio-system
    $ echo "INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT"
    
  • 請確認 INGRESS_HOST 的值為 IP 位址。在某些雲端平台(例如 AWS)上,您可能會收到網域名稱。此任務預期使用 IP 位址,因此您需要使用類似以下的命令轉換它

    $ nslookup ab52747ba608744d8afd530ffd975cbf-330887905.us-east-1.elb.amazonaws.com
    $ export INGRESS_HOST=3.225.207.109
    
  • 檢查閘道控制器的記錄檔,是否有錯誤訊息

    $ kubectl logs -n istio-system <gateway-service-pod>
    
  • 如果使用 macOS,請確認您使用的 curl 是使用 LibreSSL 函式庫編譯的,如開始之前章節所述。

  • 確認密鑰已在 istio-system 命名空間中成功建立

    $ kubectl -n istio-system get secrets
    

    httpbin-credentialhelloworld-credential 應顯示在密鑰清單中。

  • 檢查記錄檔,以確認入口閘道代理程式已將金鑰/憑證組推送至入口閘道

    $ kubectl logs -n istio-system <gateway-service-pod>
    

    記錄檔應顯示已新增 httpbin-credential 密鑰。如果使用雙向 TLS,則也應顯示 httpbin-credential-cacert 密鑰。驗證記錄檔顯示閘道代理程式從入口閘道收到 SDS 請求,資源的名稱為 httpbin-credential,並且入口閘道已取得金鑰/憑證組。如果使用雙向 TLS,記錄檔應顯示金鑰/憑證已傳送至入口閘道,閘道代理程式收到具有 httpbin-credential-cacert 資源名稱的 SDS 請求,並且入口閘道已取得根憑證。

清除

  1. 刪除閘道設定和路由
$ kubectl delete gateway mygateway
$ kubectl delete virtualservice httpbin helloworld
  1. 刪除密鑰、憑證和金鑰

    $ kubectl delete -n istio-system secret httpbin-credential helloworld-credential
    $ rm -rf ./example_certs1 ./example_certs2
    
  2. 關閉 httpbinhelloworld 服務

    $ kubectl delete -f samples/httpbin/httpbin.yaml
    $ kubectl delete deployment helloworld-v1
    $ kubectl delete service helloworld
    
此資訊是否有用?
您有任何改進建議嗎?

感謝您的回饋!