我试图按照文档here中所述使用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令牌)(具有以下作用):
因此,我做了一个肮脏的把戏:我为服务帐户创建了一个密钥文件,在本地下载了该文件,并通过向该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服务上正确进行身份验证。
我想念什么?
答案 0 :(得分:3)
我的解决方法与此相同:
诊断:目前,在audience
参数中,生成OIDC令牌不支持自定义域。我为我的云运行服务(https://my-service.my-domain.com)使用了自定义域,而不是云运行生成的url(位于云运行服务仪表板中),它看起来像这样:https://XXXXXX.run.app
伪装行为::在排队到Cloud Tasks中的任务中,如果未明确设置oidc_token的audience
字段,则使用任务中的目标网址来设置{ {1}}在OIDC令牌请求中。
在我的情况下,这意味着将要发送给目标audience
的任务排队,将用于生成OIDC令牌的受众设置为我的自定义域https://my-service.my-domain.com/resource
。由于生成OIDC令牌时不支持自定义域,因此我收到了来自目标服务的https://my-service.my-domain.com/resource
响应。
我的修正:使用Cloud Run生成的URL显式填充受众,以便发出有效令牌。在我的客户中,我能够使用基本URL:401 not authorized
全局设置针对给定服务的所有任务的受众。这产生了一个有效的令牌。我不需要更改目标资源本身的url。资源保持不变:'audience' : 'https://XXXXXX.run.app'
更多阅读内容: 在设置服务到服务的身份验证之前,我已经遇到了这个问题:Google Cloud Run Authentication Service-to-Service
答案 1 :(得分:1)
1。我使用以下代码创建了私有云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。我使用--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。检查日志,成功
答案 2 :(得分:1)
第二天,我不再能够重现此问题。我可以通过删除Cloud Run Invoker角色来重现403响应,但是我再也无法获得具有与昨天完全相同的代码的401响应。 我想这只是Google的临时问题?
我还注意到,实际上要花一些时间才能执行更新的策略(1-2分钟)。
答案 3 :(得分:1)
对于像我这样的人,在 Cloud Tasks HTTP 请求上有连续的 UNAUTHORIZED
响应时,在文档和 stackoverflow 中苦苦挣扎:
正如线程中所写,您最好为发送到 CloudTasks 的 audience
提供 oidcToken
。确保您请求的网址与您的资源完全相同。
例如,如果您有名为 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 并导致错误。