【问题标题】:Google Cloud Tasks cannot authenticate to Cloud RunGoogle Cloud Tasks 无法向 Cloud Run 进行身份验证
【发布时间】:2020-07-22 08:13:41
【问题描述】:

我正在尝试使用 Cloud Tasks 调用 Cloud Run 服务,如文档 here 中所述。

我有一个正在运行的 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 服务上正确进行身份验证。

我错过了什么?

【问题讨论】:

  • 我也是.. 遵循了文档:cloud.google.com/tasks/docs/creating-http-target-tasks,但我收到了来自目标服务的 401 响应。将任务加入队列的服务帐户应该只需要权限 1.Cloud Tasks Enqueuer 2.Service Account User 3.Cloud Run Invoker(或您所针对的任何谷歌服务的调用者)。入队服务帐户电子邮件在入队之前被添加到任务中,以便 Cloud Tasks Queue 可以使用它来生成令牌......我将看看这个问题是否像你的那样在 24 小时内解决。这太令人沮丧了
  • 也许还值得注意:我最近在尝试从 Cloud Scheduler 触发 Cloud Run 时也收到了 401 响应。我也在使用 OIDC 令牌,结果发现我在观众中设置了错误的 URL。对于 Cloud Tasks,获取 OIDC 令牌似乎是在幕后进行的,但我现在感觉那里出了点问题。
  • 我想通了。对我来说令人沮丧的是我之前必须解决这个问题......如果您没有为oidc_token 明确填充audience 字段,则使用任务中的目标url,在上面的示例中: https://url_to_my_service。这里的问题是,如果您将 Cloud Run 与自定义域(而不是云运行生成的域)一起使用,那么您将收到错误消息,因为 OIDC audience 不支持自定义域。我的解决方法是使用 Cloud Run 生成的 URL 显式填充受众,然后它就起作用了。

标签: service-accounts google-cloud-run google-cloud-tasks


【解决方案1】:

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

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

屏蔽行为:在排队到 Cloud Tasks 的任务中,如果 oidc_token 的 audience 字段未明确设置,则任务中的目标 url 用于设置 audience在 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

【讨论】:

  • 感谢您的分析,它帮助我意识到我的问题与观众有关。具体来说,使用云函数作为处理程序但带有路径后缀,我需要明确设置受众无路径后缀才能使其工作。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    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.查看日志,成功

    【讨论】:

    • 感谢分享。我看到的唯一区别是您将角色绑定到服务帐户的方式。当您使用 gcloud 命令时,我使用了控制台 UI。那会有什么不同吗?
    【解决方案4】:

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

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

    例如,如果您有名为 my-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 并导致错误。

    【讨论】:

      猜你喜欢
      • 2020-03-17
      • 2020-01-24
      • 2019-09-27
      • 2020-05-31
      • 2021-10-15
      • 2020-02-29
      • 2022-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多