From 260a128e7ba620ecd6a2e90042631f2cb538f78f Mon Sep 17 00:00:00 2001 From: "Issei.M" Date: Sun, 23 Feb 2025 10:21:24 +0900 Subject: [PATCH] Support opensearch-ruby 4.0.0 Signed-off-by: Issei.M --- lib/opensearch-aws-sigv4.rb | 139 ++++++++++-------- opensearch-aws-sigv4.gemspec | 2 +- .../open_search/aws/sigv4_client_spec.rb | 2 +- .../aws/{ => transport}/sigv4_client_spec.rb | 13 +- 4 files changed, 85 insertions(+), 71 deletions(-) rename spec/unit/open_search/aws/{ => transport}/sigv4_client_spec.rb (90%) diff --git a/lib/opensearch-aws-sigv4.rb b/lib/opensearch-aws-sigv4.rb index dea754a..00a947d 100644 --- a/lib/opensearch-aws-sigv4.rb +++ b/lib/opensearch-aws-sigv4.rb @@ -17,6 +17,77 @@ module OpenSearch module Aws + module Transport + # Extending OpenSearch::Transport::Client to sign requests with SigV4. + class Sigv4Client < ::OpenSearch::Transport::Client + attr_accessor :sigv4_signer + + def initialize(transport_args, sigv4_signer, sigv4_debug, &block) + unless sigv4_signer.is_a?(::Aws::Sigv4::Signer) + raise ArgumentError, "Please pass a Aws::Sigv4::Signer. A #{sigv4_signer.class} was given." + end + + super(transport_args, &block) + @sigv4_signer = sigv4_signer + @sigv4_debug = sigv4_debug + @logger = transport.logger + end + + # @see ::OpenSearch::Transport::Client#perform_request + def perform_request(method, path, params = {}, body = nil, headers = nil) + signature_body = body.is_a?(Hash) ? body.to_json : body.to_s + signature = sigv4_signer.sign_request( + http_method: method, + url: signature_url(path, params), + headers: headers, + body: signature_body + ) + headers = (headers || {}).merge(signature.headers) + + log_signature_info(signature) + super(method, path, params, signature_body, headers) + end + + private + + def signature_url(path, params) + host = transport.hosts.dig(0, :host) + path = "/#{path}" unless path.start_with?('/') + params = params.clone + params.delete(:ignore) + params.delete('ignore') + query_string = params.empty? ? '' : Faraday::Utils::ParamsHash[params].to_query.to_s + URI::HTTP.build(host: host, path: path, query: query_string) + end + + # @param [Aws::Sigv4::Signature] signature + def log_signature_info(signature) + return unless @sigv4_debug + + log('string to sign', signature.string_to_sign) + log('canonical request', signature.canonical_request) + log('signature headers', signature.headers) + end + + def log(title, message) + logger.debug("#{title.upcase}:\n\e[36m#{message}\e[0m") + end + + def logger + return @logger if @logger + + require 'logger' + @logger = Logger.new( + $stdout, + progname: 'Sigv4', + formatter: proc { |_severity, datetime, progname, msg| + "\e[34m(#{datetime}) #{progname} - #{msg}\e[0m\n\n" + } + ) + end + end + end + # AWS Sigv4 Wrapper for OpenSearch::Client. # This client accepts a Sigv4 Signer during initialization, and signs every request # with a Sigv4 Signature with the provided signer. @@ -36,6 +107,7 @@ module Aws # puts client.cat.health # # @attr [Aws::Sigv4::Signer] sigv4_signer Signer used to sign every request + # rubocop:disable Lint/MissingSuper class Sigv4Client < ::OpenSearch::Client attr_accessor :sigv4_signer @@ -44,71 +116,12 @@ class Sigv4Client < ::OpenSearch::Client # @param [Aws::Sigv4::Signer] sigv4_signer an instance of AWS Sigv4 Signer. # @param [Hash] options # @option options [Boolean] :sigv4_debug whether to log debug info for Sigv4 Signing + # disable:rubocop Lint/MissingSuper def initialize(transport_args, sigv4_signer, options: {}, &block) - unless sigv4_signer.is_a?(::Aws::Sigv4::Signer) - raise ArgumentError, "Please pass a Aws::Sigv4::Signer. A #{sigv4_signer.class} was given." - end - - @sigv4_signer = sigv4_signer - @sigv4_debug = options[:sigv4_debug] - @logger = nil - super(transport_args, &block) - end - - # @see OpenSearch::Transport::Transport::Base::perform_request - def perform_request(method, path, params = {}, body = nil, headers = nil) - signature_body = body.is_a?(Hash) ? body.to_json : body.to_s - signature = sigv4_signer.sign_request( - http_method: method, - url: signature_url(path, params), - headers: headers, - body: signature_body - ) - headers = (headers || {}).merge(signature.headers) - - log_signature_info(signature) - super(method, path, params, signature_body, headers) - end - - private - - def verify_open_search - @verified = true - end - - def signature_url(path, params) - host = @transport.transport.hosts.dig(0, :host) - path = "/#{path}" unless path.start_with?('/') - params = params.clone - params.delete(:ignore) - params.delete('ignore') - query_string = params.empty? ? '' : Faraday::Utils::ParamsHash[params].to_query.to_s - URI::HTTP.build(host: host, path: path, query: query_string) - end - - # @param [Aws::Sigv4::Signature] signature - def log_signature_info(signature) - return unless @sigv4_debug - - log('string to sign', signature.string_to_sign) - log('canonical request', signature.canonical_request) - log('signature headers', signature.headers) - end - - def log(title, message) - logger.debug("#{title.upcase}:\n\e[36m#{message}\e[0m") - end - - def logger - return @logger if @logger - - require 'logger' - @logger = Logger.new( - $stdout, - progname: 'Sigv4', - formatter: proc { |_severity, datetime, progname, msg| "\e[34m(#{datetime}) #{progname} - #{msg}\e[0m\n\n" } - ) + @transport = OpenSearch::Aws::Transport::Sigv4Client.new(transport_args, sigv4_signer, options[:sigv4_debug], + &block) end + # rubocop:enable Lint/MissingSuper end end end diff --git a/opensearch-aws-sigv4.gemspec b/opensearch-aws-sigv4.gemspec index 9127482..b6f2fcf 100644 --- a/opensearch-aws-sigv4.gemspec +++ b/opensearch-aws-sigv4.gemspec @@ -50,5 +50,5 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.4' s.add_dependency 'aws-sigv4', '>= 1' - s.add_dependency 'opensearch-ruby', '>= 1.0.1', '< 4.0' + s.add_dependency 'opensearch-ruby', '>= 4.0.0.pre.beta.1' end diff --git a/spec/integration/open_search/aws/sigv4_client_spec.rb b/spec/integration/open_search/aws/sigv4_client_spec.rb index 5f88849..e522e38 100644 --- a/spec/integration/open_search/aws/sigv4_client_spec.rb +++ b/spec/integration/open_search/aws/sigv4_client_spec.rb @@ -20,7 +20,7 @@ access_key_id: 'key_id', secret_access_key: 'secret') - described_class.new({ host: OPENSEARCH_URL, logger: Logger.new($stdout) }, signer) + described_class.new({ host: OPENSEARCH_URL, logger: Logger.new($stdout) }, signer, options: { sigv4_debug: true }) end it 'performs API actions without throwing any errors' do diff --git a/spec/unit/open_search/aws/sigv4_client_spec.rb b/spec/unit/open_search/aws/transport/sigv4_client_spec.rb similarity index 90% rename from spec/unit/open_search/aws/sigv4_client_spec.rb rename to spec/unit/open_search/aws/transport/sigv4_client_spec.rb index 0d17cd1..def6dd8 100644 --- a/spec/unit/open_search/aws/sigv4_client_spec.rb +++ b/spec/unit/open_search/aws/transport/sigv4_client_spec.rb @@ -9,16 +9,17 @@ # frozen_string_literal: true -require_relative '../../../spec_helper' +require_relative '../../../../spec_helper' require 'aws-sigv4' require 'timecop' -describe OpenSearch::Aws::Sigv4Client do +describe OpenSearch::Aws::Transport::Sigv4Client do subject(:client) do described_class.new( { host: 'http://localhost:9200', transport_options: { ssl: { verify: false } } }, - signer + signer, + false ) end @@ -48,12 +49,12 @@ describe '#perform_request' do let(:response) { { body: 'Response Body' } } let(:transport_double) do - double = instance_double(OpenSearch::Transport::Client, perform_request: response) - allow(double).to receive_message_chain(:transport, :hosts, :dig).and_return('localhost') + double = instance_double(OpenSearch::Transport::Client::DEFAULT_TRANSPORT_CLASS, perform_request: response) + allow(double).to receive_message_chain(:hosts, :dig).and_return('localhost') double end let(:signed_headers) do - { 'authorization' => 'AWS4-HMAC-SHA256 Credential=key_id/20220101/us-west-2/es/aws4_request, '\ + { 'authorization' => 'AWS4-HMAC-SHA256 Credential=key_id/20220101/us-west-2/es/aws4_request, ' \ 'SignedHeaders=host;x-amz-content-sha256;x-amz-date, ' \ 'Signature=9c4c690110483308f62a91c2ca873857750bca2607ba1aabdae0d2303950310a', 'host' => 'localhost',