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

如何获取ServiceAccount私钥生成PreSigned URL?遇签名凭证报错

Cloud Run环境生成GCS预签名URL失败的解决方法

问题背景

本地通过ServiceAccount JSON密钥文件生成GCS用户头像预签名URL正常,但在Cloud Run环境中遇到两个问题:

  1. 使用Secret Manager存储SA密钥时,获取到的是密钥内容而非文件路径,无法直接适配原有代码;
  2. 使用compute_engine.Credentials()时触发错误:

"An error occurred: you need a private key to sign credentials.the credentials you are currently using <class 'google.auth.compute_engine.credentials.Credentials'> just contains a token"

原因分析

  • Cloud Run默认的元数据服务提供的Compute Engine凭据仅包含访问令牌,没有私钥,而V4版本的预签名URL生成必须依赖私钥完成签名操作;
  • Secret Manager存储的是SA密钥的JSON文本内容,不是本地文件路径,无法直接被storage.Client识别为凭据文件。

解决方案一:从Secret Manager加载SA密钥内容创建凭据

操作步骤

  1. 给Cloud Run绑定的服务账号授予Secret Manager Secret Accessor权限,确保能读取存储SA密钥的Secret;
  2. 读取Secret中的JSON内容并解析为字典,基于此创建带私钥的ServiceAccount凭据;
  3. 使用该凭据完成预签名URL生成。

代码示例

import os
import json
from datetime import timedelta
from google.cloud import storage
from google.cloud import secretmanager
from google.oauth2 import service_account

# 从环境变量读取配置
bucket_name = os.environ.get("BUCKET_NAME")
service_account_email = os.environ.get("SERVICE_ACCOUNT")
sa_key_secret_name = os.environ.get("SA_KEY_SECRET_NAME")  # 存储SA密钥的Secret名称
project_id = os.environ.get("PROJECT_ID")

def get_service_account_credentials():
    # 从Secret Manager读取SA密钥JSON内容
    secret_client = secretmanager.SecretManagerServiceClient()
    secret_version_path = secret_client.secret_version_path(project_id, sa_key_secret_name, "latest")
    secret_response = secret_client.access_secret_version(request={"name": secret_version_path})
    sa_key_json = secret_response.payload.data.decode("UTF-8")
    sa_key_info = json.loads(sa_key_json)
    
    # 创建带私钥的服务账号凭据
    return service_account.Credentials.from_service_account_info(sa_key_info)

def generate_presigned_url_for_profile(blob_name, expiration=3600):
    credentials = get_service_account_credentials()
    storage_client = storage.Client(credentials=credentials, project=project_id)
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(f"userProfile/{blob_name}")
    
    return blob.generate_signed_url(
        version="v4",
        expiration=timedelta(seconds=expiration),
        method='GET',
        service_account_email=service_account_email
    )

解决方案二:使用Workload Identity(推荐,无需管理密钥)

这是GCP官方推荐的安全方案,避免手动管理SA密钥,减少泄露风险:

前置配置

  1. 创建一个专用的签名服务账号,授予roles/storage.objectViewer权限(确保能访问目标GCS对象);
  2. 给Cloud Run绑定的服务账号授予roles/iam.workloadIdentityUser权限,允许它模拟上述专用签名SA;
  3. 给Cloud Run绑定的服务账号授予roles/iam.serviceAccountTokenCreator权限,允许它调用专用SA的签名接口。

代码示例

import os
from datetime import timedelta
from google.cloud import storage

# 从环境变量读取配置
bucket_name = os.environ.get("BUCKET_NAME")
signing_service_account = os.environ.get("SIGNING_SERVICE_ACCOUNT")  # 专用签名SA的邮箱
project_id = os.environ.get("PROJECT_ID")

def generate_presigned_url_for_profile(blob_name, expiration=3600):
    # 使用Cloud Run默认凭据,无需手动加载密钥
    storage_client = storage.Client(project=project_id)
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(f"userProfile/{blob_name}")
    
    return blob.generate_signed_url(
        version="v4",
        expiration=timedelta(seconds=expiration),
        method='GET',
        service_account_email=signing_service_account,
        credentials=storage_client._credentials
    )

内容的提问来源于stack exchange,提问作者Tubagus Farhan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:07:13