4

我正在尝试使用此处文档中所述的 Cloud Tasks 调用 Cloud Run 服务。

我有一个正在运行的 Cloud Run 服务。如果我使该服务可公开访问,它会按预期运行。

我创建了一个云队列,并使用本地脚本安排了云任务。这个是用我自己的账号。脚本看起来像这样

from google.cloud import tasks_v2

client = tasks_v2.CloudTasksClient()

project = 'my-project'
queue = 'my-queue'
location = 'europe-west1'
url = 'https://url_to_my_service'

parent = client.queue_path(project, location, queue)

task = {
        'http_request': {
            'http_method': 'GET',
            'url': url,
            'oidc_token': {
               'service_account_email': 'my-service-account@my-project.iam.gserviceaccount.com'
            }
        }
}

response = client.create_task(parent, task)
print('Created task {}'.format(response.name))

我看到任务出现在队列中,但它失败并立即重试。原因(通过检查日志)是 Cloud Run 服务返回 401 响应。

我自己的用户具有“服务帐户令牌创建者”和“服务帐户用户”的角色。它没有明确的“Cloud Tasks Enqueuer”,但由于我能够在队列中创建任务,我想我已经继承了所需的权限。服务帐户“my-service-account@my-project.iam.gserviceaccount.com”(我在任务中用于获取 OIDC 令牌)具有以下角色:

  • Cloud Tasks Enqueuer(虽然我认为它不需要这个,因为我正在使用自己的帐户创建任务)
  • 云任务任务运行器
  • 云任务查看器
  • 服务帐户令牌创建者(我不确定这是否应该添加到我自己的帐户 - 安排任务的帐户 - 或应该执行调用 Cloud Run 的服务帐户)
  • 服务帐户用户(此处相同)
  • Cloud Run 调用程序

所以我做了一个肮脏的把戏:我为服务帐户创建了一个密钥文件,在本地下载它并通过在我的 gcloud 配置中添加一个带有密钥文件的帐户来模拟本地。接下来,我跑

curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://url_to_my_service

这样可行!(顺便说一句,当我切换回自己的帐户时,它也可以使用)

最终测试:如果我oidc_token在创建任务时从任务中删除 ,我会收到来自 Cloud Run 的 403 响应!不是 401...如果我从服务帐户中删除“Cloud Run Invoker”角色并使用 curl 在本地重试,我也会得到 403 而不是 401。

如果我最终使 Cloud Run 服务可以公开访问,那么一切正常。

因此,Cloud Task 似乎无法为服务帐户生成令牌以在 Cloud Run 服务上正确进行身份验证。

我错过了什么?

4

4 回答 4

3

我有同样的问题,这是我的修复:

诊断:生成 OIDC 令牌当前不支持audience参数中的自定义域。我正在为我的云运行服务 ( https://my-service.my-domain.com )使用自定义域,而不是如下所示的云运行生成的 url(在云运行服务仪表板中找到):https://XXXXXX.run.app

屏蔽行为:在排队到 Cloud Tasks 的任务中,如果audienceoidc_token 的字段未显式设置,则任务中的目标 urlaudience用于在 OIDC 令牌的请求中设置。

在我的情况下,这意味着将要发送给目标https://my-service.my-domain.com/resource受众的任务排入队列以生成 OIDC 令牌设置为我的自定义域https://my-service.my-domain.com/resource。由于生成 OIDC 令牌时不支持自定义域,因此我收到401 not authorized了来自目标服务的响应。

我的解决方法:使用 Cloud Run 生成的 URL 显式填充受众,以便发出有效的令牌。在我的客户端中,我能够使用基本 url: 为针对给定服务的所有任务全局设置受众'audience' : 'https://XXXXXX.run.app'。这生成了一个有效的令牌。我不需要更改目标资源本身的 url。资源保持不变:'url' : 'https://my-service.my-domain.com/resource'

更多阅读: 我在设置服务到服务身份验证之前遇到过这个问题:Google Cloud Run Authentication Service-to-Service

于 2020-05-15T00:19:39.550 回答
2

1.我使用以下代码创建了一个私有云运行服务:

import os

from flask import Flask
from flask import request


app = Flask(__name__)

@app.route('/index', methods=['GET', 'POST'])
def hello_world():
    target = os.environ.get('TARGET', 'World')
    print(target)
    return str(request.data)

if __name__ == "__main__":
    app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
   

2.我创建了一个服务帐户--role=roles/run.invoker,我将与云任务相关联

 gcloud iam service-accounts create SERVICE-ACCOUNT_NAME \
 --display-name "DISPLAYED-SERVICE-ACCOUNT_NAME"  
 gcloud iam service-accounts list

 gcloud run services add-iam-policy-binding SERVICE \
 --member=serviceAccount:SERVICE-ACCOUNT_NAME@PROJECT-ID.iam.gserviceaccount.com \ 
 --role=roles/run.invoker 

3.我创建了一个队列

gcloud tasks queues create my-queue

4.我创建一个test.py

from google.cloud import tasks_v2
from google.protobuf import timestamp_pb2
import datetime

# Create a client.
client = tasks_v2.CloudTasksClient()

# TODO(developer): Uncomment these lines and replace with your values.
project = 'your-project'
queue = 'your-queue'
location = 'europe-west2' # app engine locations
url = 'https://helloworld/index'
payload = 'Hello from the Cloud Task'

# Construct the fully qualified queue name.
parent = client.queue_path(project, location, queue)

# Construct the request body.
task = {
        'http_request': {  # Specify the type of request.
            'http_method': 'POST',
            'url': url,  # The full url path that the task will be sent to.
            'oidc_token': {
                'service_account_email': "your-service-account"
            },
             'headers' : {
             'Content-Type': 'application/json',
           }
        }
}

# Convert "seconds from now" into an rfc3339 datetime string.
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)

# Create Timestamp protobuf.
timestamp = timestamp_pb2.Timestamp()
timestamp.FromDatetime(d)

# Add the timestamp to the tasks.
task['schedule_time'] = timestamp
task['name'] = 'projects/your-project/locations/app-engine-loacation/queues/your-queue/tasks/your-task'


converted_payload = payload.encode()

# Add the payload to the request.
task['http_request']['body'] = converted_payload


# Use the client to build and send the task.
response = client.create_task(parent, task)

print('Created task {}'.format(response.name))
#return response

5.我使用具有所有者角色的用户帐户在 Google Cloud Shell 中运行代码。

6.收到的回复格式如下:

Created task projects/your-project/locations/app-engine-loacation/queues/your-queue/tasks/your-task

7.查看日志,成功

在此处输入图像描述

于 2020-04-10T10:38:35.420 回答
2

第二天,我不再能够重现此问题。我可以通过删除 Cloud Run Invoker 角色来重现 403 响应,但我不再获得与昨天完全相同的代码的 401 响应。我想这是谷歌方面的临时问题?

此外,我注意到更新的策略实际到位需要一些时间(1 到 2 分钟)。

于 2020-04-10T19:39:58.903 回答
1

对于像我这样的人来说,在UNAUTHORIZED对 Cloud Tasks HTTP 请求进行连续响应时,会在文档和 stackoverflow 中苦苦挣扎:

正如线程中所写,您最好audienceoidcToken您发送到 CloudTasks。确保您请求的 url 与您的资源完全相同。

例如,如果您命名了 Cloud Functionmy-awesome-cloud-function并且您的任务请求 url 是https://REGION-PROJECT-ID.cloudfunctions.net/my-awesome-cloud-function/api/v1/hello,则需要确保您设置了函数 url 本身。

{ 
  serviceAccountEmail: SERVICE-ACCOUNT_NAME@PROJECT-ID.iam.gserviceaccount.com,
  audience: https://REGION-PROJECT-ID.cloudfunctions.net/my-awesome-cloud-function 
}

否则似乎使用了完整的 url 并导致错误。

于 2021-07-09T13:49:47.133 回答