You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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"

排查关键点

  1. 确认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分钟生效(服务启用存在延迟)。
  2. 修正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"
      }
      
  3. 完善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的配置一致。
  4. 调整权限配置范围

    • 移除项目级的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权限配置,符合最小权限原则。
  5. 添加服务启用延迟等待

    • 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]
      }
      
  6. 验证API密钥关联

    • 登录GCP控制台,进入「API和服务」→「凭据」→「API密钥」,检查使用的API密钥是否已绑定到my-api-0esofolowrs06.apigateway.propane-crawler-437508-t1.cloud.goog这个API,未绑定则添加关联。

内容的提问来源于stack exchange,提问作者Aaron Provis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 08:54:52