googleauth 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +3 -1
- data/lib/googleauth/application_default.rb +1 -1
- data/lib/googleauth/external_account/aws_credentials.rb +54 -41
- data/lib/googleauth/external_account/base_credentials.rb +25 -67
- data/lib/googleauth/external_account/external_account_utils.rb +92 -0
- data/lib/googleauth/external_account/identity_pool_credentials.rb +118 -0
- data/lib/googleauth/external_account.rb +32 -13
- data/lib/googleauth/oauth2/sts_client.rb +17 -7
- data/lib/googleauth/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 146d1c41b29049fb35b0cc5c4934a811eb547f3d3000a95e3fbd8a24fc2b86bb
|
4
|
+
data.tar.gz: cb613b51160da7a51c545728ce03dac8977fee8f1806b72eca97997d28645450
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39289ca31ac785dffbb151a71c6b8337d442db75e3d2d3fcfa94a4c5656076c5965c3beb09e55cc80996c189af170f9208dbb84df6adbb9b385ede36850ea6aa
|
7
|
+
data.tar.gz: 442083ee198a5ae04f8924f2360b1512eff862d12f66327d22195dbd75c5475b8d912701d7e41ab21b7c6ceae8f196b229b1f376cf69cb36846f5787f52eeb37
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### 1.6.0 (2023-06-20)
|
4
|
+
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* adding identity pool credentials ([#433](https://github.com/googleapis/google-auth-library-ruby/issues/433))
|
8
|
+
#### Documentation
|
9
|
+
|
10
|
+
* deprecation message for discontinuing command line auth flow ([#435](https://github.com/googleapis/google-auth-library-ruby/issues/435))
|
11
|
+
|
12
|
+
### 1.5.2 (2023-04-13)
|
13
|
+
|
14
|
+
#### Bug Fixes
|
15
|
+
|
16
|
+
* AWS IMDSV2 session token fetching shall call PUT method instead of GET ([#429](https://github.com/googleapis/google-auth-library-ruby/issues/429))
|
17
|
+
* GCECredentials - Allow retrieval of ID token ([#425](https://github.com/googleapis/google-auth-library-ruby/issues/425))
|
18
|
+
|
3
19
|
### 1.5.1 (2023-04-10)
|
4
20
|
|
5
21
|
#### Bug Fixes
|
data/README.md
CHANGED
@@ -97,7 +97,9 @@ get('/oauth2callback') do
|
|
97
97
|
end
|
98
98
|
```
|
99
99
|
|
100
|
-
### Example (Command Line)
|
100
|
+
### Example (Command Line) [Deprecated]
|
101
|
+
|
102
|
+
The Google Auth OOB flow has been discontiued on January 31, 2023. The OOB flow is a legacy flow that is no longer considered secure. To continue using Google Auth, please migrate your applications to a more secure flow. For more information on how to do this, please refer to this [OOB Migration](https://developers.google.com/identity/protocols/oauth2/resources/oob-migration) guide.
|
101
103
|
|
102
104
|
```ruby
|
103
105
|
require 'googleauth'
|
@@ -14,19 +14,21 @@
|
|
14
14
|
|
15
15
|
require "time"
|
16
16
|
require "googleauth/external_account/base_credentials"
|
17
|
+
require "googleauth/external_account/external_account_utils"
|
17
18
|
|
18
19
|
module Google
|
19
|
-
# Module Auth provides classes that provide Google-specific authorization
|
20
|
-
# used to access Google APIs.
|
20
|
+
# Module Auth provides classes that provide Google-specific authorization used to access Google APIs.
|
21
21
|
module Auth
|
22
|
-
# Authenticates requests using External Account credentials, such
|
23
|
-
# as those provided by the AWS provider.
|
22
|
+
# Authenticates requests using External Account credentials, such as those provided by the AWS provider.
|
24
23
|
module ExternalAccount
|
25
|
-
# This module handles the retrieval of credentials from Google Cloud
|
26
|
-
#
|
27
|
-
# credentials for a short-lived Google Cloud access token.
|
24
|
+
# This module handles the retrieval of credentials from Google Cloud by utilizing the AWS EC2 metadata service and
|
25
|
+
# then exchanging the credentials for a short-lived Google Cloud access token.
|
28
26
|
class AwsCredentials
|
27
|
+
# Constant for imdsv2 session token expiration in seconds
|
28
|
+
IMDSV2_TOKEN_EXPIRATION_IN_SECONDS = 300
|
29
|
+
|
29
30
|
include Google::Auth::ExternalAccount::BaseCredentials
|
31
|
+
include Google::Auth::ExternalAccount::ExternalAccountUtils
|
30
32
|
extend CredentialsLoader
|
31
33
|
|
32
34
|
# Will always be nil, but method still gets used.
|
@@ -37,15 +39,17 @@ module Google
|
|
37
39
|
|
38
40
|
@audience = options[:audience]
|
39
41
|
@credential_source = options[:credential_source] || {}
|
40
|
-
@environment_id = @credential_source[
|
41
|
-
@region_url = @credential_source[
|
42
|
-
@credential_verification_url = @credential_source[
|
43
|
-
@regional_cred_verification_url = @credential_source[
|
44
|
-
@imdsv2_session_token_url = @credential_source[
|
42
|
+
@environment_id = @credential_source[:environment_id]
|
43
|
+
@region_url = @credential_source[:region_url]
|
44
|
+
@credential_verification_url = @credential_source[:url]
|
45
|
+
@regional_cred_verification_url = @credential_source[:regional_cred_verification_url]
|
46
|
+
@imdsv2_session_token_url = @credential_source[:imdsv2_session_token_url]
|
45
47
|
|
46
48
|
# These will be lazily loaded when needed, or will raise an error if not provided
|
47
49
|
@region = nil
|
48
50
|
@request_signer = nil
|
51
|
+
@imdsv2_session_token = nil
|
52
|
+
@imdsv2_session_token_expiry = nil
|
49
53
|
end
|
50
54
|
|
51
55
|
# Retrieves the subject token using the credential_source object.
|
@@ -54,22 +58,20 @@ module Google
|
|
54
58
|
#
|
55
59
|
# The logic is summarized as:
|
56
60
|
#
|
57
|
-
# Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION
|
58
|
-
#
|
59
|
-
# if not found in the environment variable.
|
61
|
+
# Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION environment variable or from the AWS
|
62
|
+
# metadata server availability-zone if not found in the environment variable.
|
60
63
|
#
|
61
|
-
# Check AWS credentials in environment variables. If not found, retrieve
|
62
|
-
#
|
64
|
+
# Check AWS credentials in environment variables. If not found, retrieve from the AWS metadata server
|
65
|
+
# security-credentials endpoint.
|
63
66
|
#
|
64
|
-
# When retrieving AWS credentials from the metadata server
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# credentials can be retrieved via: security-credentials/role_name
|
67
|
+
# When retrieving AWS credentials from the metadata server security-credentials endpoint, the AWS role needs to
|
68
|
+
# be determined by # calling the security-credentials endpoint without any argument.
|
69
|
+
# Then the credentials can be retrieved via: security-credentials/role_name
|
68
70
|
#
|
69
71
|
# Generate the signed request to AWS STS GetCallerIdentity action.
|
70
72
|
#
|
71
|
-
# Inject x-goog-cloud-target-resource into header and serialize the
|
72
|
-
#
|
73
|
+
# Inject x-goog-cloud-target-resource into header and serialize the signed request.
|
74
|
+
# This will be the subject-token to pass to GCP STS.
|
73
75
|
#
|
74
76
|
# @return [string] The retrieved subject token.
|
75
77
|
#
|
@@ -104,16 +106,30 @@ module Google
|
|
104
106
|
|
105
107
|
private
|
106
108
|
|
107
|
-
def
|
109
|
+
def imdsv2_session_token
|
110
|
+
return @imdsv2_session_token unless imdsv2_session_token_invalid?
|
111
|
+
raise "IMDSV2 token url must be provided" if @imdsv2_session_token_url.nil?
|
108
112
|
begin
|
109
|
-
|
110
|
-
headers["x-aws-ec2-metadata-token"] =
|
111
|
-
@imdsv2_session_token_url,
|
112
|
-
"Session Token",
|
113
|
-
headers: { "x-aws-ec2-metadata-token-ttl-seconds": "300" }
|
114
|
-
).body
|
113
|
+
response = connection.put @imdsv2_session_token_url do |req|
|
114
|
+
req.headers["x-aws-ec2-metadata-token-ttl-seconds"] = IMDSV2_TOKEN_EXPIRATION_IN_SECONDS.to_s
|
115
115
|
end
|
116
|
+
rescue Faraday::Error => e
|
117
|
+
raise "Fetching AWS IMDSV2 token error: #{e}"
|
118
|
+
end
|
119
|
+
raise Faraday::Error unless response.success?
|
120
|
+
@imdsv2_session_token = response.body
|
121
|
+
@imdsv2_session_token_expiry = Time.now + IMDSV2_TOKEN_EXPIRATION_IN_SECONDS
|
122
|
+
@imdsv2_session_token
|
123
|
+
end
|
116
124
|
|
125
|
+
def imdsv2_session_token_invalid?
|
126
|
+
return true if @imdsv2_session_token.nil?
|
127
|
+
@imdsv2_session_token_expiry.nil? || @imdsv2_session_token_expiry < Time.now
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_aws_resource url, name, data: nil, headers: {}
|
131
|
+
begin
|
132
|
+
headers["x-aws-ec2-metadata-token"] = imdsv2_session_token
|
117
133
|
response = if data
|
118
134
|
headers["Content-Type"] = "application/json"
|
119
135
|
connection.post url, data, headers
|
@@ -136,9 +152,8 @@ module Google
|
|
136
152
|
end
|
137
153
|
end
|
138
154
|
|
139
|
-
# Retrieves the AWS security credentials required for signing AWS
|
140
|
-
#
|
141
|
-
# or from the AWS metadata server.
|
155
|
+
# Retrieves the AWS security credentials required for signing AWS requests from either the AWS security
|
156
|
+
# credentials environment variables or from the AWS metadata server.
|
142
157
|
def fetch_security_credentials
|
143
158
|
env_aws_access_key_id = ENV[CredentialsLoader::AWS_ACCESS_KEY_ID_VAR]
|
144
159
|
env_aws_secret_access_key = ENV[CredentialsLoader::AWS_SECRET_ACCESS_KEY_VAR]
|
@@ -163,10 +178,9 @@ module Google
|
|
163
178
|
}
|
164
179
|
end
|
165
180
|
|
166
|
-
# Retrieves the AWS role currently attached to the current AWS
|
167
|
-
#
|
168
|
-
#
|
169
|
-
# the AWS security credentials needed to sign requests to AWS APIs.
|
181
|
+
# Retrieves the AWS role currently attached to the current AWS workload by querying the AWS metadata server.
|
182
|
+
# This is needed for the AWS metadata server security credentials endpoint in order to retrieve the AWS security
|
183
|
+
# credentials needed to sign requests to AWS APIs.
|
170
184
|
def fetch_metadata_role_name
|
171
185
|
unless @credential_verification_url
|
172
186
|
raise "Unable to determine the AWS metadata server security credentials endpoint"
|
@@ -175,8 +189,7 @@ module Google
|
|
175
189
|
get_aws_resource(@credential_verification_url, "IAM Role").body
|
176
190
|
end
|
177
191
|
|
178
|
-
# Retrieves the AWS security credentials required for signing AWS
|
179
|
-
# requests from the AWS metadata server.
|
192
|
+
# Retrieves the AWS security credentials required for signing AWS requests from the AWS metadata server.
|
180
193
|
def fetch_metadata_security_credentials role_name
|
181
194
|
response = get_aws_resource "#{@credential_verification_url}/#{role_name}", "credentials"
|
182
195
|
MultiJson.load response.body
|
@@ -198,8 +211,8 @@ module Google
|
|
198
211
|
# Implements an AWS request signer based on the AWS Signature Version 4 signing process.
|
199
212
|
# https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
200
213
|
class AwsRequestSigner
|
201
|
-
# Instantiates an AWS request signer used to compute authenticated signed
|
202
|
-
#
|
214
|
+
# Instantiates an AWS request signer used to compute authenticated signed requests to AWS APIs based on the AWS
|
215
|
+
# Signature Version 4 signing process.
|
203
216
|
#
|
204
217
|
# @param [string] region_name
|
205
218
|
# The AWS region to use.
|
@@ -20,15 +20,13 @@ module Google
|
|
20
20
|
# Module Auth provides classes that provide Google-specific authorization
|
21
21
|
# used to access Google APIs.
|
22
22
|
module Auth
|
23
|
-
# Authenticates requests using External Account credentials, such
|
24
|
-
# as those provided by the AWS provider.
|
25
23
|
module ExternalAccount
|
26
24
|
# Authenticates requests using External Account credentials, such
|
27
|
-
# as those provided by the AWS provider.
|
25
|
+
# as those provided by the AWS provider or OIDC provider like Azure, etc.
|
28
26
|
module BaseCredentials
|
29
27
|
# Contains all methods needed for all external account credentials.
|
30
28
|
# Other credentials should call `base_setup` during initialization
|
31
|
-
# And should define the :retrieve_subject_token method
|
29
|
+
# And should define the :retrieve_subject_token! method
|
32
30
|
|
33
31
|
# External account JSON type identifier.
|
34
32
|
EXTERNAL_ACCOUNT_JSON_TYPE = "external_account".freeze
|
@@ -36,8 +34,6 @@ module Google
|
|
36
34
|
STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange".freeze
|
37
35
|
# The token exchange requested_token_type. This is always an access_token.
|
38
36
|
STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token".freeze
|
39
|
-
# Cloud resource manager URL used to retrieve project information.
|
40
|
-
CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/".freeze
|
41
37
|
# Default IAM_SCOPE
|
42
38
|
IAM_SCOPE = ["https://www.googleapis.com/auth/iam".freeze].freeze
|
43
39
|
|
@@ -74,52 +70,23 @@ module Google
|
|
74
70
|
notify_refresh_listeners
|
75
71
|
end
|
76
72
|
|
77
|
-
|
78
|
-
#
|
79
|
-
#
|
80
|
-
# When not determinable, None is returned.
|
73
|
+
# Retrieves the subject token using the credential_source object.
|
74
|
+
# @return [string]
|
75
|
+
# The retrieved subject token.
|
81
76
|
#
|
82
|
-
|
83
|
-
|
84
|
-
# https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes
|
85
|
-
#
|
86
|
-
# @return [string,nil]
|
87
|
-
# The project ID corresponding to the workload identity pool or workforce pool if determinable.
|
88
|
-
#
|
89
|
-
def project_id
|
90
|
-
return @project_id unless @project_id.nil?
|
91
|
-
project_number = self.project_number || @workforce_pool_user_project
|
92
|
-
|
93
|
-
# if we missing either project number or scope, we won't retrieve project_id
|
94
|
-
return nil if project_number.nil? || @scope.nil?
|
95
|
-
|
96
|
-
url = "#{CLOUD_RESOURCE_MANAGER}#{project_number}"
|
97
|
-
|
98
|
-
response = connection.get url do |req|
|
99
|
-
req.headers["Authorization"] = "Bearer #{@access_token}"
|
100
|
-
req.headers["Content-Type"] = "application/json"
|
101
|
-
end
|
102
|
-
|
103
|
-
if response.status == 200
|
104
|
-
response_data = MultiJson.load response.body
|
105
|
-
@project_id = response_data[:projectId]
|
106
|
-
end
|
107
|
-
|
108
|
-
@project_id
|
77
|
+
def retrieve_subject_token!
|
78
|
+
raise NotImplementedError
|
109
79
|
end
|
110
80
|
|
111
|
-
|
112
|
-
#
|
113
|
-
# STS audience pattern:
|
114
|
-
# `//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/...`
|
115
|
-
#
|
116
|
-
# @return [string, nil]
|
81
|
+
# Returns whether the credentials represent a workforce pool (True) or
|
82
|
+
# workload (False) based on the credentials' audience.
|
117
83
|
#
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
84
|
+
# @return [bool]
|
85
|
+
# true if the credentials represent a workforce pool.
|
86
|
+
# false if they represent a workload.
|
87
|
+
def is_workforce_pool?
|
88
|
+
pattern = "//iam\.googleapis\.com/locations/[^/]+/workforcePools/"
|
89
|
+
/#{pattern}/.match?(@audience || "")
|
123
90
|
end
|
124
91
|
|
125
92
|
private
|
@@ -136,13 +103,14 @@ module Google
|
|
136
103
|
@scope = options[:scope] || IAM_SCOPE
|
137
104
|
@subject_token_type = options[:subject_token_type]
|
138
105
|
@token_url = options[:token_url]
|
106
|
+
@token_info_url = options[:token_info_url]
|
139
107
|
@service_account_impersonation_url = options[:service_account_impersonation_url]
|
140
108
|
@service_account_impersonation_options = options[:service_account_impersonation_options] || {}
|
141
109
|
@client_id = options[:client_id]
|
142
110
|
@client_secret = options[:client_secret]
|
143
111
|
@quota_project_id = options[:quota_project_id]
|
144
112
|
@project_id = nil
|
145
|
-
@workforce_pool_user_project = [:workforce_pool_user_project]
|
113
|
+
@workforce_pool_user_project = options[:workforce_pool_user_project]
|
146
114
|
|
147
115
|
@expires_at = nil
|
148
116
|
@access_token = nil
|
@@ -151,29 +119,23 @@ module Google
|
|
151
119
|
token_exchange_endpoint: @token_url,
|
152
120
|
connection: default_connection
|
153
121
|
)
|
154
|
-
|
155
|
-
|
156
|
-
def normalize_timestamp time
|
157
|
-
case time
|
158
|
-
when NilClass
|
159
|
-
nil
|
160
|
-
when Time
|
161
|
-
time
|
162
|
-
when String
|
163
|
-
Time.parse time
|
164
|
-
else
|
165
|
-
raise "Invalid time value #{time}"
|
166
|
-
end
|
122
|
+
return unless @workforce_pool_user_project && !is_workforce_pool?
|
123
|
+
raise "workforce_pool_user_project should not be set for non-workforce pool credentials."
|
167
124
|
end
|
168
125
|
|
169
126
|
def exchange_token
|
127
|
+
additional_options = nil
|
128
|
+
if @client_id.nil? && @workforce_pool_user_project
|
129
|
+
additional_options = { userProject: @workforce_pool_user_project }
|
130
|
+
end
|
170
131
|
@sts_client.exchange_token(
|
171
132
|
audience: @audience,
|
172
133
|
grant_type: STS_GRANT_TYPE,
|
173
134
|
subject_token: retrieve_subject_token!,
|
174
135
|
subject_token_type: @subject_token_type,
|
175
136
|
scopes: @service_account_impersonation_url ? IAM_SCOPE : @scope,
|
176
|
-
requested_token_type: STS_REQUESTED_TOKEN_TYPE
|
137
|
+
requested_token_type: STS_REQUESTED_TOKEN_TYPE,
|
138
|
+
additional_options: additional_options
|
177
139
|
)
|
178
140
|
end
|
179
141
|
|
@@ -190,10 +152,6 @@ module Google
|
|
190
152
|
|
191
153
|
MultiJson.load response.body
|
192
154
|
end
|
193
|
-
|
194
|
-
def retrieve_subject_token!
|
195
|
-
raise NotImplementedError
|
196
|
-
end
|
197
155
|
end
|
198
156
|
end
|
199
157
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Copyright 2023 Google, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.require "time"
|
14
|
+
|
15
|
+
require "googleauth/base_client"
|
16
|
+
require "googleauth/helpers/connection"
|
17
|
+
require "googleauth/oauth2/sts_client"
|
18
|
+
|
19
|
+
module Google
|
20
|
+
# Module Auth provides classes that provide Google-specific authorization
|
21
|
+
# used to access Google APIs.
|
22
|
+
module Auth
|
23
|
+
module ExternalAccount
|
24
|
+
# Authenticates requests using External Account credentials, such
|
25
|
+
# as those provided by the AWS provider or OIDC provider like Azure, etc.
|
26
|
+
module ExternalAccountUtils
|
27
|
+
# Cloud resource manager URL used to retrieve project information.
|
28
|
+
CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/".freeze
|
29
|
+
|
30
|
+
##
|
31
|
+
# Retrieves the project ID corresponding to the workload identity or workforce pool.
|
32
|
+
# For workforce pool credentials, it returns the project ID corresponding to the workforce_pool_user_project.
|
33
|
+
# When not determinable, None is returned.
|
34
|
+
#
|
35
|
+
# The resource may not have permission (resourcemanager.projects.get) to
|
36
|
+
# call this API or the required scopes may not be selected:
|
37
|
+
# https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes
|
38
|
+
#
|
39
|
+
# @return [string,nil]
|
40
|
+
# The project ID corresponding to the workload identity pool or workforce pool if determinable.
|
41
|
+
#
|
42
|
+
def project_id
|
43
|
+
return @project_id unless @project_id.nil?
|
44
|
+
project_number = self.project_number || @workforce_pool_user_project
|
45
|
+
|
46
|
+
# if we missing either project number or scope, we won't retrieve project_id
|
47
|
+
return nil if project_number.nil? || @scope.nil?
|
48
|
+
|
49
|
+
url = "#{CLOUD_RESOURCE_MANAGER}#{project_number}"
|
50
|
+
response = connection.get url do |req|
|
51
|
+
req.headers["Authorization"] = "Bearer #{@access_token}"
|
52
|
+
req.headers["Content-Type"] = "application/json"
|
53
|
+
end
|
54
|
+
|
55
|
+
if response.status == 200
|
56
|
+
response_data = MultiJson.load response.body, symbolize_names: true
|
57
|
+
@project_id = response_data[:projectId]
|
58
|
+
end
|
59
|
+
|
60
|
+
@project_id
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Retrieve the project number corresponding to workload identity pool
|
65
|
+
# STS audience pattern:
|
66
|
+
# `//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/...`
|
67
|
+
#
|
68
|
+
# @return [string, nil]
|
69
|
+
#
|
70
|
+
def project_number
|
71
|
+
segments = @audience.split "/"
|
72
|
+
idx = segments.index "projects"
|
73
|
+
return nil if idx.nil? || idx + 1 == segments.size
|
74
|
+
segments[idx + 1]
|
75
|
+
end
|
76
|
+
|
77
|
+
def normalize_timestamp time
|
78
|
+
case time
|
79
|
+
when NilClass
|
80
|
+
nil
|
81
|
+
when Time
|
82
|
+
time
|
83
|
+
when String
|
84
|
+
Time.parse time
|
85
|
+
else
|
86
|
+
raise "Invalid time value #{time}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Copyright 2023 Google, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "time"
|
16
|
+
require "googleauth/external_account/base_credentials"
|
17
|
+
require "googleauth/external_account/external_account_utils"
|
18
|
+
|
19
|
+
module Google
|
20
|
+
# Module Auth provides classes that provide Google-specific authorization used to access Google APIs.
|
21
|
+
module Auth
|
22
|
+
module ExternalAccount
|
23
|
+
# This module handles the retrieval of credentials from Google Cloud by utilizing the any 3PI
|
24
|
+
# provider then exchanging the credentials for a short-lived Google Cloud access token.
|
25
|
+
class IdentityPoolCredentials
|
26
|
+
include Google::Auth::ExternalAccount::BaseCredentials
|
27
|
+
include Google::Auth::ExternalAccount::ExternalAccountUtils
|
28
|
+
extend CredentialsLoader
|
29
|
+
|
30
|
+
# Will always be nil, but method still gets used.
|
31
|
+
attr_reader :client_id
|
32
|
+
|
33
|
+
# Initialize from options map.
|
34
|
+
#
|
35
|
+
# @param [string] audience
|
36
|
+
# @param [hash{symbol => value}] credential_source
|
37
|
+
# credential_source is a hash that contains either source file or url.
|
38
|
+
# credential_source_format is either text or json. To define how we parse the credential response.
|
39
|
+
#
|
40
|
+
def initialize options = {}
|
41
|
+
base_setup options
|
42
|
+
|
43
|
+
@audience = options[:audience]
|
44
|
+
@credential_source = options[:credential_source] || {}
|
45
|
+
@credential_source_file = @credential_source[:file]
|
46
|
+
@credential_source_url = @credential_source[:url]
|
47
|
+
@credential_source_headers = @credential_source[:headers] || {}
|
48
|
+
@credential_source_format = @credential_source[:format] || {}
|
49
|
+
@credential_source_format_type = @credential_source_format[:type] || "text"
|
50
|
+
validate_credential_source
|
51
|
+
end
|
52
|
+
|
53
|
+
# Implementation of BaseCredentials retrieve_subject_token!
|
54
|
+
def retrieve_subject_token!
|
55
|
+
content, resource_name = token_data
|
56
|
+
if @credential_source_format_type == "text"
|
57
|
+
token = content
|
58
|
+
else
|
59
|
+
begin
|
60
|
+
response_data = MultiJson.load content, symbolize_keys: true
|
61
|
+
token = response_data[@credential_source_field_name.to_sym]
|
62
|
+
rescue StandardError
|
63
|
+
raise "Unable to parse subject_token from JSON resource #{resource_name} " \
|
64
|
+
"using key #{@credential_source_field_name}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
raise "Missing subject_token in the credential_source file/response." unless token
|
68
|
+
token
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def validate_credential_source
|
74
|
+
# `environment_id` is only supported in AWS or dedicated future external account credentials.
|
75
|
+
unless @credential_source[:environment_id].nil?
|
76
|
+
raise "Invalid Identity Pool credential_source field 'environment_id'"
|
77
|
+
end
|
78
|
+
unless ["json", "text"].include? @credential_source_format_type
|
79
|
+
raise "Invalid credential_source format #{@credential_source_format_type}"
|
80
|
+
end
|
81
|
+
# for JSON types, get the required subject_token field name.
|
82
|
+
@credential_source_field_name = @credential_source_format[:subject_token_field_name]
|
83
|
+
if @credential_source_format_type == "json" && @credential_source_field_name.nil?
|
84
|
+
raise "Missing subject_token_field_name for JSON credential_source format"
|
85
|
+
end
|
86
|
+
# check file or url must be fulfilled and mutually exclusiveness.
|
87
|
+
if @credential_source_file && @credential_source_url
|
88
|
+
raise "Ambiguous credential_source. 'file' is mutually exclusive with 'url'."
|
89
|
+
end
|
90
|
+
return unless (@credential_source_file || @credential_source_url).nil?
|
91
|
+
raise "Missing credential_source. A 'file' or 'url' must be provided."
|
92
|
+
end
|
93
|
+
|
94
|
+
def token_data
|
95
|
+
@credential_source_file.nil? ? url_data : file_data
|
96
|
+
end
|
97
|
+
|
98
|
+
def file_data
|
99
|
+
raise "File #{@credential_source_file} was not found." unless File.exist? @credential_source_file
|
100
|
+
content = File.read @credential_source_file, encoding: "utf-8"
|
101
|
+
[content, @credential_source_file]
|
102
|
+
end
|
103
|
+
|
104
|
+
def url_data
|
105
|
+
begin
|
106
|
+
response = connection.get @credential_source_url do |req|
|
107
|
+
req.headers.merge! @credential_source_headers
|
108
|
+
end
|
109
|
+
rescue Faraday::Error => e
|
110
|
+
raise "Error retrieving from credential url: #{e}"
|
111
|
+
end
|
112
|
+
raise "Unable to retrieve Identity Pool subject token #{response.body}" unless response.success?
|
113
|
+
[response.body, @credential_source_url]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -16,6 +16,7 @@ require "time"
|
|
16
16
|
require "uri"
|
17
17
|
require "googleauth/credentials_loader"
|
18
18
|
require "googleauth/external_account/aws_credentials"
|
19
|
+
require "googleauth/external_account/identity_pool_credentials"
|
19
20
|
|
20
21
|
module Google
|
21
22
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -28,7 +29,8 @@ module Google
|
|
28
29
|
class Credentials
|
29
30
|
# The subject token type used for AWS external_account credentials.
|
30
31
|
AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request".freeze
|
31
|
-
|
32
|
+
MISSING_CREDENTIAL_SOURCE = "missing credential source for external account".freeze
|
33
|
+
INVALID_EXTERNAL_ACCOUNT_TYPE = "credential source is not supported external account type".freeze
|
32
34
|
|
33
35
|
# Create a ExternalAccount::Credentials
|
34
36
|
#
|
@@ -40,30 +42,47 @@ module Google
|
|
40
42
|
raise "A json file is required for external account credentials." unless json_key_io
|
41
43
|
user_creds = read_json_key json_key_io
|
42
44
|
|
43
|
-
#
|
44
|
-
|
45
|
+
# AWS credentials is determined by aws subject token type
|
46
|
+
return make_aws_credentials user_creds, scope if user_creds[:subject_token_type] == AWS_SUBJECT_TOKEN_TYPE
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
subject_token_type: user_creds["subject_token_type"],
|
50
|
-
token_url: user_creds["token_url"],
|
51
|
-
credential_source: user_creds["credential_source"],
|
52
|
-
service_account_impersonation_url: user_creds["service_account_impersonation_url"]
|
53
|
-
)
|
48
|
+
raise MISSING_CREDENTIAL_SOURCE if user_creds[:credential_source].nil?
|
49
|
+
user_creds[:scope] = scope
|
50
|
+
make_external_account_credentials user_creds
|
54
51
|
end
|
55
52
|
|
56
53
|
# Reads the required fields from the JSON.
|
57
54
|
def self.read_json_key json_key_io
|
58
|
-
json_key = MultiJson.load json_key_io.read
|
55
|
+
json_key = MultiJson.load json_key_io.read, symbolize_keys: true
|
59
56
|
wanted = [
|
60
|
-
|
57
|
+
:audience, :subject_token_type, :token_url, :credential_source
|
61
58
|
]
|
62
59
|
wanted.each do |key|
|
63
60
|
raise "the json is missing the #{key} field" unless json_key.key? key
|
64
61
|
end
|
65
62
|
json_key
|
66
63
|
end
|
64
|
+
|
65
|
+
class << self
|
66
|
+
private
|
67
|
+
|
68
|
+
def make_aws_credentials user_creds, scope
|
69
|
+
Google::Auth::ExternalAccount::AwsCredentials.new(
|
70
|
+
audience: user_creds[:audience],
|
71
|
+
scope: scope,
|
72
|
+
subject_token_type: user_creds[:subject_token_type],
|
73
|
+
token_url: user_creds[:token_url],
|
74
|
+
credential_source: user_creds[:credential_source],
|
75
|
+
service_account_impersonation_url: user_creds[:service_account_impersonation_url]
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def make_external_account_credentials user_creds
|
80
|
+
unless user_creds[:credential_source][:file].nil? && user_creds[:credential_source][:url].nil?
|
81
|
+
return Google::Auth::ExternalAccount::IdentityPoolCredentials.new user_creds
|
82
|
+
end
|
83
|
+
raise INVALID_EXTERNAL_ACCOUNT_TYPE
|
84
|
+
end
|
85
|
+
end
|
67
86
|
end
|
68
87
|
end
|
69
88
|
end
|
@@ -76,6 +76,20 @@ module Google
|
|
76
76
|
# TODO: Add the ability to add authentication to the headers
|
77
77
|
headers = URLENCODED_HEADERS.dup.merge(options[:additional_headers] || {})
|
78
78
|
|
79
|
+
request_body = make_request options
|
80
|
+
|
81
|
+
response = connection.post @token_exchange_endpoint, URI.encode_www_form(request_body), headers
|
82
|
+
|
83
|
+
if response.status != 200
|
84
|
+
raise "Token exchange failed with status #{response.status}"
|
85
|
+
end
|
86
|
+
|
87
|
+
MultiJson.load response.body
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def make_request options = {}
|
79
93
|
request_body = {
|
80
94
|
grant_type: options[:grant_type],
|
81
95
|
audience: options[:audience],
|
@@ -84,14 +98,10 @@ module Google
|
|
84
98
|
subject_token: options[:subject_token],
|
85
99
|
subject_token_type: options[:subject_token_type]
|
86
100
|
}
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
if response.status != 200
|
91
|
-
raise "Token exchange failed with status #{response.status}"
|
101
|
+
unless options[:additional_options].nil?
|
102
|
+
request_body[:options] = CGI.escape MultiJson.dump(options[:additional_options], symbolize_name: true)
|
92
103
|
end
|
93
|
-
|
94
|
-
MultiJson.load response.body
|
104
|
+
request_body
|
95
105
|
end
|
96
106
|
end
|
97
107
|
end
|
data/lib/googleauth/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: googleauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Emiola
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -143,6 +143,8 @@ files:
|
|
143
143
|
- lib/googleauth/external_account.rb
|
144
144
|
- lib/googleauth/external_account/aws_credentials.rb
|
145
145
|
- lib/googleauth/external_account/base_credentials.rb
|
146
|
+
- lib/googleauth/external_account/external_account_utils.rb
|
147
|
+
- lib/googleauth/external_account/identity_pool_credentials.rb
|
146
148
|
- lib/googleauth/helpers/connection.rb
|
147
149
|
- lib/googleauth/iam.rb
|
148
150
|
- lib/googleauth/id_tokens.rb
|