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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 553d9c5927ca82c62dafc3a90529029ffd7813d99a9ae4ae146ae27370beb631
4
- data.tar.gz: 55b33ba8e1ea2cf72f1f0c6c3c356ca825a4d575d8ac5665dc038118fc635198
3
+ metadata.gz: 146d1c41b29049fb35b0cc5c4934a811eb547f3d3000a95e3fbd8a24fc2b86bb
4
+ data.tar.gz: cb613b51160da7a51c545728ce03dac8977fee8f1806b72eca97997d28645450
5
5
  SHA512:
6
- metadata.gz: 73a897d4813f5f016b252a299d126535fa32f017aa40ac7ac67200bef1332696298e2e082c279c3b8b850ca1d93d46f8a56f75d5c3ab408c33db391572944af7
7
- data.tar.gz: b0d137864100e7e16ae1bac0b22ca7b2b09b5a9724692ed794170dbd712bc16b6ef00ad0913c47620d6df3e7324ce5a54484e2e4f9dfe06f999ebbeffd3c6007
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'
@@ -60,7 +60,7 @@ module Google
60
60
  GCECredentials.unmemoize_all
61
61
  raise NOT_FOUND_ERROR
62
62
  end
63
- GCECredentials.new scope: scope
63
+ GCECredentials.new options.merge(scope: scope)
64
64
  end
65
65
  end
66
66
  end
@@ -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
- # by utilizing the AWS EC2 metadata service and then exchanging the
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["environment_id"]
41
- @region_url = @credential_source["region_url"]
42
- @credential_verification_url = @credential_source["url"]
43
- @regional_cred_verification_url = @credential_source["regional_cred_verification_url"]
44
- @imdsv2_session_token_url = @credential_source["imdsv2_session_token_url"]
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
- # environment variable or from the AWS metadata server availability-zone
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
- # from the AWS metadata server security-credentials endpoint.
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
- # security-credentials endpoint, the AWS role needs to be determined by
66
- # calling the security-credentials endpoint without any argument. Then the
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
- # signed request. This will be the subject-token to pass to GCP STS.
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 get_aws_resource url, name, data: nil, headers: {}
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
- unless [nil, url].include? @imdsv2_session_token_url
110
- headers["x-aws-ec2-metadata-token"] = get_aws_resource(
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
- # requests from either the AWS security credentials environment variables
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
- # workload by querying the AWS metadata server. This is needed for the
168
- # AWS metadata server security credentials endpoint in order to retrieve
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
- # requests to AWS APIs based on the AWS Signature Version 4 signing process.
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
- # Retrieves the project ID corresponding to the workload identity or workforce pool.
79
- # For workforce pool credentials, it returns the project ID corresponding to the workforce_pool_user_project.
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
- # The resource may not have permission (resourcemanager.projects.get) to
83
- # call this API or the required scopes may not be selected:
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
- # Retrieve the project number corresponding to workload identity pool
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
- def project_number
119
- segments = @audience.split "/"
120
- idx = segments.index "projects"
121
- return nil if idx.nil? || idx + 1 == segments.size
122
- segments[idx + 1]
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
- end
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
- AWS_SUBJECT_TOKEN_INVALID = "aws is the only currently supported external account type".freeze
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
- # TODO: check for other External Account Credential types. Currently only AWS is supported.
44
- raise AWS_SUBJECT_TOKEN_INVALID unless user_creds["subject_token_type"] == AWS_SUBJECT_TOKEN_TYPE
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
- Google::Auth::ExternalAccount::AwsCredentials.new(
47
- audience: user_creds["audience"],
48
- scope: scope,
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
- "audience", "subject_token_type", "token_url", "credential_source"
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
- response = connection.post @token_exchange_endpoint, URI.encode_www_form(request_body), headers
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
@@ -16,6 +16,6 @@ module Google
16
16
  # Module Auth provides classes that provide Google-specific authorization
17
17
  # used to access Google APIs.
18
18
  module Auth
19
- VERSION = "1.5.1".freeze
19
+ VERSION = "1.6.0".freeze
20
20
  end
21
21
  end
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.5.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-04-10 00:00:00.000000000 Z
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