如何使用用户默认凭据以编程方式对受 Cloud Identity-Aware Proxy (Cloud IAP) 保护的资源进行身份验证?

Posted

技术标签:

【中文标题】如何使用用户默认凭据以编程方式对受 Cloud Identity-Aware Proxy (Cloud IAP) 保护的资源进行身份验证?【英文标题】:How to authenticate programmatically to a Cloud Identity-Aware Proxy (Cloud IAP)-secured resource using user default credentials? 【发布时间】:2018-03-02 14:48:47 【问题描述】:

我希望能够使用开发环境中的用户默认凭据以编程方式为 iap 生成一个 id 令牌(即我自己的笔记本电脑,安装并登录了 google cloud sdk)。

当关注documentation 时,我设法使用服务帐户文件生成了一个授权令牌。

在我的个人计算机上使用google.auth.default 时,我可以看到google.oauth2.credentials.Credentials 类型的凭据有一个refresh_token。我想用它来生成令牌,因为它是在documentation 下使用 curl 完成的 Authenticating from a desktop app -> Accessing the application 但我无法让它工作。有人知道是否有办法以这种方式进行身份验证?

【问题讨论】:

你能详细说明为什么它不起作用吗?也许您看到了一些错误? 【参考方案1】:

正如 Matthew 所说,用于获取刷新令牌的客户端 ID 的项目应与 IAP 客户端 ID 的项目相匹配。 Gcloud 使用在 path_to/google-cloud-sdk/lib/googlecloudsdk/api_lib/auth/util.py 中定义的客户端 ID 和密码作为默认凭据(DEFAULT_CREDENTIALS_DEFAULT_CLIENT_IDDEFAULT_CREDENTIALS_DEFAULT_CLIENT_SECRET)。因此,如果没有 util.py 更改,您不能使用来自 google.auth.default() 的刷新令牌,因为尝试获取 ID 令牌将失败:


 "error": "invalid_audience",
 "error_description": "The audience client and the client need to be in the same project."

您的选择是:

    根据 Matthew 的回复/文档获取刷新令牌(缓存它以避免每次都需要用户授权)和 ID 令牌。 修补 gcloud util.py 中存在的客户端 ID 和密钥(可能会随着 gcloud 更新而更改)。

两个选项的示例代码:

import google.auth
import requests
import json

from webbrowser import open_new_tab
from time import sleep

# use gcloud app default credentials if gcloud's util.py is patched
def id_token_from_default_creds(audience): 
    cred, proj = google.auth.default()
    # data necessary for ID token
    client_id = cred.client_id
    client_secret= cred.client_secret
    refresh_token = str(cred.refresh_token)
    return id_token_from_refresh_token(client_id, client_secret, refresh_token, audience)

def id_token_from_refresh_token(client_id, client_secret, refresh_token, audience):
    oauth_token_base_URL = "https://www.googleapis.com/oauth2/v4/token"
    payload = "client_id": client_id, "client_secret": client_secret,
                "refresh_token": refresh_token, "grant_type": "refresh_token",
                "audience": audience
    res = requests.post(oauth_token_base_URL, data=payload)
    return (str(json.loads(res.text)[u"id_token"]))

# obtain ID token for provided Client ID: get authorization code -> exchange for refresh token -> obtain and return ID token
def id_token_from_client_id(client_id, client_secret, audience):
    auth_code = get_auth_code(client_id)
    refresh_token = get_refresh_token_from_code(auth_code, client_id, client_secret)
    return id_token_from_refresh_token(client_id, client_secret, refresh_token, audience)

def get_auth_code(client_id):
    auth_url = "https://accounts.google.com/o/oauth2/v2/auth?client_id=%s&response_type=code&scope=openid%%20email&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob"%client_id
    open_new_tab(auth_url)
    sleep(1)
    return raw_input("Authorization code: ")

def get_refresh_token_from_code(auth_code, client_id, client_secret):
    oauth_token_base_URL = 'https://www.googleapis.com/oauth2/v4/token'
    payload = "code": auth_code, "client_id": client_id, "client_secret": client_secret,
                "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", "grant_type": "authorization_code"
    res = requests.post(oauth_token_base_URL, data=payload)
    return (str(json.loads(res.text)[u"refresh_token"]))

print("ID token from client ID: %s" % id_token_from_client_id("<Other client ID>", "<Other client secret>", "<IAP Client ID>")) # other client ID should be from the same project as IAP Client ID 
print("ID token from \"default\" credentials: %s" % id_token_from_default_creds("<IAP Client ID>"))

【讨论】:

感谢它从 ~/.config/gcloud/application_default_credentials.json 加载凭据的代码,您可以在您的环境中使用 GOOGLE_APPLICATION_CREDENTIALS 来指定一个新文件。我添加了另一个客户端凭据,但我有一个带有未经授权的客户端消息的 401。我目前正在研究它。 查看库,GCloud 凭据属于“authorized_user”类型,因此您确实可以尝试将 GOOGLE_APPLICATION_CREDENTIALS 环境变量设置为包含“refresh_token”的 json 文件的路径:“your_refresh_token”, “client_id”:“your_client_id”,“client_secret”:“your_client_secret”和“type”:“authorized_user”。我不明白你为什么要这样做,因为你首先需要获取刷新令牌才能填充文件,所以你基本上只需使用 google.auth 库来读取这个 json。【参考方案2】:

感谢您指出这一点!如果我们有这方面的代码示例,我会很高兴,但正如您所发现的,至少对于 Python,我们为服务帐户身份验证提供的代码示例不适用于用户帐户。

我对我们的 Python 客户端库不够熟悉,无法告诉您它们是否可以帮助您解决这些问题,但 https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_desktop_app 指导您完成的要点是:

    为您的客户端应用创建一个新的客户端 ID(在与受 IAP 保护的应用相同的项目中),并使用该客户端 ID 获取刷新令牌。您不能只使用应用程序默认凭据中的刷新令牌,因为这将具有错误的客户端 ID 和可能的范围。相反,您需要将“gcloud auth login”等功能添加到您的应用程序中并保留刷新令牌。

    拥有刷新令牌后,当您的客户端应用程序想要访问 IAP 应用程序时:使用您的应用程序 OAuth 客户端的客户端 ID 和密码、刷新令牌和 IAP 客户端 ID 发送到 https://www.googleapis.com/oauth2/v4/token .这将返回一个 OpenID Connect 令牌,该令牌将在 1 小时内有效地向 IAP 进行身份验证。

这至少足以开始了吗?

【讨论】:

非常感谢!我不确定我是否理解为什么用户而不是用户需要客户端 ID,因为他们都具有 IAP-Secured Web App User 角色? 查看它的一种方式是客户端应用程序的客户端 ID 告诉身份验证系统哪个应用程序正在对用户进行身份验证,而 IAP 应用程序的客户端 ID 指示用户想要访问哪个应用程序。在“用户身份验证”情况下需要客户端应用程序客户端 ID,但在“服务身份验证”情况下不需要,因为在服务帐户流程中,应用程序使用非 OAuth 机制(例如拥有私钥或 GCP平台魔术)来证明其充当服务帐户的权限。 好吧,我理解得更好了,如果有一种方法可以使用“GCP 平台魔法”,那么 gcloud 命令经过身份验证且有权访问 IAP 的用户可以以编程方式连接而无需再次登录。 我大致了解需要另一个客户端 ID。我不明白的是:我们需要在多大程度上保护这个二级客户的身份和秘密?这应该被视为敏感数据吗?似乎不太可能,否则数据永远无法打包到桌面样式的客户端中。硬编码这些凭据并提交版本控制是否安全? @Matthew,您对此有何看法?

以上是关于如何使用用户默认凭据以编程方式对受 Cloud Identity-Aware Proxy (Cloud IAP) 保护的资源进行身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Windows 中以编程方式切换用户

如何使用 Monotouch 在 iPad 故事板应用程序中以编程方式管理视图

如何使用PowerShell中的当前Windows用户的凭据加密KeePass?

如何使用 Spring Cloud Kubernetes 以编程方式写入 ConfigMap 值?

WCF 使用凭据和 IIS 以编程方式启用 TLS

以编程方式连接 LDAP 并在 AEM 中验证凭据