【问题标题】:Allow cloud functions ip from app engine firewall rules允许来自应用引擎防火墙规则的云功能 IP
【发布时间】:2020-06-01 02:45:55
【问题描述】:

我们创建了一个应用引擎实例作为后端,另一个来自云功能。 现在云功能需要从同一个谷歌项目中的应用程序引擎访问 api,如果应用程序引擎的防火墙允许每个人访问,这可以正常工作。但在我们的例子中,我们只需要限制来自云功能的访问。

我是 GCP 的新手,非常感谢您的建议。提前致谢。

【问题讨论】:

    标签: google-app-engine google-cloud-functions


    【解决方案1】:

    最好的解决方案是激活IAP for App Engine(身份感知代理)。 Here 您可以找到有关如何在 App Engine 上激活 IAP 的指南。

    IAP 将阻止任何人、任何应用程序访问您的 App Engine 实例,但您将手动允许。在您的情况下,您需要允许 Cloud Functions 服务帐户访问您的应用程序。您可以查看this guide,了解如何通过 Cloud Functions 以编程方式实现这一目标。您有 C#、Python、Java 和 PHP 的示例。

    例如 Python :

    import google.auth
    import google.auth.app_engine
    import google.auth.compute_engine.credentials
    import google.auth.iam
    from google.auth.transport.requests import Request
    import google.oauth2.credentials
    import google.oauth2.service_account
    import requests
    import requests_toolbelt.adapters.appengine
    
    
    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='GET', **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.
    google_open_id_connect_token = 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.
    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))
    else:
        return resp.text
    
    
    def get_google_open_id_connect_token(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_jwt = (
        service_account_credentials._make_authorization_grant_assertion())
    request = google.auth.transport.requests.Request()
    body = {
        'assertion': service_account_jwt,
        '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']
    

    如果您在 Nodejs 上使用云函数,StackOverflow 用户在this post 中创建了一个关于如何为 Nodejs 实现相同功能的示例。

    【讨论】:

    • 我经常读到解决方案是 IAP,在我的用例中,我有一个活动的 IAP 和防火墙。单独的 IAP 可能对人们有用,但它不能直接回答问题,即如何通过云功能穿透防火墙?希望我知道!
    猜你喜欢
    • 1970-01-01
    • 2019-06-26
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    • 2019-09-11
    相关资源
    最近更新 更多