IAPに対応しているGAEにアクセスする


概要

GCPにあるGAEに対してアクセスする場合、認証のためにIAPをつけることが多いハズ
その際にrequest clientに対して認証情報を付ける方法についてまとめる

サービスアカウントを作る

サービスアカウントは以下の通りに作成できる
https://cloud.google.com/iam/docs/creating-managing-service-accounts?hl=ja

先程のサービスアカウントをIAPのアクセスユーザとして追加する

先程のサービスアカウントに対してGAEに対する権限を付ける。(IAP-secured Web App Userとして先程のサービスアカウントをつける。

コードのサンプル実装

build.gradle
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    // https://mvnrepository.com/artifact/com.google.api-client/google-api-client
    implementation group: 'com.google.api-client', name: 'google-api-client', version: '1.30.9'
    // https://mvnrepository.com/artifact/com.google.auth/google-auth-library-oauth2-http
    implementation group: 'com.google.auth', name: 'google-auth-library-oauth2-http', version: '0.20.0'
}

とりあえず依存関係にgoogleのapi-clientとauthを追加する。

IapRequest.kt
// https://cloud.google.com/iap/docs/authentication-howto?hl=ja#iap_make_request-javaのコードを参考にKotlinにしただけ
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpRequestInitializer
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.File

class IapRequest(
    serviceAccountFile: File
) {
    private val IAM_SCOPE = "https://www.googleapis.com/auth/iam"
    private val httpTransport: HttpTransport = NetHttpTransport()
    private val idTokenProvider: IdTokenProvider

    init {
        val credentials: GoogleCredentials =
            GoogleCredentials.fromStream(serviceAccountFile.inputStream())
                .createScoped(setOf(IAM_SCOPE))
        if (credentials !is IdTokenProvider) {
            throw Exception("Google credentials : credentials that can provide id tokens expected")
        }
        idTokenProvider = credentials
    }

    /**
     * Clone request and add an IAP Bearer Authorization header with signed JWT token.
     *
     * @param request Request to add authorization header
     * @param iapClientId OAuth 2.0 client ID for IAP protected resource
     * @return Clone of request with Bearer style authorization header with signed jwt token.
     * @throws Exception exception creating signed JWT
     */
    fun build(request: HttpRequest, iapClientId: String): HttpRequest {
        val idTokenProvider: IdTokenProvider = idTokenProvider
        val credentials: IdTokenCredentials = IdTokenCredentials.newBuilder()
            .setIdTokenProvider(idTokenProvider)
            .setTargetAudience(iapClientId)
            .build()

        val httpRequestInitializer: HttpRequestInitializer = HttpCredentialsAdapter(credentials)
        return httpTransport
            .createRequestFactory(httpRequestInitializer)
            .buildRequest(request.requestMethod, request.url, request.content)
    }
}

com.google.auth.oauth2.IdTokenProviderがBetaがunstableになっているからというwarningがでているが気にしない。
処理としては、ベースとなるGoogleのRequest Objectに対してauthorization headerをつけるAdapterを追加している

GoogleCredentials.fromStream(...) で認証ファイルを読み込んでくれている

Main.kt
import com.google.api.client.http.GenericUrl
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpResponseException
import com.google.api.client.http.HttpTransport
import com.google.api.client.http.javanet.NetHttpTransport
import java.io.File

// 先程のGAEのURL
val protectedUr = "https://hoge.appspot.com/"
// クライアント ID (IAPのOAuthクライアントを編集)
val clientId = "hogehoge"

fun main() {
    auth()
}

fun auth() {
    val httpTransport: HttpTransport = NetHttpTransport()
    val request =
        IapRequest(File(ClassLoader.getSystemResource("service-account.json").toURI()))
            .build(
                httpTransport.createRequestFactory().buildGetRequest(GenericUrl(protectedUr)),
                clientId
            )
    try {
        val response = request.execute()
        println("content: ${response.parseAsString()}")
    } catch (e: HttpResponseException) {
        println(e)
    }
}

ここは実際に使っているだけ
ユースケースとしてGAEのサービス1つづつに対してそれぞれIAP許可設定をつけることがあるので、IAP Request作成時にサービスアカウントの認証ファイルを渡す。

まとめ

課題として、このコードの場合GoogleのHttpClientに依存しているので、他のHttpClientでも使えるようにしたい。。。コードを追っていく必要がある。

もしくは以下の手順を地道にコードにしていくか
https://cloud.google.com/iap/docs/app-engine-quickstart?hl=ja#iap-access