EC2上Dialogflow API权限问题:已确认服务账号密钥与角色
问题
部署在EC2实例上的Node.js(NestJs)应用调用Dialogflow API时触发权限错误,本地环境使用相同服务账号密钥和角色可正常调用,但EC2上失败。
详情
- 使用Dialogflow Node.js客户端库
- 服务账号已分配Dialogflow Admin API角色和Owner角色
- 同一服务账号密钥在EC2上可正常调用其他Google Cloud服务(如Speech to Text)
- EC2实例具备网络访问权限,无明显网络问题
- 已设置
GOOGLE_APPLICATION_CREDENTIALS环境变量
已执行的排查步骤
- 确认Node.js项目中Dialogflow API版本正确
- 在代码中显式设置凭证路径
- 尝试重新创建服务账号密钥
错误信息
Error: 7 PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' on 'projects/cropsea-xseb/agent' denied. Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at callErrorFromStatus (/var/app/current/node_modules/@grpc/grpc-js/build/src/call.js:31:19) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at Object.onReceiveStatus (/var/app/current/node_modules/@grpc/grpc-js/build/src/client.js:192:76) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at Object.onReceiveStatus (/var/app/current/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at Object.onReceiveStatus (/var/app/current/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:323:181) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at /var/app/current/node_modules/@grpc/grpc-js/build/src/resolving-call.js:99:78 Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at process.processTicksAndRejections (node:internal/process/task_queues:77:11) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: for call at Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at ServiceClientImpl.makeUnaryRequest (/var/app/current/node_modules/@grpc/grpc-js/build/src/client.js:160:32) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at ServiceClientImpl.<anonymous> (/var/app/current/node_modules/@grpc/grpc-js/build/src/make-client.js:105:19) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at /var/app/current/node_modules/@google-cloud/dialogflow/build/src/v2/sessions_client.js:241:29 Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at /var/app/current/node_modules/google-gax/build/src/normalCalls/timeout.js:44:16 Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at repeat (/var/app/current/node_modules/google-gax/build/src/normalCalls/retries.js:80:25) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at /var/app/current/node_modules/google-gax/build/src/normalCalls/retries.js:118:13 Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at OngoingCallPromise.call (/var/app/current/node_modules/google-gax/build/src/call.js:67:27) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at NormalApiCaller.call (/var/app/current/node_modules/google-gax/build/src/normalCalls/normalApiCaller.js:34:19) Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at /var/app/current/node_modules/google-gax/build/src/createApiCall.js:84:30 Dec 2 15:03:16 ip-172-31-3-220 web[36519]: at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { Dec 2 15:03:16 ip-172-31-3-220 web[36519]: code: 7, Dec 2 15:03:16 ip-172-31-3-220 web[36519]: details: "IAM permission 'dialogflow.sessions.detectIntent' on 'projects/cropsea-xseb/agent' denied.", Dec 2 15:03:16 ip-172-31-3-220 web[36519]: metadata: Metadata { Dec 2 15:03:16 ip-172-31-3-220 web[36519]: internalRepr: Map(2) { Dec 2 15:03:16 ip-172-31-3-220 web[36519]: 'endpoint-load-metrics-bin' => [Array], Dec 2 15:03:16 ip-172-31-3-220 web[36519]: 'grpc-server-stats-bin' => [Array] Dec 2 15:03:16 ip-172-31-3-220 web[36519]: }, Dec 2 15:03:16 ip-172-31-3-220 web[36519]: options: {} Dec 2 15:03:16 ip-172-31-3-220 web[36519]: }, Dec 2 15:03:16 ip-172-31-3-220 web[36519]: note: 'Exception occurred in retry method that was not classified as transient' Dec 2 15:03:16 ip-172-31-3-220 web[36519]: } 7 PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' on 'projects/cropsea-xseb/agent' denied. {"code":7,"details":"IAM permission 'dialogflow.sessions.detectIntent' on 'projects/cropsea-xseb/agent' denied.","metadata":{"endpoint-load-metrics-bin":[{"type":"Buffer","data":[49,215,148,34,241,30,74,31,64,57,48,169,63,59,54,89,185,63,73,182,174,237,235,80,251,189,63]}],"grpc-server-stats-bin":[{"type":"Buffer","data":[0,0,130,48,50,18,0,0,0,0]}]},"note":"Exception occurred in retry method that was not classified as transient"}
代码片段
try { let sessionId = await this.getSessionId(userId); if (!sessionId) { sessionId = uuid(); // Save sessionId to DB await this.saveSessionId(userId, sessionId as string); } const sessionPath = this.sessionClient.projectAgentSessionPath( process.env.PROJECT_ID as string, sessionId as string, ); const request = { session: sessionPath, queryInput: { text: { text: text, languageCode: defaultLocale, // Adjust as needed }, }, }; const [response] = await this.sessionClient.detectIntent(request);
排查与解决步骤
验证EC2上使用的服务账号身份
在EC2实例上执行命令,确认当前使用的服务账号是否为预期的那个:gcloud auth list或者获取应用默认访问令牌后,查看令牌对应的账号信息,确保和本地使用的一致。
检查Dialogflow Agent的权限绑定
进入Dialogflow控制台,找到对应Agent,进入设置 -> 权限,确认服务账号已被添加到Agent的权限列表中,且分配了至少Dialogflow API Client级别的角色。确认环境变量与密钥文件有效性
在EC2的应用启动脚本中添加echo $GOOGLE_APPLICATION_CREDENTIALS命令,验证路径正确;同时检查密钥文件内容是否和本地完全一致,避免复制时出现格式错误或截断。等待IAM角色生效延迟
IAM角色变更最长可能有15分钟生效延迟,若最近修改过服务账号角色,等待一段时间后再测试。排查网络访问限制
在EC2上执行curl https://dialogflow.googleapis.com/v2/projects/cropsea-xseb/agent/sessions/test:detectIntent,验证是否能连通Dialogflow API;同时检查VPC端点是否配置了Dialogflow相关端点,防火墙规则是否允许访问dialogflow.googleapis.com。显式指定凭证初始化客户端
尝试在代码中直接加载密钥文件,绕过环境变量依赖,确保客户端使用正确凭证:const { SessionsClient } = require('@google-cloud/dialogflow'); const sessionClient = new SessionsClient({ keyFilename: '/path/to/your/service-account-key.json', });确认Dialogflow API启用状态
进入GCP控制台API库,搜索Dialogflow API,确认对应项目中该API处于启用状态。验证项目ID一致性
检查代码中process.env.PROJECT_ID的值是否和Dialogflow Agent所在项目ID完全一致,避免拼写错误导致访问错误资源。
内容的提问来源于stack exchange,提问作者user3338348

