# frozen_string_literal: true require_relative '../spec_helper' require 'stringio' module Aws module S3 describe Client do let(:client_opts) do { region: 'us-east-1', credentials: Credentials.new('akid', 'secret') } end let(:client) { Client.new(client_opts) } let(:auth_header_malformed_body) do '\n'\ 'AuthorizationHeaderMalformedThe '\ "authorization header is malformed; the region 'us-east-1' is "\ "wrong; expecting 'us-west-2'"\ 'us-west-2531B68B3613F5C96'\ 'TMnOREh0Ms0touCRX0XkJinw7xqsF0v/iFyA+nCC4d3PpF+k2oek'\ 'WlSrUk+8d2/rvcnEv2QXer0=' end let(:expired_credentials_body) do '\n'\ 'ExpiredTokenThe provided token has expired'\ '531B68B3613F5C96'\ 'TMnOREh0Ms0touCRX0XkJinw7xqsF0v/iFyA+nCC4d3PpF+k2oek'\ 'WlSrUk+8d2/rvcnEv2QXer0=' end before(:each) do allow($stderr).to receive(:write) S3::BUCKET_REGIONS.clear end context 'accessing us-west-2 bucket using classic endpoint' do it 'detects the moved permanently and redirects' do stub_request(:put, 'https://bucket.s3.amazonaws.com/key') .to_return(status: [400, 'Bad Request'], body: auth_header_malformed_body) stub_request(:put, 'https://bucket.s3.us-west-2.amazonaws.com/key') .to_return(status: [200, 'Ok']) client = S3::Client.new(client_opts) expect_any_instance_of(Plugins::S3Signer::BucketRegionErrorHandler) .to receive(:warn) expect_auth({ 'signingRegion' => 'us-east-1' }) expect_auth({ 'signingRegion' => 'us-east-1' }, 'us-west-2') resp = client.put_object(bucket: 'bucket', key: 'key', body: 'body') host = resp.context.http_request.endpoint.host expect(host).to eq('bucket.s3.us-west-2.amazonaws.com') end it 'does not redirect fips endpoints' do stub_request( :put, 'https://bucket.s3-fips.us-east-1.amazonaws.com/key' ).to_return( status: [301, 'Moved Permanently'] ) client = S3::Client.new( client_opts.merge(use_fips_endpoint: true) ) expect do client.put_object(bucket: 'bucket', key: 'key', body: 'body') end.to raise_error(Aws::S3::Errors::Http301Error) end it 'does not redirect with expired credentials' do client = S3::Client.new(client_opts) stub_request(:put, 'https://s3.amazonaws.com/my.bucket/key') .to_return(status: [400, 'ExpiredToken'], body: expired_credentials_body) expect do client.put_object(bucket: 'my.bucket', key: 'key') end.to raise_error(Aws::S3::Errors::ExpiredToken) end it 'does not redirect custom endpoints' do stub_request(:put, 'http://bucket.localhost:9000/key') .to_return(status: [400, 'Bad Request'], body: auth_header_malformed_body) client = S3::Client.new( client_opts.merge(endpoint: 'http://localhost:9000') ) expect do client.put_object(bucket: 'bucket', key: 'key', body: 'body') end.to raise_error(Aws::S3::Errors::AuthorizationHeaderMalformed) end it 'does not redirect custom endpoints when the region is cached' do S3::BUCKET_REGIONS['bucket'] = 'us-west-2' stub_request(:put, 'http://bucket.localhost:9000/key') .to_return(status: [200, 'Ok']) client = S3::Client.new( client_opts.merge(endpoint: 'http://localhost:9000') ) expect_auth({ 'signingRegion' => 'us-east-1' }) resp = client.put_object(bucket: 'bucket', key: 'key', body: 'body') host = resp.context.http_request.endpoint.host expect(host).to eq('bucket.localhost') end it 'does not redirect regional endpoints' do stub_request( :put, 'https://bucket.s3.us-east-2.amazonaws.com/key' ).to_return( status: [301, 'Moved Permanently'] ) client = S3::Client.new( client_opts.merge(region: 'us-east-2') ) expect do client.put_object(bucket: 'bucket', key: 'key', body: 'body') end.to raise_error(Aws::S3::Errors::Http301Error) end end describe 'Client Side Monitoring' do let(:stub_publisher) do StubPublisher.new end let(:client_opts) do { region: 'us-east-1', credentials: Credentials.new('akid', 'secret'), client_side_monitoring: true, client_side_monitoring_publisher: stub_publisher } end let(:client) { Client.new(client_opts) } before(:each) do stub_publisher.metrics = [] end it 'updates fqdn and region in metrics when a 400 redirect occurs' do stub_request(:put, 'https://bucket.s3.amazonaws.com/key') .to_return(status: [400, 'Bad Request'], body: auth_header_malformed_body) stub_request(:put, 'https://bucket.s3.us-west-2.amazonaws.com/key') .to_return(status: [200, 'Ok']) expect_any_instance_of(Plugins::S3Signer::BucketRegionErrorHandler) .to receive(:warn) expect_auth({ 'signingRegion' => 'us-east-1' }) expect_auth({ 'signingRegion' => 'us-east-1' }, 'us-west-2') client.put_object(bucket: 'bucket', key: 'key', body: 'body') expect(stub_publisher.metrics.size).to eq(1) metric = stub_publisher.metrics.first expect(metric.api_call.region).to eq('us-west-2') expect(metric.api_call_attempts.size).to eq(2) fail_attempt = metric.api_call_attempts[0] good_attempt = metric.api_call_attempts[1] expect(fail_attempt.region).to eq('us-east-1') expect(fail_attempt.fqdn).to eq('bucket.s3.amazonaws.com') expect(fail_attempt.http_status_code).to eq(400) expect(good_attempt.region).to eq('us-west-2') expect(good_attempt.fqdn).to eq( 'bucket.s3.us-west-2.amazonaws.com' ) expect(good_attempt.http_status_code).to eq(200) end end end class StubPublisher attr_accessor :metrics attr_accessor :agent_port attr_accessor :agent_host def initialize @metrics = [] end def publish(request_metrics) @metrics << request_metrics end end end end