使用python验证GCP计算API端点

时间:2019-06-28 14:12:07

标签: python oauth-2.0 google-cloud-platform jwt

我的目标是不依赖于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"]



1 个答案:

答案 0 :(得分:2)

在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