Skip to content

Commit

Permalink
Support opensearch-ruby 4.0.0
Browse files Browse the repository at this point in the history
Signed-off-by: Issei.M <issei.m7@gmail.com>
  • Loading branch information
issei-m committed Feb 25, 2025
1 parent 68dbf52 commit 260a128
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 71 deletions.
139 changes: 76 additions & 63 deletions lib/opensearch-aws-sigv4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion opensearch-aws-sigv4.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion spec/integration/open_search/aws/sigv4_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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',
Expand Down

0 comments on commit 260a128

Please sign in to comment.