Istio を使用した Cloud Run for Anthos サービスへのアクセスの認可

このチュートリアルでは、Istio を使用して、Cloud Run for Anthos にデプロイする Service へのアクセスを認可する方法について説明します。

Cloud Run は、デベロッパーがアプリや機能をデプロイして提供するための環境を提供します。Cloud Run は、フルマネージド環境または Cloud Run for Anthos という Google Kubernetes Engine(GKE)クラスタで Service を実行します。

いずれの環境でもデベロッパーのエクスペリエンスは変わりませんが、基盤となるプラットフォームの機能は異なります。

Cloud Run for Anthos は、Identity and Access Management(IAM)を Service の呼び出し権限の付与には使用しません。代わりに、Istio を使用して認証と認可を行います。このチュートリアルでは、Istio 認証ポリシーと認可ポリシーを使用して、Cloud Run for Anthos のサンプル サービスを保護します。

認証ポリシーと認可ポリシーでは、サンプル サービスを呼び出せる ID(IAM サービス アカウントとユーザー)を指定します。

このチュートリアルでは、GKE クラスタの外部で実行されるクライアントの認証と認可を実装します。

目標

  • GKE クラスタを作成して Cloud Run を有効にする。
  • Cloud Run for Anthos にサンプル サービスをデプロイする。
  • IAM サービス アカウントを作成する。
  • Istio 認証ポリシーを作成する。
  • Istio 認可ポリシーを作成する。
  • ソリューションをテストする。

費用

このチュートリアルでは、課金対象である次の Google Cloud コンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。新しい Google Cloud ユーザーは無料トライアルをご利用いただけます。

このチュートリアルを終了した後、作成したリソースを削除すると、それ以上の請求は発生しません。詳しくは、クリーンアップをご覧ください。

始める前に

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  2. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  3. Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Google Kubernetes Engine API、Cloud Run API、Cloud APIs を有効にします。
    API を有効にする
  5. Cloud Console で Cloud Shell に移動します。
    Cloud Shell に移動
    Cloud Console の下部に Cloud Shell セッションが開き、コマンドライン プロンプトが表示されます。Cloud Shell はシェル環境です。gcloud コマンドライン ツールなどの Cloud SDK がすでにインストールされており、現在のプロジェクトの値もすでに設定されています。セッションが初期化されるまで数秒かかることがあります。このチュートリアルでは、コマンドの実行はすべて Cloud Shell で行います。

環境の設定

  1. このチュートリアルで使用する Compute Engine ゾーンの環境変数と gcloud ツールのデフォルトを定義します。

    ZONE=us-central1-f
    gcloud config set compute/zone $ZONE
    

    ゾーンを変更できます。

  2. Cloud Run アドオンを使用して GKE クラスタを作成します。

    CLUSTER=cloud-run-gke-invoker-tutorial
    
    gcloud beta container clusters create $CLUSTER \
        --addons HorizontalPodAutoscaling,HttpLoadBalancing,CloudRun \
        --enable-ip-alias \
        --enable-stackdriver-kubernetes \
        --machine-type e2-standard-2 \
        --release-channel regular
    

    このチュートリアルでは、GKE バージョン 1.15.11-gke.9 以降、1.16.8-gke.7 以降、1.17.4-gke.5 以降が必要です。regular リリース チャンネルを使用する新しい GKE クラスタは、バージョンの制約を満たします。

サンプル サービスのデプロイ

  1. Cloud Shell で、GKE クラスタに tutorial という Namespace を作成します。

    kubectl create namespace tutorial
    

    Namespace の名前は変更できます。

  2. sample という Service を tutorial Namespace の Cloud Run for Anthos にデプロイします。

    gcloud run deploy sample \
        --cluster $CLUSTER \
        --cluster-location $ZONE \
        --namespace tutorial \
        --image gcr.io/knative-samples/simple-api \
        --platform gke
    

    このコマンドを実行すると、Knative Serving サービス オブジェクトが作成されます。

  3. Cloud Run for Anthos は、Istio Ingress ゲートウェイの外部 IP アドレスで Service を公開します。外部 IP アドレスを取得して、EXTERNAL_IP という環境変数と external-ip.txt というファイルに格納します。

    export EXTERNAL_IP=$(./get-external-ip.sh | tee external-ip.txt)
    
    echo $EXTERNAL_IP
    
    get_external_ip () {
        external_ip=$(kubectl -n gke-system get svc istio-ingress \
            -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    }
    get_external_ip
    while [ -z "$external_ip" ]; do
        sleep 2
        get_external_ip
    done
    echo "$external_ip"
  4. サンプル サービスから HTTP/1.1 200 OK が返されていることを確認します。

    curl -siH "Host: sample.tutorial.example.com" $EXTERNAL_IP | head -n1
    

サービス アカウントの作成

  1. 後のステップで、サンプル サービスへのアクセス権を付与する IAM サービス アカウントを作成します。サービス アカウントのメールアドレスを環境変数に格納します。

    export SERVICE_ACCOUNT_EMAIL=$(gcloud iam service-accounts create \
        cloudrun-gke-invoker-tutorial \
        --display-name "Cloud Run for Anthos authorization tutorial service account" \
        --format "value(email)")
    

    このチュートリアルでは、サービス アカウント名に cloudrun-gke-invoker-tutorial を使用します。この名前は変更できます。

Istio 認証と認可の構成

  1. Istio 認証ポリシーを作成します。

    kubectl apply -f authenticationpolicy.yaml
    
    apiVersion: authentication.istio.io/v1alpha1
    kind: Policy
    metadata:
      name: istio-ingress-jwt
      namespace: gke-system
    spec:
      targets:
      - name: istio-ingress
        ports:
        - name: http2
        - name: https
      origins:
      - jwt:
          issuer: https://accounts.google.com
          jwksUri: https://www.googleapis.com/oauth2/v3/certs
      originIsOptional: true # use authorization policy to allow or deny request
      principalBinding: USE_ORIGIN

    この認証ポリシーは、Istio Ingress ゲートウェイの http2 ポートと https ポートへの受信リクエストで Authorization ヘッダーの JSON ウェブトークン(JWT)を検証します。ポリシーを遵守するには、JWT が Google から発行され、Google の署名が付いた OpenID Connect(OIDC) ID トークンである必要があります。ID トークンの検証については、Google Identity Platform のドキュメントをご覧ください。

    originIsOptional: true 属性を使用すると、指定された制約を満たす JWT が含まれていない場合でも、認証ポリシーでリクエストが許可されます。この認証ポリシーの目的は、次のステップで作成する認可ポリシーで JWT の属性を使用できるようにすることです。この認可ポリシーでは、リクエストを承認するか拒否するかを決定します。

  2. invoke-tutorial という名前の Istio 認可ポリシーを作成します。

    envsubst < authorizationpolicy-invoker.tmpl.yaml | kubectl apply -f -
    

    この認可ポリシーでは、HTTP メソッドと URL パスを使用して、http://example.com のオーディエンス(aud)クレームの $SERVICE_ACCOUNT_EMAIL で指定されたサービス アカウントに Google が発行および署名した JWT(https://accounts.google.com)を含むすべてのリクエストに、ホスト名が *.tutorial.example.com と一致するすべてのワークロードへのアクセスを許可します。

    Cloud Run for Anthos のデフォルト ドメインを変更する場合は、実際の環境で、実際のドメインに合わせて hosts フィールドの値を調整します。

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: invoke-tutorial
      namespace: gke-system
    spec:
      action: ALLOW
      rules:
      - to:
        - operation:
            hosts:
            - '*.tutorial.example.com'
        when:
        - key: request.auth.claims[aud]
          values:
          - http://example.com
        - key: request.auth.claims[email]
          values:
          - $SERVICE_ACCOUNT_EMAIL
        - key: request.auth.claims[iss]
          values:
          - https://accounts.google.com
      selector:
       matchLabels:
         istio: ingress-gke-system
  3. 認証ポリシーと認可のポリシーが有効になるまで、少し時間がかかる場合があります。次のコマンドを実行し、出力に HTTP/1.1 403 Forbidden が表示されるまで待ちます。

    while sleep 2; do
        curl -siH "Host: sample.tutorial.example.com" $EXTERNAL_IP | head -n1
    done
    

    最初は、出力に「HTTP/1.1 200 OK」と「HTTP/1.1 403 Forbidden」が交互に表示されることがあります。これは、EnvoyIstio のポリシー変更が結果整合性になるためです。

    HTTP/1.1 403 Forbidden が繰り返し表示されたら、Ctrl+C を押して待機状態を解除します。

サービスへのアクセス

作成した認証ポリシーと認可ポリシーでは、Google が発行した署名付き ID トークンが必要です。トークンはいくつかの方法で取得できます。たとえば、次のような方法があります(最も推奨する方法から順番に説明します)。

  1. 保護されたサービスに Compute Engine インスタンスまたは GKE クラスタからアクセスする場合、あるいはメタデータ サーバーへのアクセス権がある別の環境からアクセスする場合は、Compute Engine メタデータ サーバーを使用します。
  2. ご使用のプログラミング言語で利用できる場合は、Google 認証クライアント ライブラリを使用します。
  3. Google 認証クライアント ライブラリを使用できない場合は、IAM Service Account Credentials API を直接使用します。
  4. 実装中などに Service をインタラクティブにテストする場合は、gcloud コマンドライン ツールを使用します。

Compute Engine メタデータ サーバーを使用する

  1. Cloud Shell で、Compute Engine インスタンス(VM)を作成し、以前に作成したサービス アカウントに関連付けます。

    VM=cloudrun-gke-invoker-tutorial-vm
    
    gcloud compute instances create $VM \
        --scopes cloud-platform \
        --service-account $SERVICE_ACCOUNT_EMAIL
    

    このチュートリアルでは、cloudrun-gke-invoker-tutorial-vm というインスタンス名を使用します。 この名前は変更できます。

  2. Istio Ingress ゲートウェイのパブリック IP アドレスを含むファイルを VM にコピーします。

    gcloud compute scp external-ip.txt $VM:~
    
  3. SSH を使用して VM に接続します。

    gcloud compute ssh $VM
    
  4. SSH セッションで、Compute Engine メタデータ サーバーから ID トークンを取得します。

    ID_TOKEN=$(curl -s -H Metadata-Flavor:Google \
        --data-urlencode format=full \
        --data-urlencode audience=http://example.com \
        http://metadata/computeMetadata/v1/instance/service-accounts/default/identity)
    
  5. ID トークンを含むリクエストをサンプルの Cloud Run for Anthos サービスに送信します。

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $(cat external-ip.txt)
    

    次のように出力されます。

    OK
    
  6. SSH セッションを終了します。

    exit
    

クライアント ライブラリを使用する

PythonJavaGoNode.js 用の Google 認証クライアント ライブラリを使用すると、便利な API を使用して ID トークンを取得できます。Python ライブラリと IDTokenCredentials クラスを使用するには、次の操作を行います。

  1. Cloud Shell で、サービス アカウントの認証情報を作成し、ダウンロードします。

    gcloud iam service-accounts keys create service-account.json \
        --iam-account $SERVICE_ACCOUNT_EMAIL
    
  2. Python 仮想環境を作成します。

    virtualenv -p python3 .venv
    
  3. このターミナル セッションの Python 仮想環境を有効にします。

    source .venv/bin/activate
    
  4. 依存関係をインストールします。

    pip install -r requirements.txt
    
  5. ID トークンを取得し、サンプルの Cloud Run for Anthos サービスにリクエストを送信します。

    python id_token_request.py http://$EXTERNAL_IP http://example.com \
        Host:sample.tutorial.example.com
    

    次のように出力されます。

    OK
    
    import os
    import sys
    from google.auth.transport.requests import AuthorizedSession
    from google.oauth2 import service_account
    
    def request(method, url, target_audience=None, service_account_file=None,
                data=None, headers=None, **kwargs):
        """Obtains a Google-issued ID token and uses it to make a HTTP request.
    
        Args:
          method (str): The HTTP request method to use
                ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
          url: The URL where the HTTP request will be sent.
          target_audience (str): Optional, the value to use in the audience
                ('aud') claim of the ID token. Defaults to the value of 'url'
                if not provided.
          service_account_file (str): Optional, the full path to the service
                account credentials JSON file. Defaults to
                '<working directory>/service-account.json'.
          data: Optional dictionary, list of tuples, bytes, or file-like object
                to send in the body of the request.
          headers (dict): Optional dictionary of HTTP headers to send with the
                request.
          **kwargs: Any of the parameters defined for the request function:
                https://github.com/requests/requests/blob/master/requests/api.py
                If no timeout is provided, it is set to 90 seconds.
    
        Returns:
          The page body, or raises an exception if the HTTP request failed.
        """
        # Set target_audience, if missing
        if not target_audience:
            target_audience = url
    
        # Set service_account_file, if missing
        if not service_account_file:
            service_account_file = os.path.join(os.getcwd(),
                                                'service-account.json')
    
        # Set the default timeout, if missing
        if 'timeout' not in kwargs:
            kwargs['timeout'] = 90  # seconds
    
        # Obtain ID token credentials for the specified audience
        creds = service_account.IDTokenCredentials.from_service_account_file(
            service_account_file, target_audience=target_audience)
    
        # Create a session for sending requests with the ID token credentials
        session = AuthorizedSession(creds)
    
        # Send a HTTP request to the provided URL using the Google-issued ID token
        resp = session.request(method, url, data=data, headers=headers, **kwargs)
        if resp.status_code == 403:
            raise Exception('Service account {} does not have permission to '
                            'access the application.'.format(
                                creds.service_account_email))
        elif resp.status_code != 200:
            raise Exception(
                'Bad response from application: {!r} / {!r} / {!r}'.format(
                    resp.status_code, resp.headers, resp.text))
        else:
            return resp.text

Identity-Aware Proxy のドキュメントには、他のプログラミング言語を使用して Google 発行の ID トークンを取得するサンプルコードがあります。

IAM Service Account Credentials API の使用

ご使用のプログラミング言語で ID トークンを作成できる Google 認証クライアント ライブラリがない場合は、IAM Service Account Credentials API を直接使用できます。

  1. サービス アカウントの ID トークンを作成する権限を持つ Cloud IAM カスタムロールを作成します。

    gcloud iam roles create serviceAccountIdTokenCreator \
        --project $GOOGLE_CLOUD_PROJECT \
        --description "Impersonate service accounts to create OpenID Connect ID tokens" \
        --permissions "iam.serviceAccounts.getOpenIdToken" \
        --stage GA \
        --title "Service Account ID Token Creator"
    

    カスタムロールを作成する代わりに、事前に定義されたサービス アカウント トークン作成者ロールiam.serviceAccountTokenCreator)を使用することもできますが、このロールを使用すると、Service Account Credentials API で ID トークンを作成する場合に不要な追加の権限も付与されます。

  2. 以前に作成したサービス アカウントでカスタムロールを付与するポリシー バインディングを作成します。

    gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
        --member user:$(gcloud config get-value account) \
        --role projects/$GOOGLE_CLOUD_PROJECT/roles/serviceAccountIdTokenCreator
    

    ポリシー バインディングが有効になるまでに少し時間がかかることがあります。次のコマンドでは、ポリシー バインディングが有効かどうかを 5 秒ごとに確認します。ポリシー バインディングが有効になったら、確認を停止してターミナル プロンプトに戻ります。

    status_code=""
    while [ "$status_code" != "200" ] ; do
        sleep 5
        echo "Checking permissions"
        status_code=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
            -H "Authorization: Bearer $(gcloud auth print-access-token)" \
            --data-urlencode audience=http://example.com \
            --data-urlencode includeEmail=true \
            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:generateIdToken")
    done
    
  3. IAM Service Account Credentials API の generateIdToken メソッドを使用して、サービス アカウントの ID トークンを生成します。

    ID_TOKEN=$(curl -s -X POST \
        -H "Authorization: Bearer $(gcloud auth print-access-token)" \
        --data-urlencode audience=http://example.com \
        --data-urlencode includeEmail=true \
        "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:generateIdToken" | jq -r '.token')
    
  4. ID トークンを含むリクエストをサンプルの Cloud Run for Anthos サービスに送信します。

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $EXTERNAL_IP
    

    次のように出力されます。

    OK
    

gcloud コマンドライン ツールの使用

Google 発行の ID トークンを取得するには、gcloud コマンドライン ツールを使用します。 これらのトークンは、自分自身またはサービス アカウントを識別するため、Service をインタラクティブにテストする場合に役立ちます。

  1. 以前に作成したサービス アカウントに事前定義のサービス アカウント トークン作成者ロールを付与します。

    gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
        --member user:$(gcloud config get-value account) \
        --role roles/iam.serviceAccountTokenCreator
    

    ポリシー バインディングが有効になるまでに少し時間がかかることがあります。次のコマンドでは、ポリシー バインディングが有効かどうかを 5 秒ごとに確認します。ポリシー バインディングが有効になったら、確認を停止してターミナル プロンプトに戻ります。

    status_code=""
    while [ "$status_code" != "200" ] ; do
        sleep 3
        echo "Checking permissions"
        status_code=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
            -H "Authorization: Bearer $(gcloud auth print-access-token)" \
            -H "Content-Type: application/json" \
            -d '{"payload": "{}"}' \
            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:signJwt")
    done
    
  2. サービス アカウントを使用して ID トークンを取得します。

    ID_TOKEN=$(gcloud auth print-identity-token \
        --audiences http://example.com \
        --impersonate-service-account $SERVICE_ACCOUNT_EMAIL \
        --include-email)
    
  3. ID トークンを含むリクエストをサンプルの Cloud Run for Anthos サービスに送信します。

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $EXTERNAL_IP
    

    次のように出力されます。

    OK
    

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

プロジェクトの削除

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

リソースの削除

このチュートリアルで使用した Google Cloud プロジェクトを残しておく場合は、個々のリソースを削除します。

  1. GKE クラスタを削除します。

    gcloud container clusters delete $CLUSTER --async --quiet
    
  2. サービス アカウントを削除します。

    gcloud iam service-accounts delete $SERVICE_ACCOUNT_EMAIL --quiet
    
  3. サービス アカウントの認証情報ファイルを削除します。

    rm -f service-account.json
    
  4. Compute Engine インスタンスを削除します。

    gcloud compute instances delete $VM --quiet
    

次のステップ