使用Terraform配置GCP API Gateway调用Cloud Function遇403错误求助
GCP API Gateway调用Cloud Function持续返回403错误排查
需求背景
使用Terraform部署GCP API Gateway与Cloud Function,实现:
- API端点受API密钥保护
- API Gateway验证密钥后,以无认证方式调用Cloud Function
- Cloud Function配置为仅内部访问的网络准入规则
错误信息
调用API Gateway URL时始终返回403:
{"message":"PERMISSION_DENIED: API my-api-0esofolowrs06.apigateway.propane-crawler-437508-t1.cloud.goog is not enabled for the project.","code":403}
已尝试操作
- 确认API Gateway服务(
apigateway.googleapis.com)已启用 - 手动启用错误提示中的特定API
my-api-0esofolowrs06.apigateway.propane-crawler-437508-t1.cloud.goog - 调整Cloud Function认证配置:添加认证、配置服务账号访问、尝试JWT认证
- 查看服务间请求日志
- 在控制台启用所有相关GCP服务
Terraform配置代码
terraform { required_providers { google = { source = "hashicorp/google" version = "~> 4.0" } google-beta = { source = "hashicorp/google-beta" version = "~> 4.0" } } } # Initialise Providers ######################################################################### provider "google" { project = var.project_id region = var.region } provider "google-beta" { project = var.project_id region = var.region } # Utilities ################################################################################## # Retrieve the project number data "google_project" "project" { project_id = var.project_id } # Create Service Accounts ##################################################################### # Service account for the gateway resource "google_service_account" "my-gateway" { account_id = "my-gateway" } # Give the gateway permissions to invoke cloud run services resource "google_project_iam_member" "my-gateway" { member = "serviceAccount:${google_service_account.my-gateway.email}" project = var.project_id role = "roles/run.invoker" } resource "google_cloudfunctions2_function_iam_member" "invoker" { project = var.project_id location = google_cloudfunctions2_function.default.location cloud_function = google_cloudfunctions2_function.default.name role = "roles/cloudfunctions.invoker" member = "allUsers" depends_on = [google_cloudfunctions2_function.default] } resource "google_cloud_run_service_iam_member" "member" { location = google_cloudfunctions2_function.default.location service = google_cloudfunctions2_function.default.name role = "roles/run.invoker" member = "allUsers" # member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-apigateway.iam.gserviceaccount.com" depends_on = [google_cloudfunctions2_function.default] } # Cloud Function Deployment ############################################################### resource "random_id" "default" { byte_length = 8 } resource "google_storage_bucket" "default" { name = "${random_id.default.hex}-gcf-source" # Every bucket name must be globally unique location = "US" uniform_bucket_level_access = true } data "archive_file" "default" { type = "zip" output_path = "/tmp/function-source.zip" source_dir = "nodejs-docs-samples/functions/helloworld/helloworldHttp" } resource "google_storage_bucket_object" "object" { name = "function-source.zip" bucket = google_storage_bucket.default.name source = data.archive_file.default.output_path # Add path to the zipped function source code } resource "google_cloudfunctions2_function" "default" { name = "function-v2" location = "us-central1" description = "a new function" build_config { runtime = "nodejs20" entry_point = "helloHttp" # Set the entry point source { storage_source { bucket = google_storage_bucket.default.name object = google_storage_bucket_object.object.name } } } service_config { max_instance_count = 1 available_memory = "256M" timeout_seconds = 60 ingress_settings = "ALLOW_INTERNAL_ONLY" } } # API Gateway Deployment ################################################################### # Enable the API Gateway API resource "google_project_service" "apigateway" { service = "apigateway.googleapis.com" } # Create the API resource "google_api_gateway_api" "my-api" { provider = google-beta api_id = "my-api" depends_on = [google_project_service.apigateway] } resource "google_api_gateway_api_config" "my-api-config" { provider = google-beta api = google_api_gateway_api.my-api.api_id api_config_id = "my-api-config" gateway_config { backend_config { google_service_account = google_service_account.my-gateway.email } } openapi_documents { document { path = "openapi.yaml" contents = base64encode(templatefile("${path.module}/openapi.yaml.tmpl", { backend_address = google_cloudfunctions2_function.default.service_config[0].uri service_account_email = "service-${data.google_project.project.number}@gcp-sa-apigateway.iam.gserviceaccount.com" })) } } depends_on = [google_cloudfunctions2_function.default] lifecycle { create_before_destroy = true } } # Create the Gateway resource "google_api_gateway_gateway" "gateway" { provider = google-beta gateway_id = "my-api-gateway" api_config = google_api_gateway_api_config.my-api-config.id depends_on = [ google_api_gateway_api_config.my-api-config, google_cloudfunctions2_function.default ] } # Outputs ################################################################################## output "function_uri" { value = "Function - [${google_cloudfunctions2_function.default.service_config[0].uri}]" } output "api_gateway_url" { value = "Gateway - [https://${google_api_gateway_gateway.gateway.default_hostname}]" } output "pipeline_api_id" { value = "Pipeline API ID - [${google_api_gateway_api.my-api.id}]" }
OpenAPI规范模板
swagger: '2.0' info: title: my-api API for all operations description: API for all operations on Cloud Functions version: 1.0.0 schemes: - https produces: - application/json paths: /hello: get: summary: Greet a user operationId: hello x-google-backend: address: "${backend_address}" # Set to the cloud function url path_translation: APPEND_PATH_TO_ADDRESS security: - api_key: [] responses: '200': description: A successful response schema: type: string securityDefinitions: # This section configures basic authentication with an API key. api_key: type: "apiKey" name: "key" in: "query"
排查关键点
确认API启用状态
- 使用gcloud命令验证目标API是否真的启用:
gcloud services describe my-api-0esofolowrs06.apigateway.propane-crawler-437508-t1.cloud.goog --project=propane-crawler-437508-t1 - 检查输出中的
state字段是否为ENABLED,若为DISABLED则重新启用并等待5-10分钟生效(服务启用存在延迟)。
- 使用gcloud命令验证目标API是否真的启用:
修正Cloud Function准入规则
- 当前CF设置
ALLOW_INTERNAL_ONLY,仅允许同VPC内部资源访问,而API Gateway默认是外部托管服务,无法直接访问。需将准入规则改为ALLOW_INTERNAL_AND_GCP_SERVICES,允许GCP托管服务(如API Gateway)通过服务账号调用:service_config { # ... 其他配置 ingress_settings = "ALLOW_INTERNAL_AND_GCP_SERVICES" }
- 当前CF设置
完善OpenAPI服务账号配置
- 在OpenAPI的
x-google-backend中明确指定网关使用的服务账号,确保调用CF时身份正确:x-google-backend: address: "${backend_address}" path_translation: APPEND_PATH_TO_ADDRESS service_account: "${service_account_email}" - 注意:此处
service_account_email应使用自定义的网关SA(google_service_account.my-gateway.email),而非API Gateway默认SA,保持与Terraform中gateway_config.backend_config的配置一致。
- 在OpenAPI的
调整权限配置范围
- 移除项目级的
roles/run.invoker权限,改为直接给CF添加网关SA的调用权限,避免过度授权且确保权限精准生效:resource "google_cloudfunctions2_function_iam_member" "gateway_invoker" { project = var.project_id location = google_cloudfunctions2_function.default.location cloud_function = google_cloudfunctions2_function.default.name role = "roles/cloudfunctions.invoker" member = "serviceAccount:${google_service_account.my-gateway.email}" depends_on = [google_cloudfunctions2_function.default] } - 删除不必要的
allUsers权限配置,符合最小权限原则。
- 移除项目级的
添加服务启用延迟等待
- API Gateway服务启用后存在生效延迟,需添加
time_sleep资源确保服务完全启用后再创建API资源:resource "time_sleep" "wait_for_apigateway_service" { create_duration = "300s" # 等待5分钟 depends_on = [google_project_service.apigateway] } resource "google_api_gateway_api" "my-api" { provider = google-beta api_id = "my-api" depends_on = [time_sleep.wait_for_apigateway_service] }
- API Gateway服务启用后存在生效延迟,需添加
验证API密钥关联
- 登录GCP控制台,进入「API和服务」→「凭据」→「API密钥」,检查使用的API密钥是否已绑定到
my-api-0esofolowrs06.apigateway.propane-crawler-437508-t1.cloud.goog这个API,未绑定则添加关联。
- 登录GCP控制台,进入「API和服务」→「凭据」→「API密钥」,检查使用的API密钥是否已绑定到
内容的提问来源于stack exchange,提问作者Aaron Provis
相关产品推荐
相关产品推荐

