Я пытаюсь настроить функцию облака Google (2-го поколения с триггером HTTP), но, похоже, не могу пройти неавторизованную ошибку 401. Я заработал, используя токен удостоверения, который я сгенерировал через интерфейс gcloud, но мне нужно использовать учетную запись службы, чтобы избежать истечения срока действия токена удостоверения в 1 час.
Я установил разрешения для вызова облачной функции и запуска облака для учетной записи службы, а также напрямую подключил учетную запись службы к облачной функции. Несмотря на то, что я делаю, я не могу добиться авторизации с использованием учетной записи службы.
Я пытаюсь пройти аутентификацию в приложении Rails, используя драгоценный камень Googleauth. Ниже мой сервис.
# app/services/cloud_function_service.rb
class CloudFunctionService
# Use the credentials and URL from Rails credentials or environment
def self.api_request(function_type, body_data)
new.api_request(function_type, body_data)
end
def api_request(function_type, body_data)
access_token = fetch_access_token
uri = Rails.application.credentials.CLOUD_FUNCTION_URL
body = {
'function_type': function_type,
'body': body_data
}.to_json
headers = {
'Authorization' => "Bearer #{access_token}",
'Content-Type' => 'application/json'
}
# Make the POST request with headers and body
result = HTTParty.post(uri, body: body, headers: headers)
JSON.parse(result.body)
rescue => e
Rails.logger.error "Failed to call cloud function: #{e}"
nil
end
private
def fetch_access_token
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open('./google-service-account-keys.json'),
scope: 'https://www.googleapis.com/auth/cloud-platform'
)
response = authorizer.fetch_access_token!
response['access_token']
end
end
🤔 А знаете ли вы, что...
Ruby on Rails активно использует принцип DRY (Don't Repeat Yourself), что способствует уменьшению дублирования кода.
Отвечаю здесь на свой вопрос. На самом деле существовало две проблемы, которые могут оказаться полезными для всех, кто вызывает облачную функцию из приложения Ruby on Rails.
Во-первых, я неправильно понял использование токена доступа и токена идентификации, но на самом деле вам нужны оба. Токен доступа необходим для доступа к конечным точкам API Google (конечные точки облачных функций не считаются частью). Токен идентификации необходим для доступа к конечным точкам облачной функции. Но... вы используете токен доступа, чтобы получить токен идентификации.
Во-вторых, в отличие от других языковых библиотек для Google Auth, библиотека Ruby не предоставляет метода получения токена идентификации. Он предоставляет только метод получения токена доступа. Вам необходимо создать собственный обработчик API для конечной точки токена идентификации.
В принципе поток выглядит так:
Вот мой окончательный код, который работает!
class CloudFunctionService
def self.api_request(function_type, body_data)
new.api_request(function_type, body_data)
end
def api_request(function_type, body_data)
access_token = fetch_access_token
service_account_email = _your_gcp_service_account_email_
target_audience = _your_cloud_function_uri_
identity_token = fetch_identity_token(service_account_email, target_audience, access_token)
uri = _your_cloud_function_uri_
body = {
# Your body data
}.to_json
headers = {
'Authorization' => "Bearer #{identity_token}",
'Content-Type' => 'application/json'
}
result = HTTParty.post(uri, body: body, headers: headers)
JSON.parse(result.body)
rescue => e
Rails.logger.error "Failed to call cloud function: #{e}"
nil
end
private
def fetch_access_token
decoded_json_key = Base64.decode64(_your_base64_encoded_json_key_file)
json_key_io = StringIO.new(decoded_json_key)
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: json_key_io,
scope: 'https://www.googleapis.com/auth/cloud-platform'
)
authorizer.fetch_access_token!['access_token']
end
def fetch_identity_token(service_account_email, target_audience, access_token)
url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/#{service_account_email}:generateIdToken"
body = {
audience: target_audience,
includeEmail: true
}.to_json
response = HTTParty.post(
url,
body: body,
headers: {
'Content-Type' => 'application/json',
'Authorization' => "Bearer #{access_token}"
}
)
if response.code == 200
JSON.parse(response.body)['token']
else
raise "Failed to fetch identity token: #{response.body}"
end
end
end