エグレス TLS オリジネーション

外部サービスへのアクセスタスクでは、サービスメッシュの外部にあるHTTPおよびHTTPSサービスに、メッシュ内のアプリケーションからアクセスする方法を示しています。このタスクで説明されているように、ServiceEntryは、外部サービスへのアクセスを制御する方法でIstioを設定するために使用されます。この例では、外部サービスへのトラフィックに対してTLSオリジネーションを実行するようにIstioを設定する方法を示します。Istioは外部サービスへのHTTPS接続を開き、元のトラフィックはHTTPのままとなります。

ユースケース

外部サイトに対してHTTP呼び出しを実行するレガシーアプリケーションを考えてみましょう。アプリケーションを運用する組織が、すべての外部トラフィックを暗号化する必要があるという新しい要件を受け取ったとします。Istioを使用すると、アプリケーションのコードを変更することなく、構成のみでこの要件を達成できます。アプリケーションは暗号化されていないHTTPリクエストを送信でき、Istioがアプリケーションのためにそれらを暗号化します。

ソースから暗号化されていないHTTPリクエストを送信し、IstioにTLSアップグレードを実行させるもう1つの利点は、Istioがより良いテレメトリーを生成し、暗号化されていないリクエストに対してより多くのルーティング制御を提供できることです。

始める前に

  • インストールガイドの指示に従ってIstioをセットアップします。

  • 外部呼び出しのテストソースとして使用されるcurlサンプルを起動します。

    自動サイドカーインジェクションを有効にしている場合は、curlアプリケーションをデプロイします。

    Zip
    $ kubectl apply -f @samples/curl/curl.yaml@
    

    それ以外の場合は、curlアプリケーションをデプロイする前に、手動でサイドカーをインジェクトする必要があります。

    Zip
    $ kubectl apply -f <(istioctl kube-inject -f @samples/curl/curl.yaml@)
    

    以下の手順では、execおよびcurlを実行できる任意のポッドで実行できます。

  • 外部サービスにリクエストを送信するためのソースポッドの名前を保持するシェル変数を作成します。curlサンプルを使用した場合は、以下を実行します。

    $ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath={.items..metadata.name})
    

外部サービスへのアクセスの構成

まず、外部サービスへのアクセスタスクで示されているのと同じ手法を使用して、外部サービスedition.cnn.comへのアクセスを設定することから始めます。ただし、今回は、1つのServiceEntryを使用して、サービスへのHTTPおよびHTTPSアクセスを両方とも有効にします。

  1. edition.cnn.comへのアクセスを有効にするためのServiceEntryを作成します。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: edition-cnn-com
    spec:
      hosts:
      - edition.cnn.com
      ports:
      - number: 80
        name: http-port
        protocol: HTTP
      - number: 443
        name: https-port
        protocol: HTTPS
      resolution: DNS
    EOF
    
  2. 外部HTTPサービスにリクエストを送信します。

    $ kubectl exec "${SOURCE_POD}" -c curl -- curl -sSL -o /dev/null -D - http://edition.cnn.com/politics
    HTTP/1.1 301 Moved Permanently
    ...
    location: https://edition.cnn.com/politics
    ...
    
    HTTP/2 200
    ...
    

    出力は上記と同様になるはずです(一部の詳細は省略記号で置き換えられています)。

curl-Lフラグに注意してください。これは、curlにリダイレクトに従うように指示します。この場合、サーバーは、http://edition.cnn.com/politicsへのHTTPリクエストに対してリダイレクト応答(301 Moved Permanently)を返しました。リダイレクト応答は、クライアントに、今回はHTTPSを使用してhttps://edition.cnn.com/politicsに追加のリクエストを送信するように指示します。2番目のリクエストでは、サーバーはリクエストされたコンテンツと200 OKステータスコードを返しました。

curlコマンドはリダイレクトを透過的に処理しましたが、ここでは2つの問題があります。最初の問題は、http://edition.cnn.com/politicsのコンテンツの取得のレイテンシーを2倍にする冗長なリクエストです。2番目の問題は、URLのパス(この場合はpolitics)がクリアテキストで送信されることです。アプリケーションとedition.cnn.com間の通信を傍受する攻撃者がいる場合、攻撃者はアプリケーションがedition.cnn.comのどの特定のトピックを取得したかを知ることができます。プライバシー上の理由から、そのような開示を防ぎたい場合があります。

これらの問題は両方とも、IstioがTLSオリジネーションを実行するように構成することで解決できます。

エグレストラフィックのTLSオリジネーション

  1. 前のセクションのServiceEntryを再定義して、HTTPリクエストをポート443にリダイレクトし、TLSオリジネーションを実行するためのDestinationRuleを追加します。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: edition-cnn-com
    spec:
      hosts:
      - edition.cnn.com
      ports:
      - number: 80
        name: http-port
        protocol: HTTP
        targetPort: 443
      - number: 443
        name: https-port
        protocol: HTTPS
      resolution: DNS
    ---
    apiVersion: networking.istio.io/v1
    kind: DestinationRule
    metadata:
      name: edition-cnn-com
    spec:
      host: edition.cnn.com
      trafficPolicy:
        portLevelSettings:
        - port:
            number: 80
          tls:
            mode: SIMPLE # initiates HTTPS when accessing edition.cnn.com
    EOF
    

    上記のDestinationRuleは、ポート80のHTTPリクエストに対してTLSオリジネーションを実行し、ServiceEntryはポート80のリクエストをターゲットポート443にリダイレクトします。

  2. 前のセクションと同様に、http://edition.cnn.com/politicsにHTTPリクエストを送信します。

    $ kubectl exec "${SOURCE_POD}" -c curl -- curl -sSL -o /dev/null -D - http://edition.cnn.com/politics
    HTTP/1.1 200 OK
    ...
    

    今回は、最初で唯一の応答として200 OKを受け取ります。Istioはcurlに対してTLSオリジネーションを実行したため、元のHTTPリクエストはHTTPSとしてedition.cnn.comに転送されました。サーバーは、リダイレクトの必要なしに、コンテンツを直接返しました。クライアントとサーバー間の二重のラウンドトリップを排除し、アプリケーションがedition.cnn.compoliticsセクションを取得したという事実を明らかにすることなく、リクエストは暗号化された状態でメッシュを離れました。

    前のセクションと同じコマンドを使用したことに注意してください。外部サービスにプログラムでアクセスするアプリケーションの場合、コードを変更する必要はありません。コードを1行も変更することなく、Istioを構成することでTLSオリジネーションのメリットを得ることができます。

  3. HTTPSを使用して外部サービスにアクセスするアプリケーションは、以前と同様に動作し続けることに注意してください。

    $ kubectl exec "${SOURCE_POD}" -c curl -- curl -sSL -o /dev/null -D - https://edition.cnn.com/politics
    HTTP/2 200
    ...
    

追加のセキュリティに関する考慮事項

アプリケーションポッドとローカルホスト上のサイドカープロキシ間のトラフィックは暗号化されていないため、アプリケーションのノードに侵入できる攻撃者は、ノードのローカルネットワーク上で暗号化されていない通信を見ることができます。一部の環境では、厳格なセキュリティ要件により、ノードのローカルネットワーク上でもすべてのトラフィックを暗号化する必要がある場合があります。このような厳格な要件では、アプリケーションはHTTPS(TLS)のみを使用する必要があります。この例で説明したTLSオリジネーションでは不十分です。

また、アプリケーションによってオリジンされたHTTPSの場合でも、攻撃者はサーバー名表示(SNI)を調べることで、edition.cnn.comへのリクエストが送信されていることを知ることができます。SNIフィールドは、TLSハンドシェイク中に暗号化されていない状態で送信されます。HTTPSを使用すると、攻撃者は特定のトピックや記事を知ることを防ぐことができますが、edition.cnn.comにアクセスしていることを学習することを防ぐことはできません。

TLSオリジネーションの設定のクリーンアップ

作成したIstio構成項目を削除します。

$ kubectl delete serviceentry edition-cnn-com
$ kubectl delete destinationrule edition-cnn-com

エグレス通信のための相互TLSオリジネーション

このセクションでは、外部サービスに対してTLSオリジネーションを実行するようにサイドカーを構成する方法について説明します。今回は、相互TLSを必要とするサービスを使用します。この例は、次の設定が必要なため、かなり複雑になります。

  1. クライアントとサーバーの証明書を生成します。
  2. 相互TLSプロトコルをサポートする外部サービスをデプロイします。
  3. 手順1で作成したクレデンシャルを使用するようにクライアント(curlポッド)を構成します。

このセットアップが完了したら、TLSオリジネーションを実行するサイドカーを通過するように外部トラフィックを構成できます。

クライアントとサーバーの証明書およびキーの生成

このタスクでは、証明書とキーを生成するために好みのツールを使用できます。以下のコマンドではopensslを使用します。

  1. サービスの証明書に署名するためのルート証明書と秘密キーを作成します。

    $ 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
    
  2. my-nginx.mesh-external.svc.cluster.localの証明書と秘密キーを作成します。

    $ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization"
    $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt
    

    オプションで、宛先のSAN検証を有効にする場合は、証明書にSubjectAltNamesを追加できます。例えば

    $ cat > san.conf <<EOF
    [req]
    distinguished_name = req_distinguished_name
    req_extensions = v3_req
    x509_extensions = v3_req
    prompt = no
    [req_distinguished_name]
    countryName = US
    [v3_req]
    keyUsage = critical, digitalSignature, keyEncipherment
    extendedKeyUsage = serverAuth, clientAuth
    basicConstraints = critical, CA:FALSE
    subjectAltName = critical, @alt_names
    [alt_names]
    DNS = my-nginx.mesh-external.svc.cluster.local
    EOF
    $
    $ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:4096 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization" -config san.conf
    $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt -extfile san.conf -extensions v3_req
    
  3. クライアント証明書と秘密キーを生成します。

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

相互TLSサーバーのデプロイ

相互TLSプロトコルをサポートする実際の外部サービスをシミュレートするために、NGINXサーバーをKubernetesクラスターにデプロイしますが、Istioサービスメッシュの外部、つまりIstioサイドカープロキシインジェクションが有効になっていない名前空間で実行します。

  1. Istioメッシュの外部のサービスを表す名前空間(つまりmesh-external)を作成します。自動サイドカーインジェクションが有効化されていないため、サイドカープロキシは、この名前空間のポッドに自動的にインジェクトされないことに注意してください。

    $ kubectl create namespace mesh-external
    
  2. サーバーとCAの証明書を保持するKubernetes Secretsを作成します。

    $ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt
    $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
    
  3. NGINXサーバーの構成ファイルを作成します。

    $ cat <<\EOF > ./nginx.conf
    events {
    }
    
    http {
      log_format main '$remote_addr - $remote_user [$time_local]  $status '
      '"$request" $body_bytes_sent "$http_referer" '
      '"$http_user_agent" "$http_x_forwarded_for"';
      access_log /var/log/nginx/access.log main;
      error_log  /var/log/nginx/error.log;
    
      server {
        listen 443 ssl;
    
        root /usr/share/nginx/html;
        index index.html;
    
        server_name my-nginx.mesh-external.svc.cluster.local;
        ssl_certificate /etc/nginx-server-certs/tls.crt;
        ssl_certificate_key /etc/nginx-server-certs/tls.key;
        ssl_client_certificate /etc/nginx-ca-certs/example.com.crt;
        ssl_verify_client on;
      }
    }
    EOF
    
  4. NGINXサーバーの構成を保持するKubernetes ConfigMapを作成します。

    $ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
    
  5. NGINXサーバーをデプロイします。

    $ kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Service
    metadata:
      name: my-nginx
      namespace: mesh-external
      labels:
        run: my-nginx
      annotations:
        "networking.istio.io/exportTo": "." # simulate an external service by not exporting outside this namespace
    spec:
      ports:
      - port: 443
        protocol: TCP
      selector:
        run: my-nginx
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-nginx
      namespace: mesh-external
    spec:
      selector:
        matchLabels:
          run: my-nginx
      replicas: 1
      template:
        metadata:
          labels:
            run: my-nginx
        spec:
          containers:
          - name: my-nginx
            image: nginx
            ports:
            - containerPort: 443
            volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx
              readOnly: true
            - name: nginx-server-certs
              mountPath: /etc/nginx-server-certs
              readOnly: true
            - name: nginx-ca-certs
              mountPath: /etc/nginx-ca-certs
              readOnly: true
          volumes:
          - name: nginx-config
            configMap:
              name: nginx-configmap
          - name: nginx-server-certs
            secret:
              secretName: nginx-server-certs
          - name: nginx-ca-certs
            secret:
              secretName: nginx-ca-certs
    EOF
    

クライアント(curlポッド)の設定

  1. クライアントの証明書を保持するKubernetes Secretsを作成します。

    $ kubectl create secret generic client-credential --from-file=tls.key=client.example.com.key \
      --from-file=tls.crt=client.example.com.crt --from-file=ca.crt=example.com.crt
    

    シークレットは、クライアントポッドがデプロイされているのと同じ名前空間(この場合はdefault)に作成する必要があります。

  2. 上記の手順で作成されたシークレットがクライアントポッド(この場合はcurl)からアクセスできるように、必要なRBACを追加します。

    $ kubectl create role client-credential-role --resource=secret --verb=list
    $ kubectl create rolebinding client-credential-role-binding --role=client-credential-role --serviceaccount=default:curl
    

サイドカーでのエグレス通信のための相互TLSオリジネーションの設定

  1. HTTPリクエストをポート443にリダイレクトするためのServiceEntryを追加し、相互TLSオリジネーションを実行するためのDestinationRuleを追加します。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: originate-mtls-for-nginx
    spec:
      hosts:
      - my-nginx.mesh-external.svc.cluster.local
      ports:
      - number: 80
        name: http-port
        protocol: HTTP
        targetPort: 443
      - number: 443
        name: https-port
        protocol: HTTPS
      resolution: DNS
    ---
    apiVersion: networking.istio.io/v1
    kind: DestinationRule
    metadata:
      name: originate-mtls-for-nginx
    spec:
      workloadSelector:
        matchLabels:
          app: curl
      host: my-nginx.mesh-external.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        portLevelSettings:
        - port:
            number: 80
          tls:
            mode: MUTUAL
            credentialName: client-credential # this must match the secret created earlier to hold client certs, and works only when DR has a workloadSelector
            sni: my-nginx.mesh-external.svc.cluster.local
            # subjectAltNames: # can be enabled if the certificate was generated with SAN as specified in previous section
            # - my-nginx.mesh-external.svc.cluster.local
    EOF
    

    上記のDestinationRuleは、ポート80のHTTPリクエストに対してmTLSオリジネーションを実行し、ServiceEntryはポート80のリクエストをターゲットポート443にリダイレクトします。

  2. クレデンシャルがサイドカーに提供され、アクティブであることを確認します。

    $ istioctl proxy-config secret deploy/curl | grep client-credential
    kubernetes://client-credential            Cert Chain     ACTIVE     true           1                                          2024-06-04T12:15:20Z     2023-06-05T12:15:20Z
    kubernetes://client-credential-cacert     Cert Chain     ACTIVE     true           10792363984292733914                       2024-06-04T12:15:19Z     2023-06-05T12:15:19Z
    
  3. http://my-nginx.mesh-external.svc.cluster.localにHTTPリクエストを送信します。

    $ kubectl exec "$(kubectl get pod -l app=curl -o jsonpath={.items..metadata.name})" -c curl -- curl -sS http://my-nginx.mesh-external.svc.cluster.local
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    ...
    
  4. リクエストに対応する行について、curlポッドのログを確認します。

    $ kubectl logs -l app=curl -c istio-proxy | grep 'my-nginx.mesh-external.svc.cluster.local'
    

    次のような行が表示されるはずです。

    [2022-05-19T10:01:06.795Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 615 1 0 "-" "curl/7.83.1-DEV" "96e8d8a7-92ce-9939-aa47-9f5f530a69fb" "my-nginx.mesh-external.svc.cluster.local:443" "10.107.176.65:443"
    

相互TLSオリジネーションの設定のクリーンアップ

  1. 作成したKubernetesリソースを削除します。

    $ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
    $ kubectl delete secret client-credential
    $ kubectl delete rolebinding client-credential-role-binding
    $ kubectl delete role client-credential-role
    $ kubectl delete configmap nginx-configmap -n mesh-external
    $ kubectl delete service my-nginx -n mesh-external
    $ kubectl delete deployment my-nginx -n mesh-external
    $ kubectl delete namespace mesh-external
    $ kubectl delete serviceentry originate-mtls-for-nginx
    $ kubectl delete destinationrule originate-mtls-for-nginx
    
  2. 証明書と秘密キーを削除します。

    $ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr client.example.com.crt client.example.com.csr client.example.com.key
    
  3. この例で使用した生成済みの構成ファイルを削除します。

    $ rm ./nginx.conf
    

共通設定のクリーンアップ

curlサービスとデプロイメントを削除します。

$ kubectl delete service curl
$ kubectl delete deployment curl
この情報は役に立ちましたか?
改善のための提案はありますか?

フィードバックありがとうございます!