From f8ca6ced6c745e18c3a00ea712ebe3396b12f75a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 9 Apr 2024 14:43:49 -0700 Subject: [PATCH] storage: fix incorrect API scopes for IAM SignBlob API Previously when a service account attempted to use the IAM SignBlob API, the request would fail with a 403 `ACCESS_TOKEN_SCOPE_INSUFFICIENT` because the wrong scope was requested. As documented in https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob, either `https://www.googleapis.com/auth/iam` or `https://www.googleapis.com/auth/cloud-platform` is needed. This commit fixes an issue where the default authorization header with the `https://www.googleapis.com/auth/devstorage.full_control` scope was being used by the IAM service. This occurred because the previous code did not actually set the scope properly, and for the IAM service to work properly, we need to request a new access token with the correct scope. Note that the service account in question needs to have the `Service Account Token Creator` IAM role to work. Closes #599 --- lib/fog/google/shared.rb | 17 +++++++++-------- lib/fog/storage/google_json.rb | 2 +- lib/fog/storage/google_json/real.rb | 20 ++++++++++++++------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/fog/google/shared.rb b/lib/fog/google/shared.rb index 0520a695b..a70e75697 100644 --- a/lib/fog/google/shared.rb +++ b/lib/fog/google/shared.rb @@ -67,20 +67,21 @@ def initialize_google_client(options) ::Google::Apis.logger.level = ::Logger::DEBUG end - auth = nil + initialize_auth(options).tap do |auth| + ::Google::Apis::RequestOptions.default.authorization = auth + end + end + def initialize_auth(options) if options[:google_json_key_location] || options[:google_json_key_string] - auth = process_key_auth(options) + process_key_auth(options) elsif options[:google_auth] - auth = options[:google_auth] + options[:google_auth] elsif options[:google_application_default] - auth = process_application_default_auth(options) + process_application_default_auth(options) else - auth = process_fallback_auth(options) + process_fallback_auth(options) end - - ::Google::Apis::RequestOptions.default.authorization = auth - auth end ## diff --git a/lib/fog/storage/google_json.rb b/lib/fog/storage/google_json.rb index 5d7ca9afa..aec31fbf3 100644 --- a/lib/fog/storage/google_json.rb +++ b/lib/fog/storage/google_json.rb @@ -29,7 +29,7 @@ class GoogleJSON < Fog::Service # Version of IAM API used for blob signing, see Fog::Storage::GoogleJSON::Real#iam_signer GOOGLE_STORAGE_JSON_IAM_API_VERSION = "v1".freeze - GOOGLE_STORAGE_JSON_IAM_API_SCOPE_URLS = %w(https://www.googleapis.com/auth/devstorage.full_control).freeze + GOOGLE_STORAGE_JSON_IAM_API_SCOPE_URLS = %w(https://www.googleapis.com/auth/iam).freeze # TODO: Come up with a way to only request a subset of permissions. # https://cloud.google.com/storage/docs/json_api/v1/how-tos/authorizing diff --git a/lib/fog/storage/google_json/real.rb b/lib/fog/storage/google_json/real.rb index d562a07d1..7c627da30 100644 --- a/lib/fog/storage/google_json/real.rb +++ b/lib/fog/storage/google_json/real.rb @@ -10,16 +10,12 @@ class Real def initialize(options = {}) shared_initialize(options[:google_project], GOOGLE_STORAGE_JSON_API_VERSION, GOOGLE_STORAGE_JSON_BASE_URL) + @options = options.dup options[:google_api_scope_url] = GOOGLE_STORAGE_JSON_API_SCOPE_URLS.join(" ") @host = options[:host] || "storage.googleapis.com" # TODO(temikus): Do we even need this client? @client = initialize_google_client(options) - # IAM client used for SignBlob API - @iam_service = ::Google::Apis::IamcredentialsV1::IAMCredentialsService.new - apply_client_options(@iam_service, { - google_api_scope_url: GOOGLE_STORAGE_JSON_IAM_API_SCOPE_URLS.join(" ") - }) @storage_json = ::Google::Apis::StorageV1::StorageService.new apply_client_options(@storage_json, options) @@ -141,6 +137,18 @@ def default_signer(string_to_sign) return key.sign(digest, string_to_sign) end + # IAM client used for SignBlob API. + # Lazily initialize this since it requires another authorization request. + def iam_service + return @iam_service if defined?(@iam_service) + + @iam_service = ::Google::Apis::IamcredentialsV1::IAMCredentialsService.new + apply_client_options(@iam_service, @options) + iam_options = @options.merge(google_api_scope_url: GOOGLE_STORAGE_JSON_IAM_API_SCOPE_URLS.join(" ")) + @iam_service.authorization = initialize_auth(iam_options) + @iam_service + end + ## # Fallback URL signer using the IAM SignServiceAccountBlob API, see # Google::Apis::IamcredentialsV1::IAMCredentialsService#sign_service_account_blob @@ -162,7 +170,7 @@ def iam_signer(string_to_sign) ) resource = "projects/-/serviceAccounts/#{google_access_id}" - response = @iam_service.sign_service_account_blob(resource, request) + response = iam_service.sign_service_account_blob(resource, request) return response.signed_blob end