使用 python 对 GCP 计算 API 端点进行身份验证

Posted

技术标签:

【中文标题】使用 python 对 GCP 计算 API 端点进行身份验证【英文标题】:using python to authenticate to GCP compute API endpoint 【发布时间】:2019-11-10 12:11:15 【问题描述】:

我的目标是在不依赖 gcloud 二进制文件的情况下重现/复制 gcloud compute addresses create 的功能。

我正在尝试根据https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address 上关于保留静态外部 IP 地址的文档,使用 python 对 googleapis 计算端点的 POST 进行身份验证

但我的 POST 每次都返回 401。

我从 google.auth.jwt python 模块创建了一个 JWT,当我对其进行解码时,JWT 嵌入了我希望存在的所有字符串。

我还尝试了将以下 OAuth 范围组合到 JWT 中: - “https://www.googleapis.com/auth/userinfo.email” - “https://www.googleapis.com/auth/compute” - “https://www.googleapis.com/auth/cloud-platform”

这是我使用服务帐户的 JSON 密钥文件中的信息获取 JWT 的函数

def _generate_jwt( tokenPath, expiry_length=3600 ):
    now = int(time.time())
    tokenData = load_json_data( tokenPath )
    sa_email = tokenData['client_email']
    payload = 
        'iat': now, 
        # expires after 'expiry_length' seconds.
        "exp": now + expiry_length,
        'iss': sa_email,
        "scope": " ".join( [
            "https://www.googleapis.com/auth/cloud-platform",
            "https://www.googleapis.com/auth/compute",
            "https://www.googleapis.com/auth/userinfo.email"
        ] ),
        'aud': "https://www.googleapis.com/oauth2/v4/token",
        'email': sa_email
    
    # sign with keyfile
    signer = google.auth.crypt.RSASigner.from_service_account_file( tokenPath )
    jwt = google.auth.jwt.encode(signer, payload)

    return jwt

一旦我有了 JWT,我就会发布以下失败的帖子,401,::

    gapiURL = 'https://www.googleapis.com/compute/v1/projects/' + projectID + '/regions/' + region + '/addresses'
    jwtToken = _generate_jwt( servicetoken )
    headers =   
        'Authorization': 'Bearer '.format( jwtToken ),
        'content-type' : 'application/json',
        
    post = requests.post( url=gapiURL, headers=headers, data=data ) 
    post.raise_for_status()
    return post.text

无论我在 JWT 中使用了多少范围组合或向我的服务帐户提供了多少权限,我都收到了 401。我做错了什么?

编辑:非常感谢 @JohnHanley 指出我在 GCP 的身份验证序列中缺少到 https://www.googleapis.com/oauth2/v4/token URL 的下一个/第二个 POST。因此,您可以通过 JWT 获取“访问令牌”。

我已将调用更改为使用 python jwt 模块,而不是 google.auth.jwt 模块与 google.auth.crypt.RSASigner 的组合。所以代码比较简单,我把它放在一个方法中

## serviceAccount auth sequence for google :: JWT -> accessToken
def gke_get_token( serviceKeyDict, expiry_seconds=3600 ):

    epoch_time = int(time.time())
    # Generate a claim from the service account file.
    claim = 
        "iss": serviceKeyDict["client_email"],
        "scope": " ".join([
            "https://www.googleapis.com/auth/cloud-platform",
            "https://www.googleapis.com/auth/userinfo.email"
        ]),
        "aud": "https://www.googleapis.com/oauth2/v4/token",
        "exp": epoch_time + expiry_seconds,
        "iat": epoch_time
        
    # Sign claim with JWT.
    assertion = jwt.encode( claim, serviceKeyDict["private_key"], algorithm='RS256' ).decode() 
    data = urllib.urlencode( 
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": assertion
     )  
    # Request the access token.
    result = requests.post(
        url="https://www.googleapis.com/oauth2/v4/token",
        headers=
            "Content-Type": "application/x-www-form-urlencoded"
        ,
        data=data
    )    
    result.raise_for_status()
    return loadJsonData(result.text)["access_token"]



【问题讨论】:

您尚未创建访问令牌,您已创建签名 JWT。您需要用签名的 JWT 交换访问令牌。我写了一篇关于如何在 Python 中做到这一点的文章。 jhanley.com/… @JohnHanley - 谢谢你的澄清。我通读了您的文章,并且成功地为我的服务帐户获取了身份验证令牌。请将您的评论更改为答案,以便我可以选择它,因为它是正确的解决方案,我希望您为此获得荣誉。稍后,我将根据阅读您的文章所做的更改来编辑我的问题。 已发布答案。谢谢。 【参考方案1】:

在 Google Cloud 中,授予访问权限的“令牌”分为三种类型:

签名的 JWT 访问令牌 身份令牌

在您的情况下,您创建了一个签名的 JWT。一些 Google 服务接受此令牌。大多数没有。

创建签名 JWT 后,下一步就是调用 Google OAuth 端点并交换访问令牌。我写了一篇文章详细描述了这一点:

Google Cloud – Creating OAuth Access Tokens for REST API Calls

一些 Google 服务现在接受身份令牌。这称为基于身份的访问控制 (IBAC)。这不适用于您的问题,但它是 Google Cloud Authorization 未来的趋势。一个例子是我关于 Cloud Run + Cloud Storage + KMS 的文章:

Google Cloud – Go – Identity Based Access Control

以下 Python 代码示例展示了如何交换令牌:

def exchangeJwtForAccessToken(signed_jwt):
    '''
    This function takes a Signed JWT and exchanges it for a Google OAuth Access Token
    '''

    auth_url = "https://www.googleapis.com/oauth2/v4/token"

    params = 
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": signed_jwt
    

    r = requests.post(auth_url, data=params)

    if r.ok:
        return(r.json()['access_token'], '')

    return None, r.text

【讨论】:

以上是关于使用 python 对 GCP 计算 API 端点进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

使用 python api 从 GCP 管理 Kubernetes 集群

使用 GCP API 网关的 Graphql 后端的 OpenAPI 配置?

GCP API Gateway:路径参数作为查询参数传递

使用GCP API密钥限制对特定GCP App Engine服务的访问?

GCP端点和Apigee有啥区别

如何使用 Python 创建/管理 GCP API 密钥