Skip to content

Commit

Permalink
storage: fix incorrect API scopes for IAM SignBlob API
Browse files Browse the repository at this point in the history
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
  • Loading branch information
stanhu committed Apr 9, 2024
1 parent afd289d commit 1866bc0
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 15 deletions.
17 changes: 9 additions & 8 deletions lib/fog/google/shared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

##
Expand Down
2 changes: 1 addition & 1 deletion lib/fog/storage/google_json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 14 additions & 6 deletions lib/fog/storage/google_json/real.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 1866bc0

Please sign in to comment.