如何获取ServiceAccount私钥生成PreSigned URL?遇签名凭证报错
Cloud Run环境生成GCS预签名URL失败的解决方法
问题背景
本地通过ServiceAccount JSON密钥文件生成GCS用户头像预签名URL正常,但在Cloud Run环境中遇到两个问题:
- 使用Secret Manager存储SA密钥时,获取到的是密钥内容而非文件路径,无法直接适配原有代码;
- 使用
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密钥内容创建凭据
操作步骤
- 给Cloud Run绑定的服务账号授予
Secret Manager Secret Accessor权限,确保能读取存储SA密钥的Secret; - 读取Secret中的JSON内容并解析为字典,基于此创建带私钥的ServiceAccount凭据;
- 使用该凭据完成预签名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密钥,减少泄露风险:
前置配置
- 创建一个专用的签名服务账号,授予
roles/storage.objectViewer权限(确保能访问目标GCS对象); - 给Cloud Run绑定的服务账号授予
roles/iam.workloadIdentityUser权限,允许它模拟上述专用签名SA; - 给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
相关产品推荐
相关产品推荐

