diff --git a/build_tools/services.rb b/build_tools/services.rb index d95b7938193..d91ae467fbd 100644 --- a/build_tools/services.rb +++ b/build_tools/services.rb @@ -9,10 +9,10 @@ class ServiceEnumerator MANIFEST_PATH = File.expand_path('../../services.json', __FILE__) # Minimum `aws-sdk-core` version for new gem builds - MINIMUM_CORE_VERSION = "3.241.0" + MINIMUM_CORE_VERSION = "3.241.3" # Minimum `aws-sdk-core` version for new S3 gem builds - MINIMUM_CORE_VERSION_S3 = "3.241.0" + MINIMUM_CORE_VERSION_S3 = "3.241.3" EVENTSTREAM_PLUGIN = "Aws::Plugins::EventStreamConfiguration" diff --git a/gems/aws-sdk-core/CHANGELOG.md b/gems/aws-sdk-core/CHANGELOG.md index 4facff7a145..5eb2de0e0fb 100644 --- a/gems/aws-sdk-core/CHANGELOG.md +++ b/gems/aws-sdk-core/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Issue - Disable request trailer checksums when using non-HTTPs endpoints. + 3.241.2 (2026-01-07) ------------------ diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/plugins/checksum_algorithm.rb b/gems/aws-sdk-core/lib/aws-sdk-core/plugins/checksum_algorithm.rb index c172ef678d0..0bb447e67a3 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-core/plugins/checksum_algorithm.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-core/plugins/checksum_algorithm.rb @@ -275,8 +275,7 @@ def checksum_provided_as_header?(headers) # 1. **No existing checksum in header**: Skips if checksum header already present # 2. **Operation support**: Considers model, client configuration and user input. def should_calculate_request_checksum?(context) - !checksum_provided_as_header?(context.http_request.headers) && - checksum_applicable?(context) + !checksum_provided_as_header?(context.http_request.headers) && checksum_applicable?(context) end # Checks if checksum calculation should proceed based on operation requirements and client settings. @@ -317,12 +316,13 @@ def choose_request_algorithm!(context) end def checksum_request_in(context) - if context.operation['unsignedPayload'] || - context.operation['authtype'] == 'v4-unsigned-body' - 'trailer' - else - 'header' - end + return 'header' unless supports_trailer_checksums?(context.operation) + + should_fallback_to_header?(context) ? 'header' : 'trailer' + end + + def supports_trailer_checksums?(operation) + operation['unsignedPayload'] || operation['authtype'] == 'v4-unsigned-body' end def calculate_request_checksum(context, checksum_properties) @@ -331,12 +331,6 @@ def calculate_request_checksum(context, checksum_properties) headers[algorithm_header] = checksum_properties[:algorithm] end - # Trailer implementation within Mac/JRUBY environment is facing some - # network issues that will need further investigation: - # * https://github.com/jruby/jruby-openssl/issues/271 - # * https://github.com/jruby/jruby-openssl/issues/317 - return apply_request_checksum(context, headers, checksum_properties) if defined?(JRUBY_VERSION) - case checksum_properties[:in] when 'header' apply_request_checksum(context, headers, checksum_properties) @@ -347,6 +341,20 @@ def calculate_request_checksum(context, checksum_properties) end end + def should_fallback_to_header?(context) + # Trailer implementation within Mac/JRUBY environment is facing some + # network issues that will need further investigation: + # * https://github.com/jruby/jruby-openssl/issues/271 + # * https://github.com/jruby/jruby-openssl/issues/317 + return true if defined?(JRUBY_VERSION) + + # Chunked signing is currently not supported + # Https is required for unsigned payload for security + return true if context.http_request.endpoint.scheme == 'http' + + context[:skip_trailer_checksums] + end + def apply_request_checksum(context, headers, checksum_properties) header_name = checksum_properties[:name] headers[header_name] = calculate_checksum( @@ -422,8 +430,7 @@ def add_verify_response_checksum_handlers(context) end def add_verify_response_headers_handler(context, checksum_context) - validation_list = CHECKSUM_ALGORITHM_PRIORITIES & - operation_response_algorithms(context) + validation_list = CHECKSUM_ALGORITHM_PRIORITIES & operation_response_algorithms(context) context[:http_checksum][:validation_list] = validation_list context.http_response.on_headers do |_status, headers| diff --git a/gems/aws-sdk-s3/CHANGELOG.md b/gems/aws-sdk-s3/CHANGELOG.md index 92271206fca..0e94a15ac9d 100644 --- a/gems/aws-sdk-s3/CHANGELOG.md +++ b/gems/aws-sdk-s3/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Issue - Falls back to header request checksums when using custom endpoints or endpoint providers for PutObject and UploadPart operations. + 1.210.1 (2026-01-06) ------------------ diff --git a/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/checksum_algorithm.rb b/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/checksum_algorithm.rb index c8a5ed4ba70..b84e969cfb1 100644 --- a/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +++ b/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/checksum_algorithm.rb @@ -18,12 +18,25 @@ def call(context) end end + # Handler to disable trailer checksums for S3-compatible services + # that don't support STREAMING-UNSIGNED-PAYLOAD-TRAILER + # See: https://github.com/aws/aws-sdk-ruby/issues/3338 + class SkipTrailerChecksumsHandler < Seahorse::Client::Handler + def call(context) + context[:skip_trailer_checksums] = true if custom_endpoint?(context.config) + @handler.call(context) + end + + private + + def custom_endpoint?(config) + !config.regional_endpoint || !config.endpoint_provider.instance_of?(Aws::S3::EndpointProvider) + end + end + def add_handlers(handlers, _config) - handlers.add( - SkipWholeMultipartGetChecksumsHandler, - step: :initialize, - operations: [:get_object] - ) + handlers.add(SkipWholeMultipartGetChecksumsHandler, step: :initialize, operations: [:get_object]) + handlers.add(SkipTrailerChecksumsHandler, step: :build, priority: 16, operations: %i[put_object upload_part]) end end end diff --git a/gems/aws-sdk-s3/spec/plugins/checksum_algorithm_spec.rb b/gems/aws-sdk-s3/spec/plugins/checksum_algorithm_spec.rb index d3452c6e462..08a0b03f674 100644 --- a/gems/aws-sdk-s3/spec/plugins/checksum_algorithm_spec.rb +++ b/gems/aws-sdk-s3/spec/plugins/checksum_algorithm_spec.rb @@ -13,7 +13,7 @@ module Plugins let(:body) { 'hello world' } let(:digest) { 'DUoRhQ==' } - let(:body_part_1) { 'hello '} + let(:body_part_1) { 'hello ' } let(:digest_part_1) { '7YH59g==' } it 'validates the checksum on an Object GET' do @@ -112,6 +112,31 @@ module Plugins end context 'request trailer checksum', skip: defined?(JRUBY_VERSION) do + it 'falls back to header checksums when endpoint scheme is http' do + client = Aws::S3::Client.new(stub_responses: true, endpoint: 'http://example.com') + + resp = client.put_object(bucket: bucket, key: key, body: body, content_encoding: 'gzip') + expect(resp.context.http_request.headers['X-Amz-Content-Sha256']).not_to eq('STREAMING-UNSIGNED-PAYLOAD-TRAILER') + expect(resp.context.http_request.headers['Content-Encoding']).not_to include('aws-chunked') + end + + it 'falls back to header checksums for custom endpoint URLs' do + client = Aws::S3::Client.new(stub_responses: true, endpoint: 'https://example.com') + + resp = client.put_object(bucket: bucket, key: key, body: body, content_encoding: 'gzip') + expect(resp.context.http_request.headers['X-Amz-Content-Sha256']).not_to eq('STREAMING-UNSIGNED-PAYLOAD-TRAILER') + expect(resp.context.http_request.headers['Content-Encoding']).not_to include('aws-chunked') + end + + it 'falls back to header checksums for custom endpoint providers' do + custom_endpoint_provider = Class.new(Aws::S3::EndpointProvider).new + client = Aws::S3::Client.new(stub_responses: true, endpoint_provider: custom_endpoint_provider) + + resp = client.put_object(bucket: bucket, key: key, body: body, content_encoding: 'gzip') + expect(resp.context.http_request.headers['X-Amz-Content-Sha256']).not_to eq('STREAMING-UNSIGNED-PAYLOAD-TRAILER') + expect(resp.context.http_request.headers['Content-Encoding']).not_to include('aws-chunked') + end + it 'sets aws-chunked when no existing Content-Encoding header' do resp = client.put_object(bucket: bucket, key: key, body: body) expect(resp.context.http_request.headers['Content-Encoding']).to eq('aws-chunked')