googleauth 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|