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 令牌)具有以下角色:
- Cloud Tasks Enqueuer(虽然我认为它不需要这个,因为我正在用自己的帐户创建任务)
- Cloud Tasks 任务 运行ner
- 云任务查看器
- Service Account Token Creator(我不确定是否应该将其添加到我自己的帐户 - 安排任务的帐户 - 或应添加到应该执行云调用的服务帐户 运行)
- 服务帐户用户(此处相同)
- 云运行调用者
所以我做了一个卑鄙的把戏:我为服务帐户创建了一个密钥文件,将其下载到本地并通过使用密钥文件向我的 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 被使用并导致错误。
我正在尝试使用云任务调用云 运行 服务,如文档 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 令牌)具有以下角色:
- Cloud Tasks Enqueuer(虽然我认为它不需要这个,因为我正在用自己的帐户创建任务)
- Cloud Tasks 任务 运行ner
- 云任务查看器
- Service Account Token Creator(我不确定是否应该将其添加到我自己的帐户 - 安排任务的帐户 - 或应添加到应该执行云调用的服务帐户 运行)
- 服务帐户用户(此处相同)
- 云运行调用者
所以我做了一个卑鄙的把戏:我为服务帐户创建了一个密钥文件,将其下载到本地并通过使用密钥文件向我的 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 被使用并导致错误。