🔖

GCPのAPIGatewayをAPIキー認証を付けて独自ドメインで公開する設定をTerraformで行う

2023/11/06に公開

GCPのAPIGatewayって癖があるうえ、ドキュメントが若干抜けてるところとか、そもそもTerraformでどうすればいいのかみたいなのがすごいわかりにくいのでまとめた

かなり端折っているのでここをとっかかりにTerraform,GCPの各ドキュメントを精読すればわかると思う

前提

  • バックエンドはCloud Functions
  • APIキー認証を入れる
  • 自前のドメイン(カスタムドメイン)でホストする

バックエンドを作る

Cloud Functionsを作って設定します

cloudfunctions.tf
resource "google_cloudfunctions2_function" "func" {
  name = "test-func"
  build_config {
    ...
  }
  service_config {
    ingress_settings = "ALLOW_ALL"
    ...
  }
}

いろいろ省略したけど大事なのは ingress_settings の部分
INTERNAL_ONLYINTERNAL_ONLY_AND_GCLBを設定したい気持ちになるがAPI Gateway経由のリクエストの場合対応していないらしい(これはバックエンドがCloud RunなどでもAPI Gateway経由だとそうらしい)。らしいというあやふやな答えで申し訳ないが調べた感じ駄目なようだし、実際にALLOW_ALL以外だと弾かれるのは確認した

かわりにservice_iam_memberで設定する

cloudfunctions.tf
resource "google_cloud_run_service_iam_member" "func_auth" {
  role = "roles/run.invoker"
  member = "allAuthenticatedUsers"
  ...
}

2世代目のCloud Functionsでこれらの設定を行う google_cloudfunctions2_function_iam_member もあるがこれはバージョンによっては動かないらしい。これも実際にハマったのでこうしている。もしかしたら最新版では解決してるかもしれない

API Gatewayの設定

API Gatewayの設定で必要になるのは大雑把に以下の3つ

  • API
  • API設定
  • APIのゲートウェイ

APIに設定を結びつけ、その結び付けられた設定を参照するゲートウェイを置く、という感じ

APIファイルの作成

APIに読み込ませるAPI構造のファイル。今回はOpenAPIを用いる

api.yaml
swagger: '2.0'
info:
  ...
paths:
  /api/v1/hello:
    get:
      x-google-backend:
        address: ${func_url}
      security:
        - api_key: []
      ...

securityDefinitions
  api_key:
    type: apiKey
    name: x-api-key
    in: header

公式ドキュメントにもサンプルはあるのでざっくりだが注意事項は security と securityDefinitions の項、あとバックエンドのアドレスはTerraformで入れるため変数を用意する

API定義の作成

続いてAPI定義のTerraform

api.tf
resource "google_api_gateway_api" "api" {
  api_id = "func-api"
  ...
}

resource "google_api_gateway_api_config" "api_config" {
  api = google_api_gateway_api.api.api_id
  openapi_documents {
    path = "spec.yaml"
    contents = base64encode(templatefile("path/to/api.yaml", {
      func_url = google_cloudfunctions2_function.func.url
    }))
  }
  ...
}

resource "google_api_gateway_gateway" "api_gateway" {
  api_config = google_api_gateway_api_config.api_config.id
  gateway_id = "gateway-id"
  ...
}

templatefileを使って ${func_url} をURLのアドレスに置き換えさせる。

ちなみにこの時点でAPIキーを必要とするAPI Gatewayの設定は完了する。ここでデプロイすれば認証が必要なAPIが動いているはず(APIキーの設定をしていないので呼び出せるクライアントが無いが

APIキーの設定

サービスのAPIキー設定への登録

APIキーを設定するが、このAPIキーは当然API Gateway以外で使えていると問題がある。そして別のAPI GatewayのAPIが呼び出せたりしても困る

しかし、公式ドキュメントを見るとどうも設定がgcloudコマンドでしかできない疑惑がある

というわけでこんなふうにして無理やり設定する

api.tf
resource "null_resource" "enable_api_gateway_service" {
  provisioner "local-exec" {
    command = <<-EOT
      gcloud services enable \
      ${google_api_gateway_api.api.managed_service}
    EOT
  }
}

ただ、この google_api_gateway_api.managed_service はドキュメントに書かれておらず、調べている中であとあと見つけたので動くのか若干怪しい。マネージドサービス名を取るため私はこんな事をした

api.tf
data "external" "api_describe" {
  program = [
    "gcloud",
    "api-gateway",
    "apis",
    "describe",
    "${google_api_gateway_api.api.api_id}",
    "--format=json"
  ]
}

resource "null_resource" "enable_api_gateway_service" {
  provisioner "local-exec" {
    command = <<-EOT
      gcloud services enable \
      ${data.external.api_describe.result[managedService]}
    EOT
  }
}

data.externalは結果がJSONでないといけないが、 --formatオプションでそこはとてもいい感じにごまかせる。

APIキーの生成

上記の2つのどちらかで有効になるはずなので、これをそのまま用いてAPIキーを設定する

api.tf
resource "google_apikeys_key" "api_key" {
  restrictions {
    api_targets {
      service = google_api_gateway_api.api.managed_service
      # 上記は google_api_gateway_api.managed_service を用いた場合
    }
  ...
}

nameとかdisplay_nameとかは各々入れてください

ネットワークグループ、URLマップ、ロードバランサなどの設定

ここから先はカスタムドメインを使用するケースの話。
まず前提として、現在(2023-11-06時点)プレビュー版であること、(やってることは同じなので)負荷分散のスタートガイドを理解しておくことが必要。ここの設定を全部Terraform上で記述するイメージ

大雑把には通常のCloud RunやCloud Functionsをカスタムドメインで公開する場合とほぼ同じ。バックエンドサービスの指定にAPI Gatewayを指定するため、ネットワークエンドグループにAPI Gatewayの設定を入れるのが一番の違い

api.tf
resource "google_compute_region_network_endpoint_group" "api_neg" {
  ...
  network_endpoint_type = "SERVERLESS"
  serverless_deployment { # ベータ版
    platform = "apigateway.googleapis.com"
    resource = google_api_gateway_gateway.api_gateway.gateway_id
  }
}

resource "google_compute_backend_service" "api_backend_service" {
  ...
  backend {
    group = google_compute_region_network_endpoint_group.api_neg.id
  }
}

resource "google_compute_url_map" "api_url_map" {
  ...
  default_service = google_compute_backend_service.api_backend_service.id
}

resource "google_compute_target_https_proxy" "api_https_proxy" {
  ...
  url_map = google_compute_url_map.api_url_map.id
  ssl_certifiates = [
    ... # SSL設定は別途やってここに設定する
  ]
}

resource "google_compute_global_forwarding_rule" "api_forwarding_rule" {
  ...
  target = "google_compute_target_https_proxy.api_https_proxy.id
  ip_address = ... # IPアドレスも別途設定してここに入れる
}

書いてない部分として

  • SSL設定 (google_compute_ssl_policy, google_compute_managed_ssl_certificate)
  • IPアドレスの確保(google_compute_global_address)
  • IPアドレスとドメインの結びつけ(google_dns_record_setなど)

このあたりが設定できればあとは(SSL認証が通れば)テストするのみである。APIキーは下手に持ってくるよりコンソールで確認したほうが安全である

curl -X POST -H "x-api-key: [your api key]" "https://[your domain]/api/v1/hello"

まとめ

GCPのAPI Gatewayって癖があるので大変ですが、まあなんとかなると思います

わかんないこと・間違ったことがあったら教えてください。あとプレビュー版・ベータ版の機能が多めなので、半年もしたらいろいろ変わってるかもしれません。適宜公式ドキュメントを参照しましょう

参考資料

Discussion