如何在没有任何 Google API 绑定的情况下从 GCE 容器访问应用程序默认凭据?
How to access application default credentials from a GCE container without any Google API binding?
我正在编写一个在容器中运行的应用程序,在 Google 容器引擎上,使用的语言与 Google API 没有任何绑定。
我需要访问应用程序默认凭据。不幸的是,官方文档没有解释如何在不使用 Google API.
的现有绑定之一的情况下在生产环境中执行此类操作。
在开发环境中(即在我的本地开发机器上)我导出 GOOGLE_APPLICATION_CREDENTIALS 变量,但它在生产容器中不可用。这是否意味着我必须使用来自 REST API 的一些端点?
Ruby 的实现是开源的,可以访问 here。
按优先级检查的不同位置
get_application_default
方法清楚地表明:
- 检查GOOGLE_APPLICATION_CREDENTIALS环境变量,
- 然后检查PATH,
- 然后勾选默认路径
/etc/google/auth
,
- 最后,如果在计算实例上仍然没有任何内容,则获取新的访问令牌。
def get_application_default(scope = nil, options = {})
creds = DefaultCredentials.from_env(scope) ||
DefaultCredentials.from_well_known_path(scope) ||
DefaultCredentials.from_system_default_path(scope)
return creds unless creds.nil?
raise NOT_FOUND_ERROR unless GCECredentials.on_gce?(options)
GCECredentials.new
end
和the official documentation说的一致:
The environment variable GOOGLE_APPLICATION_CREDENTIALS is checked. If
this variable is specified it should point to a file that defines the
credentials. [...]
If you have installed the Google Cloud SDK on your machine and have run
the command gcloud auth application-default login, your identity can
be used as a proxy to test code calling APIs from that machine.
If you
are running in Google App Engine production, the built-in service
account associated with the application will be used.
If you are running in Google Compute Engine production, the built-in service
account associated with the virtual machine instance will be used.
- If none of these conditions is true, an error will occur.
检测GCE环境
on_gce?
method shows how to check whether we are on GCE by sending a GET/HEAD HTTP request to http://169.254.169.254。如果响应中有Metadata-Flavor: Google
header,那么很可能是GCE.
def on_gce?(options = {})
c = options[:connection] || Faraday.default_connection
resp = c.get(COMPUTE_CHECK_URI) do |req|
# Comment from: oauth2client/client.py
#
# Note: the explicit `timeout` below is a workaround. The underlying
# issue is that resolving an unknown host on some networks will take
# 20-30 seconds; making this timeout short fixes the issue, but
# could lead to false negatives in the event that we are on GCE, but
# the metadata resolution was particularly slow. The latter case is
# "unlikely".
req.options.timeout = 0.1
end
return false unless resp.status == 200
return false unless resp.headers.key?('Metadata-Flavor')
return resp.headers['Metadata-Flavor'] == 'Google'
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
return false
end
直接从 Google
获取访问令牌
如果在文件系统上找不到默认凭据并且应用程序在 GCE 上是 运行,我们可以在没有任何事先身份验证的情况下请求一个新的访问令牌。这是可能的,因为默认服务帐户是在项目中启用 GCE 时自动创建的。
fetch_access_token
方法展示了如何从 GCE 实例中通过简单地向 http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
.
发出 GET 请求来获取新的访问令牌
def fetch_access_token(options = {})
c = options[:connection] || Faraday.default_connection
c.headers = { 'Metadata-Flavor' => 'Google' }
resp = c.get(COMPUTE_AUTH_TOKEN_URI)
case resp.status
when 200
Signet::OAuth2.parse_credentials(resp.body,
resp.headers['content-type'])
when 404
raise(Signet::AuthorizationError, NO_METADATA_SERVER_ERROR)
else
msg = "Unexpected error code #{resp.status}" + UNEXPECTED_ERROR_SUFFIX
raise(Signet::AuthorizationError, msg)
end
end
这里用一个curl命令来说明:
curl \
http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token \
-H 'accept: application/json' \
-H 'Metadata-Flavor: Google'
我正在编写一个在容器中运行的应用程序,在 Google 容器引擎上,使用的语言与 Google API 没有任何绑定。
我需要访问应用程序默认凭据。不幸的是,官方文档没有解释如何在不使用 Google API.
的现有绑定之一的情况下在生产环境中执行此类操作。在开发环境中(即在我的本地开发机器上)我导出 GOOGLE_APPLICATION_CREDENTIALS 变量,但它在生产容器中不可用。这是否意味着我必须使用来自 REST API 的一些端点?
Ruby 的实现是开源的,可以访问 here。
按优先级检查的不同位置
get_application_default
方法清楚地表明:
- 检查GOOGLE_APPLICATION_CREDENTIALS环境变量,
- 然后检查PATH,
- 然后勾选默认路径
/etc/google/auth
, - 最后,如果在计算实例上仍然没有任何内容,则获取新的访问令牌。
def get_application_default(scope = nil, options = {})
creds = DefaultCredentials.from_env(scope) ||
DefaultCredentials.from_well_known_path(scope) ||
DefaultCredentials.from_system_default_path(scope)
return creds unless creds.nil?
raise NOT_FOUND_ERROR unless GCECredentials.on_gce?(options)
GCECredentials.new
end
和the official documentation说的一致:
The environment variable GOOGLE_APPLICATION_CREDENTIALS is checked. If this variable is specified it should point to a file that defines the credentials. [...]
If you have installed the Google Cloud SDK on your machine and have run the command gcloud auth application-default login, your identity can be used as a proxy to test code calling APIs from that machine.
If you are running in Google App Engine production, the built-in service account associated with the application will be used.
If you are running in Google Compute Engine production, the built-in service account associated with the virtual machine instance will be used.
- If none of these conditions is true, an error will occur.
检测GCE环境
on_gce?
method shows how to check whether we are on GCE by sending a GET/HEAD HTTP request to http://169.254.169.254。如果响应中有Metadata-Flavor: Google
header,那么很可能是GCE.
def on_gce?(options = {})
c = options[:connection] || Faraday.default_connection
resp = c.get(COMPUTE_CHECK_URI) do |req|
# Comment from: oauth2client/client.py
#
# Note: the explicit `timeout` below is a workaround. The underlying
# issue is that resolving an unknown host on some networks will take
# 20-30 seconds; making this timeout short fixes the issue, but
# could lead to false negatives in the event that we are on GCE, but
# the metadata resolution was particularly slow. The latter case is
# "unlikely".
req.options.timeout = 0.1
end
return false unless resp.status == 200
return false unless resp.headers.key?('Metadata-Flavor')
return resp.headers['Metadata-Flavor'] == 'Google'
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
return false
end
直接从 Google
获取访问令牌如果在文件系统上找不到默认凭据并且应用程序在 GCE 上是 运行,我们可以在没有任何事先身份验证的情况下请求一个新的访问令牌。这是可能的,因为默认服务帐户是在项目中启用 GCE 时自动创建的。
fetch_access_token
方法展示了如何从 GCE 实例中通过简单地向 http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
.
def fetch_access_token(options = {})
c = options[:connection] || Faraday.default_connection
c.headers = { 'Metadata-Flavor' => 'Google' }
resp = c.get(COMPUTE_AUTH_TOKEN_URI)
case resp.status
when 200
Signet::OAuth2.parse_credentials(resp.body,
resp.headers['content-type'])
when 404
raise(Signet::AuthorizationError, NO_METADATA_SERVER_ERROR)
else
msg = "Unexpected error code #{resp.status}" + UNEXPECTED_ERROR_SUFFIX
raise(Signet::AuthorizationError, msg)
end
end
这里用一个curl命令来说明:
curl \
http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token \
-H 'accept: application/json' \
-H 'Metadata-Flavor: Google'