1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Cloud Runのサービス間認証

Posted at

概要

Cloud Runを例にアクセスに認証情報が必要な非公開サービスと通信するための手順をまとめる。

参照ドキュメント(Cloud Run)

⑴クイックスタート: ビルドとデプロイ
https://cloud.google.com/run/docs/quickstarts/build-and-deploy/
⑵認証の概要
https://cloud.google.com/run/docs/authenticating/overview
⑶サービス間認証
https://cloud.google.com/run/docs/authenticating/service-to-service

全体像

Screen Shot 2021-08-16 at 14.34.41.png

手順

  1. アプリケーションを Cloud Run にデプロイ
  2. サービスアカウントの設定
  3. IDトークンの取得
  4. テスト

1.アプリケーションをデプロイ

まず、呼び出し側のアプリケーションを作成してコンテナ イメージにパッケージ化し、コンテナ イメージを Container Registry にアップロードして Cloud Run にデプロイする。*⑴

2. サービスアカウントの設定

呼び出し側サービスからのリクエストを受け入れるようにするためには、呼び出し元のサービスアカウントを受信側サービスのメンバーにする必要がある。*⑵

今回は、呼び出し元のサービスアカウントに Cloud Run 起動元(roles/run.invoker)のロールを付与する。*⑶

3. IDトークンの取得

Google 署名付きIDトークンを取得して、リクエストにセットする方法はドキュメントによると以下になる。

・受信側のサービスの URL に設定されたオーディエンスクレーム(aud)を使用して、Google 署名付き ID トークンを取得します。
( 現在、カスタム ドメインは aud 値でサポートされていません。)
・受信側サービスに対するリクエストの Authorization: Bearer ID_TOKEN ヘッダーに ID トークンを含めます。

オーディエンスクレーム(aud)ってなんぞや?が分からない。。。(分かる人コメントいただけると助かります)
受信側のサービスURLだということは分かる。

とりあえず受信側サービスのURLをもとにIDトークンを取得し、リクエストの Authorization: Bearer ID_TOKEN ヘッダーにセットすればいい。*⑶

以下のサンプルコードをもとに呼び出し側のサービスを実装。

authentication/src/main/java/com/example/cloudrun/Authentication.java
package com.example.cloudrun;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdTokenCredentials;
import com.google.auth.oauth2.IdTokenProvider;
import java.io.IOException;

public class Authentication {

  // makeGetRequest makes a GET request to the specified Cloud Run or
  // Cloud Functions endpoint, serviceUrl (must be a complete URL), by
  // authenticating with an Id token retrieved from Application Default Credentials.
  public static HttpResponse makeGetRequest(String serviceUrl) throws IOException {
    GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
    if (!(credentials instanceof IdTokenProvider)) {
      throw new IllegalArgumentException("Credentials are not an instance of IdTokenProvider.");
    }
    IdTokenCredentials tokenCredential =
        IdTokenCredentials.newBuilder()
            .setIdTokenProvider((IdTokenProvider) credentials)
            .setTargetAudience(serviceUrl)
            .build();

    GenericUrl genericUrl = new GenericUrl(serviceUrl);
    HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(tokenCredential);
    HttpTransport transport = new NetHttpTransport();
    HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl);
    return request.execute();
  }
}
security/idtoken.go
package security

import (
	"context"
	"fmt"
	"io"

	"google.golang.org/api/idtoken"
)

// makeGetRequest makes a request to the provided targetURL with an authenticated client.
func makeGetRequest(w io.Writer, targetURL string) error {
	// functionURL := "https://TARGET_URL"
	ctx := context.Background()

	// client is a http.Client that automatically adds an "Authorization" header
	// to any requests made.
	client, err := idtoken.NewClient(ctx, targetURL)
	if err != nil {
		return fmt.Errorf("idtoken.NewClient: %v", err)
	}

	resp, err := client.Get(targetURL)
	if err != nil {
		return fmt.Errorf("client.Get: %v", err)
	}
	defer resp.Body.Close()
	if _, err := io.Copy(w, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %v", err)
	}

	return nil
}

4. テスト

以下の手順にそってテスト開始

  1. サービスアカウントの認証情報の取得(呼び出し側サービスがGoogle Cloudの外部である場合)
    参照:https://cloud.google.com/docs/authentication/production
  2. サービス間認証を行わなかった時に、アクセスができないことを確認
  3. サービス間認証を行った時に、アクセスができることを確認

以下のコードはJavaでのテストコード
(受信側であるCloud RunのサービスURLをAuthentication.makeGetRequestの引数として渡して、レスポンスが返ってこれば、アクセスできたことが確認できる)

authentication/src/test/java/com/example/cloudrun/AuthenticationTest.java
package com.example.cloudrun;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.core.StringContains.containsString;

import com.google.api.client.http.HttpResponse;
import java.io.IOException;
import org.junit.Test;

public class AuthenticationTest {

  @Test
  public void canMakeGetRequest() throws IOException {
    String url = "https://helloworld-gqnrt6tdna-an.a.run.app"; // Cloud RunのサービスURL
    HttpResponse response = Authentication.makeGetRequest(url);
    // "Hello World!"と返すだけのアプリケーションを受信側のサービスとした場合、
    assertThat(response.parseAsString(), containsString("Hello World!"));
    assertThat(response.getContentType(), containsString("text/html"));
    assertThat(response.getStatusCode(), equalTo(200));
  }

  @Test
  public void failsMakeGetRequestWithoutProtocol() throws IOException {
    String url = "helloworld-gqnrt6tdna-an.a.run.app/";
    try {
      Authentication.makeGetRequest(url);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("no protocol"));
    }
  }
}
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?