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 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