如何验证 Python 客户端应用程序以访问受限的 Google 云功能?

How to authenticate Python client app for access to restricted Google Cloud function?

我创建了一个 Google 云函数,我想从我正在开发的 Python 应用程序访问它。不需认证时可以访问该功能,但启用认证后无法访问该功能

这是我使用的服务帐户密钥,其中包含已删除的信息。它配置的唯一角色是调用云功能。

{
  "type": "service_account",
  "project_id": "XYZ",
  "private_key_id": "XYZ",
  "private_key": "XYZ",
  "client_email": "XYZ",
  "client_id": "XYZ",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "XYZ"
}

经过身份验证的请求似乎需要包含在请求中的令牌 Authorization header,但我不知道从哪里获取此令牌。

我试过使用 here 概述的方法和环境变量以及默认的 auth 方法,但这不起作用。我认为这是因为服务帐户密钥不同于 OAuth 令牌。 (我已经创建了一个具有 Cloud Functions 调用者权限的新服务帐户,并且正在使用该服务帐户密钥)。我收到以下错误:

google.auth.exceptions.RefreshError: ('invalid_scope: Invalid OAuth scope or ID token audience provided.', '{"error":"invalid_scope","error_description":"Invalid OAuth scope or ID token audience provided."}')

如何生成此令牌来验证来自我的 Python 脚本的请求?还是建议使用服务帐户的方法,但其他地方出了问题?

您的问题几乎可以肯定是您赋予该服务帐户的角色。服务帐户很挑剔,根据我的经验,角色/权限并不总是按照您认为的方式行事。首先创建一个具有完全权限的服务帐户(项目所有者)。在您的脚本中使用该服务帐户,然后从那里开始限制权限。听起来您至少需要云功能“admin”。如果可行,请尝试降低另一个级别。云功能“开发者”等

如果您使用 App Engine 或其他 Cloud Functions 连接到您的 Cloud Functions,您可以使用这个:Function-to-function,步骤基本上是:

  1. 授予 Cloud Functions Invoker。

  2. 在调用函数中,您需要:

  3. 创建一个 Google-signed OAuth ID 令牌,并将受众 (aud) 设置为接收函数的 URL

  4. 在对函数的请求中的 Authorization: Bearer ID_TOKEN header 中包含 ID 令牌。

    导入请求

    //# TODO<developer>: set these values
    REGION = 'us-central1'
    PROJECT_ID = 'my-project'
    RECEIVING_FUNCTION = 'my-function'
    
    //# Constants for setting up metadata server request
    //# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
    function_url = f'https://{REGION}-{PROJECT_ID}.cloudfunctions.net/{RECEIVING_FUNCTION}'
    metadata_server_url = \
        'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
    token_full_url = metadata_server_url + function_url
    token_headers = {'Metadata-Flavor': 'Google'}
    
    
    def calling_function(request):
        //# Fetch the token
        token_response = requests.get(token_full_url, headers=token_headers)
        jwt = token_response.text
    
        //# Provide the token in the request to the receiving function
        function_headers = {'Authorization': f'bearer {jwt}'}
        function_response = requests.get(function_url, headers=function_headers)
    
        return function_response.text
    

我已经测试了这个解决方案并且按预期工作。

如果您从无法访问计算元数据的计算实例(例如您自己的服务器)调用函数,则必须手动生成正确的令牌:

  1. Self-sign 服务帐户 JWT,其 target_audience 声明设置为接收函数的 URL。

  2. 将 self-signed JWT 换成 Google-signed ID 令牌,它应该将 aud 声明设置为上述 URL.

  3. 在对函数的请求中的 Authorization: Bearer ID_TOKEN header 中包含 ID 令牌。

云 IAP 文档有 sample code to demonstrate this functionality. The part you could be interested in should be this Authenticating from a service account

虽然在撰写本文时 there is a bug in the documentation 让我失望了,但我能够使它正常工作。

使用 google-auth 库中的 IDTokenCredentials class 可以访问受保护的 Cloud Functions:

credentials = service_account.IDTokenCredentials.from_service_account_file( 
         SERVICE_ACCOUNT_JSON_FILE, 
         target_audience="https://function/url/here",
 ) 

authed_session = AuthorizedSession(credentials)
response = authed_session.post(url)

我的服务帐户配置了“Cloud Functions Invoker”角色。