寻求无需向托管商暴露凭据的GCP服务账号认证优化方案
无需暴露GCP服务账号凭据的Cloud Function认证方案
针对你部署在Akash平台的Express服务需要调用GCP Cloud Function,但不想把服务账号凭据暴露给托管商的问题,以下是几个可行的方案:
方案1:使用工作负载身份联盟(Workload Identity Federation)
这是GCP官方推荐的无密钥认证方案,允许第三方平台(如Akash/Spheron)的服务直接通过身份联盟获取临时访问凭证,无需存储长期服务账号密钥。
配置步骤:
- 在GCP控制台创建工作负载身份池和对应的身份提供商,提供商类型选择「OpenID Connect(OIDC)」,填写Spheron/Akash提供的OIDC issuer URL(如果平台支持OIDC身份输出)。
- 为身份池中的身份绑定GCP IAM角色,授予调用目标Cloud Function的权限(比如
roles/cloudfunctions.invoker)。 - 在Express服务的代码中,使用Google Auth库通过工作负载身份联盟认证,无需传入服务账号JSON:
var express = require("express"); var app = express(); require("dotenv").config(); const GoogleAuth = require("google-auth-library").GoogleAuth; const cors = require("cors"); app.use( cors({ origin: process.env.ORIGIN, credentials: process.env.CREDENTIALS }) ); app.get("/hello", async function (req, res) { const auth = new GoogleAuth({ // 使用工作负载身份联盟配置,无需硬编码凭据 workloadIdentityPoolId: process.env.WORKLOAD_POOL_ID, providerId: process.env.WORKLOAD_PROVIDER_ID, audience: process.env.WORKLOAD_AUDIENCE, subjectToken: await getPlatformSubjectToken(), // 从Spheron/Akash获取当前实例的身份令牌 }); const url = process.env.RUN_APP_URL; const client = await auth.getIdTokenClient(url); const result = await client.request({ url }); const resData = result.data; res.send(resData); }); // 模拟从托管平台获取身份令牌的函数(具体实现需参考Spheron/Akash的文档) async function getPlatformSubjectToken() { // 这里替换为平台提供的获取OIDC令牌的逻辑 return process.env.PLATFORM_OIDC_TOKEN; } var server = app.listen(8081, function () { var host = server.address().address; var port = server.address().port; console.log("Example app listening at http://localhost:", port); });
优势:
- 无需存储长期服务账号密钥,托管商无法获取敏感凭据;
- 临时凭证自动过期,降低泄露风险;
- 符合GCP安全最佳实践。
方案2:将认证逻辑移至前端(类比Netlify静态客户端+Function模式)
如果你的应用架构允许,可让前端客户端直接完成身份认证并调用Cloud Function,Akash上的Express服务仅做路由转发或完全不参与认证流程:
配置步骤:
- 在GCP控制台配置Cloud Function的权限,允许经过Google OAuth/Firebase Auth认证的用户调用(添加
allAuthenticatedUsers为cloudfunctions.invoker角色,或更精细的用户组权限)。 - 前端通过Google Sign-In或Firebase Auth获取ID Token,直接调用Cloud Function的HTTP端点:
// 前端示例代码(浏览器环境) async function callCloudFunction() { const idToken = await firebase.auth().currentUser.getIdToken(); const response = await fetch(process.env.CLOUD_FUNCTION_URL, { method: "GET", headers: { "Authorization": `Bearer ${idToken}` } }); const data = await response.json(); return data; }
- 如果需要Express服务做中间转发,仅需透传前端的ID Token,无需自身持有GCP凭据:
// 修改后的Express代码 app.get("/hello", async function (req, res) { const idToken = req.headers.authorization?.split(" ")[1]; if (!idToken) { return res.status(401).send("Unauthorized"); } const url = process.env.RUN_APP_URL; const result = await fetch(url, { headers: { "Authorization": `Bearer ${idToken}` } }); const resData = await result.json(); res.send(resData); });
优势:
- 后端服务完全不需要持有任何GCP凭据,彻底避免托管商访问敏感信息;
- 符合静态前端+无服务器函数的架构模式,降低后端复杂度。
方案3:使用GCP Cloud Run作为中间代理(可选)
如果必须保留后端服务的认证逻辑,可将敏感的GCP认证逻辑迁移至GCP Cloud Run服务,Akash上的Express服务仅调用Cloud Run的公开接口,而Cloud Run通过内置的服务账号自动认证(无需手动存储凭据):
- 部署一个Cloud Run服务,内部完成调用Cloud Function的逻辑,Cloud Run使用默认服务账号(或自定义服务账号)并配置对应权限。
- Akash上的Express服务直接调用Cloud Run的HTTP端点,无需处理GCP认证:
// 修改后的Express代码 app.get("/hello", async function (req, res) { const cloudRunUrl = process.env.CLOUD_RUN_URL; const result = await fetch(cloudRunUrl); const resData = await result.json(); res.send(resData); });
优势:
- 敏感认证逻辑完全在GCP生态内,第三方托管商无法接触;
- Cloud Run自动处理服务账号认证,无需手动管理凭据。
内容的提问来源于stack exchange,提问作者ChristianOConnor
相关产品推荐
相关产品推荐

