【问题标题】:Error accessing a IAP resources when authenticating from a service account - 502 server error从服务帐户进行身份验证时访问 IAP 资源时出错 - 502 服务器错误
【发布时间】:2020-03-11 02:58:58
【问题描述】:

我们正在尝试实现服务器到服务器的身份验证并根据文档 here 访问 IAP 资源。

url = "https://project-name-B.appspot.com" # This is the IAP resource. This application is hosted in a different project. Lets call this as "Project B"
client_id = "client-id-number"                # This is the client id when you click on "Edit Oauth Client" in GCP console for the IAP resource.

response = authenticate_obj.make_iap_request(url, client_id)

我们在执行上面的代码时得到如下错误。

Traceback (most recent call last): File "/env/lib/python3.7/site-packages/gunicorn/workers/gthread.py", line 271, in handle keepalive = self.handle_request(req, conn) File "/env/lib/python3.7/site-packages/gunicorn/workers/gthread.py", line 320, in handle_request respiter = self.wsgi(environ, resp.start_response) 
File "/env/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__ return self.wsgi_app(environ, start_response) File "/env/lib/python3.7/site-packages/flask/app.py", 
line 2449, in wsgi_app response = self.handle_exception(e) 
File "/env/lib/python3.7/site-packages/flask/app.py", 
line 1866, in handle_exception reraise(exc_type, exc_value, tb) 
File "/env/lib/python3.7/site-packages/flask/_compat.py", 
line 39, in reraise raise value File "/env/lib/python3.7/site-packages/flask/app.py", 
line 2446, in wsgi_app response = self.full_dispatch_request() 
File "/env/lib/python3.7/site-packages/flask/app.py", 
line 1951, in full_dispatch_request rv = self.handle_user_exception(e) 
File "/env/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception reraise(exc_type, exc_value, tb) 
File "/env/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise raise value File "/env/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request rv = self.dispatch_request() File "/env/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "/srv/controllers/migratedata.py", line 84, in migrate_data response = authenticate_obj.make_iap_request(url, client_id) 
File "/srv/controllers/authentication/iap_authentication.py", line 107, in make_iap_request resp.status_code, resp.headers, resp.text)) 
Exception: Bad response from application: 502 / {'Content-Type': 'text/html; charset=UTF-8', 'Referrer-Policy': 'no-referrer', 'Content-Length': '1613', 'Date': 'Fri, 15 Nov 2019 00:44:06 GMT', 'Alt-Svc': 'quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000'} / '<!DOCTYPE html>\n<html lang=en>\n <meta charset=utf-8>\n <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
\n <title>Error 502 (Server Error)!!1</title>\n <style>\n *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}\n </style>\n <a href=//www.google.com/><span id=logo aria-label=Google></span></a>\n <p><b>502.</b> <ins>That’s an error.</ins>
\n <p>The server encountered a temporary error and could not complete your request.<p>Please try again in 30 seconds. <ins>That’s all we know.

我们发出请求的项目(我们称之为项目 A)有一个默认服务帐户 project-name-A@appspot.gserviceaccount.com。我们已为其添加了“服务帐户令牌创建者”角色。 我们正在按照文档 here 创建 JWT 并发出 IAP 请求。

url = "https://project-name-B.appspot.com"
client_id = "this is the cliennt id that we got from GCP console in the IAP resource (Project name B) - Edit Oauth Client"
    authenticate_obj = authenticate_server_to_server()
    response = authenticate_obj.make_iap_request(url, client_id)        

class authenticate_server_to_server()

    IAM_SCOPE = 'https://www.googleapis.com/auth/iam'
    OAUTH_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'

    def make_iap_request(url, client_id, method='POST', **kwargs):
    """Makes a request to an application protected by Identity-Aware Proxy.

    Args:
    url: The Identity-Aware Proxy-protected URL to fetch.
    client_id: The client ID used by Identity-Aware Proxy.
    method: The request method to use
        ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
    **kwargs: Any of the parameters defined for the request function:
        https://github.com/requests/requests/blob/master/requests/api.py
        If no timeout is provided, it is set to 90 by default.

    Returns:
        The page body, or raises an exception if the page couldn't be retrieved.
    """
    # Set the default timeout, if missing
    if 'timeout' not in kwargs:
        kwargs['timeout'] = 90

    # Figure out what environment we're running in and get some preliminary
    # information about the service account.
    bootstrap_credentials, _ = google.auth.default(
        scopes=[IAM_SCOPE])
    if isinstance(bootstrap_credentials,
        google.oauth2.credentials.Credentials):
        raise Exception('make_iap_request is only supported for service '
            'accounts.')
    elif isinstance(bootstrap_credentials,
        google.auth.app_engine.Credentials):
        requests_toolbelt.adapters.appengine.monkeypatch()

    # For service account's using the Compute Engine metadata service,
    # service_account_email isn't available until refresh is called.
    bootstrap_credentials.refresh(Request())

    signer_email = bootstrap_credentials.service_account_email
    if isinstance(bootstrap_credentials,
        google.auth.compute_engine.credentials.Credentials):
        # Since the Compute Engine metadata service doesn't expose the service
        # account key, we use the IAM signBlob API to sign instead.
        # In order for this to work:
        #
        # 1. Your VM needs the https://www.googleapis.com/auth/iam scope.
        #    You can specify this specific scope when creating a VM
        #    through the API or gcloud. When using Cloud Console,
        #    you'll need to specify the "full access to all Cloud APIs"
        #    scope. A VM's scopes can only be specified at creation time.
        #
        # 2. The VM's default service account needs the "Service Account Actor"
        #    role. This can be found under the "Project" category in Cloud
        #    Console, or roles/iam.serviceAccountActor in gcloud.
        signer = google.auth.iam.Signer(
            Request(), bootstrap_credentials, signer_email)
    else:
        # A Signer object can sign a JWT using the service account's key.
        signer = bootstrap_credentials.signer

    # Construct OAuth 2.0 service account credentials using the signer
    # and email acquired from the bootstrap credentials.
    service_account_credentials = google.oauth2.service_account.Credentials(
        signer, signer_email, token_uri=OAUTH_TOKEN_URI, additional_claims={
            'target_audience': client_id
        })

    # service_account_credentials gives us a JWT signed by the service
    # account. Next, we use that to obtain an OpenID Connect token,
    # which is a JWT signed by Google.

    authenticate_server_to_server_obj = authenticate_server_to_server()     
    google_open_id_connect_token = authenticate_server_to_server_obj.get_google_open_id_connect_token(service_account_credentials)

    # Fetch the Identity-Aware Proxy-protected URL, including an
    # Authorization header containing "Bearer " followed by a
    # Google-issued OpenID Connect token for the service account.
    # url = "https://" + url
    url = "https://project-name-B.appspot.com"
    resp = requests.request(
        method, url,
        headers={'Authorization': 'Bearer {}'.format(
        google_open_id_connect_token)}, **kwargs)
    if resp.status_code == 403:
        raise Exception('Service account {} does not have permission to '
            'access the IAP-protected application.'.format(
            signer_email))
    elif resp.status_code != 200:

        raise Exception(
            'Bad response from application: {!r} / {!r} / {!r}'.format(
            resp.status_code, resp.headers, resp.text))
        return resp.text
    else:
        return resp.text

我们使用以下函数来接收 OpenID Connect 令牌。

def get_google_open_id_connect_token(self,service_account_credentials):
    """Get an OpenID Connect token issued by Google for the service account.

    This function:

    1. Generates a JWT signed with the service account's private key
     containing a special "target_audience" claim.

    2. Sends it to the OAUTH_TOKEN_URI endpoint. Because the JWT in #1
     has a target_audience claim, that endpoint will respond with
     an OpenID Connect token for the service account -- in other words,
     a JWT signed by *Google*. The aud claim in this JWT will be
     set to the value from the target_audience claim in #1.

    For more information, see
    https://developers.google.com/identity/protocols/OAuth2ServiceAccount .
    The HTTP/REST example on that page describes the JWT structure and
    demonstrates how to call the token endpoint. (The example on that page
    shows how to get an OAuth2 access token; this code is using a
    modified version of it to get an OpenID Connect token.)
    """

    service_account = service_account_credentials._make_authorization_grant_assertion()
    request = google.auth.transport.requests.Request()
    body = {
        'assertion': service_account,
        'grant_type': google.oauth2._client._JWT_GRANT_TYPE,
    }
    token_response = google.oauth2._client._token_endpoint_request(request, OAUTH_TOKEN_URI, body)
    return token_response['id_token']

请在此处找到 Project-B 的 IAP 配置。。 IAP 安全网络应用用户是 project-A@appspot.gserviceaccount.com - 项目 A 的默认服务帐户。

【问题讨论】:

  • 您能否提供您在哪里进行身份验证的代码(当然是经过消毒的)?您如何生成 JWT、请求 OIDC 等。您是创建了新的服务帐户还是使用了默认帐户?

标签: google-app-engine google-authentication google-iap


【解决方案1】:

服务帐户权限至少应为受 IAP 保护的 Web 应用用户。更具体的IAM roles for IAP

【讨论】:

    【解决方案2】:

    从您提供的代码看来,您似乎没有使用合适的路径初始化变量 OAUTH_TOKEN_URIIAM_SCOPE

    IAM_SCOPE = 'https://www.googleapis.com/auth/iam'
    OAUTH_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'
    

    编辑:

    您能否同时确认您实际上有 enabled the IAP 用于您尝试访问的 App Engine 实例?我之前在未启用 IAP 的情况下看到了这个确切的错误。

    【讨论】:

    • 两个变量都在类中(我已经编辑了我的问题以包含它们)。
    • 您能否确认您确实为您尝试访问的 App Engine 实例启用了 IAP:cloud.google.com/iap/docs/app-engine-quickstart#enabling_iap?我之前在未启用 IAP 的情况下看到了这个确切的错误。
    • 已确认。我已将 GCP 控制台中的 IAP 设置附加到问题中。
    • 我重现了这种情况并面临 500 响应,因为项目 A 中我的 App Engine 的服务帐户没有服务帐户令牌创建者的 IAM 角色。您能否尝试将此角色添加到您的服务帐户并重试? - cloud.google.com/iam/docs/granting-roles-to-service-accounts
    • 我能够像您一样重现确切的堆栈跟踪。一切似乎都很好,所以我决定创建一个问题跟踪器来进一步更新这个问题。你可以在这里查看:issuetracker.google.com/144749437
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-23
    • 1970-01-01
    • 2020-01-06
    • 2020-03-21
    • 2020-12-30
    • 2018-06-18
    • 1970-01-01
    相关资源
    最近更新 更多