diff --git a/aws-sdk-core/lib/aws-sdk-core/s3/presigner.rb b/aws-sdk-core/lib/aws-sdk-core/s3/presigner.rb index d51b78ed955..ad73f29425a 100644 --- a/aws-sdk-core/lib/aws-sdk-core/s3/presigner.rb +++ b/aws-sdk-core/lib/aws-sdk-core/s3/presigner.rb @@ -35,6 +35,9 @@ def initialize(options = {}) # bucket name will be used as the hostname. This will cause # the returned URL to be 'http' and not 'https'. # + # @option params [Array] :whitelist_headers header([]) values to escape + # BLACKLIST_HEADERS check in V4 signer + # # @raise [ArgumentError] Raises an ArgumentError if `:expires_in` # exceeds one week. # @@ -42,12 +45,17 @@ def presigned_url(method, params = {}) if params[:key].nil? or params[:key] == '' raise ArgumentError, ":key must not be blank" end + virtual_host = !!params.delete(:virtual_host) + whitelist_headers = params.delete(:whitelist_headers) scheme = http_scheme(params, virtual_host) req = @client.build_request(method, params) use_bucket_as_hostname(req) if virtual_host - sign_but_dont_send(req, expires_in(params), scheme) + sign_but_dont_send( + req, expires_in(params), + scheme, whitelist_headers || [] + ) req.send_request.data end @@ -85,7 +93,7 @@ def use_bucket_as_hostname(req) end end - def sign_but_dont_send(req, expires_in, scheme) + def sign_but_dont_send(req, expires_in, scheme, whitelist_headers) req.handlers.remove(Plugins::S3RequestSigner::SigningHandler) req.handlers.remove(Seahorse::Client::Plugins::ContentLength::Handler) req.handle(step: :send) do |context| @@ -97,7 +105,7 @@ def sign_but_dont_send(req, expires_in, scheme) end signer = Signers::V4.new( context.config.credentials, 's3', - context.config.region + context.config.region, whitelist_headers ) url = signer.presigned_url( context.http_request, diff --git a/aws-sdk-core/lib/aws-sdk-core/signers/v4.rb b/aws-sdk-core/lib/aws-sdk-core/signers/v4.rb index b31fc7ebece..4ceb20583c9 100644 --- a/aws-sdk-core/lib/aws-sdk-core/signers/v4.rb +++ b/aws-sdk-core/lib/aws-sdk-core/signers/v4.rb @@ -39,10 +39,12 @@ def self.sign(context) # the endpoint prefix. # @param [String] region The region (e.g. 'us-west-1') the request # will be made to. - def initialize(credentials, service_name, region) + # @param [Array] whitelist headers provided to escape blacklist check + def initialize(credentials, service_name, region, whitelist_headers = []) @service_name = service_name @credentials = credentials.credentials @region = EndpointProvider.signing_region(region, service_name) + @blacklist = BLACKLIST_HEADERS - whitelist_headers end # @param [Seahorse::Client::Http::Request] req @@ -182,7 +184,7 @@ def normalized_querystring(querystring) def signed_headers(request) request.headers.keys.inject([]) do |signed_headers, header_key| header_key = header_key.downcase - unless BLACKLIST_HEADERS.include?(header_key) + unless @blacklist.include?(header_key) signed_headers << header_key end signed_headers @@ -193,7 +195,7 @@ def canonical_headers(request) headers = [] request.headers.each_pair do |k,v| k = k.downcase - headers << [k,v] unless BLACKLIST_HEADERS.include?(k) + headers << [k,v] unless @blacklist.include?(k) end headers = headers.sort_by(&:first) headers.map{|k,v| "#{k}:#{canonical_header_value(v.to_s)}" }.join("\n") diff --git a/aws-sdk-core/spec/aws/s3/presigner_spec.rb b/aws-sdk-core/spec/aws/s3/presigner_spec.rb index 12a87a45462..8e416438925 100644 --- a/aws-sdk-core/spec/aws/s3/presigner_spec.rb +++ b/aws-sdk-core/spec/aws/s3/presigner_spec.rb @@ -109,6 +109,28 @@ module S3 expect(url).to match(/^http:/) end + it 'can whitelist headers in signing' do + signer = Presigner.new(client: client) + actual_url = signer.presigned_url(:put_object, + bucket: 'aws-sdk', + key: 'foo', + cache_control: 'max-age=20000', + whitelist_headers: [ + 'cache-control' + ] + ) + expected_url = "https://aws-sdk.s3.amazonaws.com/foo?"\ + "X-Amz-Algorithm=AWS4-HMAC-SHA256&"\ + "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2F"\ + "us-east-1%2Fs3%2Faws4_request&"\ + "X-Amz-Date=20130524T000000Z&"\ + "X-Amz-Expires=900&"\ + "X-Amz-SignedHeaders=cache-control%3Bhost&"\ + "X-Amz-Signature=58bc17bb475505a1a2de9debf6bcccfbad8b22345f106c7a80bb76be1540c766" + + expect(actual_url).to eq(expected_url) + end + end end end diff --git a/aws-sdk-core/spec/aws/signers/v4_spec.rb b/aws-sdk-core/spec/aws/signers/v4_spec.rb index 9c3f652fc63..0a82ac92c61 100644 --- a/aws-sdk-core/spec/aws/signers/v4_spec.rb +++ b/aws-sdk-core/spec/aws/signers/v4_spec.rb @@ -10,6 +10,7 @@ module Signers let(:service_name) { 'SERVICE' } let(:region) { 'REGION' } let(:endpoint) { URI.parse('https://domain.com') } + let(:whitelist) { ['cache-control', 'mno'] } let(:signer) { V4.new(credentials, service_name, region) } let(:sign) { signer.sign(http_request) } let(:http_request) do @@ -133,6 +134,15 @@ module Signers expect(signer.signed_headers(http_request)).to eq('abc;mno;xyz') end + it 'ignores certain headers unless providing headers via :whitelist_headers' do + http_request.headers = {} + http_request.headers['Mno'] = '3' + http_request.headers['Cache-Control'] = '4' + http_request.headers['User-Agent'] = '5' + new_signer = V4.new(credentials, service_name, region, whitelist) + expect(new_signer.signed_headers(http_request)).to eq('cache-control;mno'); + end + end context '#canonical_headers' do @@ -160,6 +170,13 @@ module Signers expect(signer.canonical_headers(http_request)).to eq('abc:"a b c"') end + it 'ignores certain headers unless providing headers via :whitelist_headers' do + http_request.headers = {} + http_request.headers['Cache-Control'] = '4' + new_signer = V4.new(credentials, service_name, region, whitelist) + expect(new_signer.canonical_headers(http_request)).to eq('cache-control:4'); + end + end context '#normalized_querystring' do diff --git a/aws-sdk-resources/lib/aws-sdk-resources/services/s3/object.rb b/aws-sdk-resources/lib/aws-sdk-resources/services/s3/object.rb index eaad393b026..5c453b06ce7 100644 --- a/aws-sdk-resources/lib/aws-sdk-resources/services/s3/object.rb +++ b/aws-sdk-resources/lib/aws-sdk-resources/services/s3/object.rb @@ -179,6 +179,9 @@ def presigned_post(options = {}) # temporary tokens generated for signing also have a default expiration # which will affect the effective expiration of the pre-signed URL. # + # @option params [Array] :whitelist_headers ([]) header values that expected + # to escape BLACKLIST_HEADER check when using V4 signer + # # @raise [ArgumentError] Raised if `:expires_in` exceeds one week # (604800 seconds). #