Google Cloud Tasks 无法向 Cloud 进行身份验证 运行

Google Cloud Tasks cannot authenticate to Cloud Run

我正在尝试使用云任务调用云 运行 服务,如文档 here 中所述。

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

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

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 运行 服务 returns 401 响应。

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

所以我做了一个卑鄙的把戏:我为服务帐户创建了一个密钥文件,将其下载到本地并通过使用密钥文件向我的 gcloud 配置添加一个帐户来在本地模拟。接下来,我运行

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

行得通! (顺便说一句,当我切换回自己的帐户时它也有效)

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

如果我最终使 Cloud 运行 服务可公开访问,一切正常。

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

我错过了什么?

1.I 使用此代码创建了私有云 run service

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.I 使用 --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.I 创建了队列

gcloud tasks queues create my-queue

4.I 创建一个 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.I 运行 Google Cloud Shell 中的代码,我的用户帐户具有 Owner 角色。

6.The 收到的响应格式为:

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

7.Check 日志,成功

第二天我无法再重现这个问题。我可以通过删除 Cloud 运行 Invoker 角色来重现 403 响应,但我不再使用与昨天完全相同的代码获得 401 响应。 我想这是 Google 方面的临时问题?

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

我遇到了同样的问题,这是我的解决方法:

诊断: 生成 OIDC 令牌目前不支持 audience 参数中的自定义域。我为我的云 运行 服务 (https://my-service.my-domain.com) 使用自定义域而不是云 运行 生成的 url (在云 运行 服务仪表板中找到)看起来像这样:https://XXXXXX.run.app

屏蔽行为: 在排队到 Cloud Tasks 的任务中,如果未明确设置 oidc_token 的 audience 字段,则目标 url 来自任务用于在 OIDC 令牌请求中设置 audience

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

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

更多阅读: 在设置服务到服务身份验证之前,我 运行 遇到过这个问题:

对于像我这样的人,在对 Cloud Tasks HTTP 请求进行连续 UNAUTHORIZED 响应时努力浏览文档和计算器:

如线程中所写,您最好为发送到 CloudTasks 的 oidcToken 提供 audience。确保您请求的 url 完全等于您的资源。

例如,如果您有一个名为 my-awesome-cloud-function 的 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 被使用并导致错误。