diff --git a/aws-sdk-core/lib/aws-sdk-core.rb b/aws-sdk-core/lib/aws-sdk-core.rb index 3bd1f4bdad7..16d67d16ca7 100644 --- a/aws-sdk-core/lib/aws-sdk-core.rb +++ b/aws-sdk-core/lib/aws-sdk-core.rb @@ -324,6 +324,7 @@ module Plugins autoload :S3RequestSigner, 'aws-sdk-core/plugins/s3_request_signer' autoload :S3SseCpk, 'aws-sdk-core/plugins/s3_sse_cpk' autoload :S3UrlEncodedKeys, 'aws-sdk-core/plugins/s3_url_encoded_keys' + autoload :STSRegionalEndpoints, 'aws-sdk-core/plugins/sts_regional_endpoints' autoload :SQSQueueUrls, 'aws-sdk-core/plugins/sqs_queue_urls' autoload :SQSMd5s, 'aws-sdk-core/plugins/sqs_md5s' autoload :StubResponses, 'aws-sdk-core/plugins/stub_responses' diff --git a/aws-sdk-core/lib/aws-sdk-core/api/customizations.rb b/aws-sdk-core/lib/aws-sdk-core/api/customizations.rb index 0cdeafc719f..f53553b7d4d 100644 --- a/aws-sdk-core/lib/aws-sdk-core/api/customizations.rb +++ b/aws-sdk-core/lib/aws-sdk-core/api/customizations.rb @@ -274,6 +274,10 @@ def is_eventstream?(api, shape_name) Aws::Plugins::S3RequestSigner )) + plugins('AWS Security Token Service', add: %w( + Aws::Plugins::STSRegionalEndpoints + )) + plugins('AWS S3 Control', add: %w( Aws::Plugins::S3HostId Aws::Plugins::S3ControlDns diff --git a/aws-sdk-core/lib/aws-sdk-core/endpoint_provider.rb b/aws-sdk-core/lib/aws-sdk-core/endpoint_provider.rb index f2f57839329..7f492b297cc 100644 --- a/aws-sdk-core/lib/aws-sdk-core/endpoint_provider.rb +++ b/aws-sdk-core/lib/aws-sdk-core/endpoint_provider.rb @@ -2,12 +2,34 @@ module Aws # @api private class EndpointProvider + # when sts_regional_endpoint set to `legacy` + # endpoint pattern stays global for + # following regions + STS_LEGACY_REGIONS = %w( + ap-northeast-1 + ap-south-1 + ap-southeast-1 + ap-southeast-2 + aws-global + ca-central-1 + eu-central-1 + eu-north-1 + eu-west-1 + eu-west-2 + eu-west-3 + sa-east-1 + us-east-1 + us-east-2 + us-west-1 + us-west-2 + ) + def initialize(rules) @rules = rules end - def resolve(region, service) - "https://" + endpoint_for(region, service) + def resolve(region, service, sts_regional_endpoints = nil) + "https://" + endpoint_for(region, service, sts_regional_endpoints) end def signing_region(region, service) @@ -27,7 +49,7 @@ def dns_suffix_for(region) private - def endpoint_for(region, service) + def endpoint_for(region, service, sts_regional_endpoints) partition = get_partition(region) endpoint = default_endpoint(partition, service, region) service_cfg = partition.fetch("services", {}).fetch(service, {}) @@ -35,8 +57,13 @@ def endpoint_for(region, service) # Check for service-level default endpoint. endpoint = service_cfg.fetch("defaults", {}).fetch("hostname", endpoint) + # Check for sts legacy behavior + sts_legacy = service == 'sts' && + sts_regional_endpoints == 'legacy' && + STS_LEGACY_REGIONS.include?(region) + # Check for global endpoint. - if service_cfg["isRegionalized"] == false + if sts_legacy || service_cfg["isRegionalized"] == false region = service_cfg.fetch("partitionEndpoint", region) end @@ -81,8 +108,8 @@ def default_partition class << self - def resolve(region, service) - default_provider.resolve(region, service) + def resolve(region, service, sts_regional_endpoints = nil) + default_provider.resolve(region, service, sts_regional_endpoints) end def signing_region(region, service) diff --git a/aws-sdk-core/lib/aws-sdk-core/plugins/regional_endpoint.rb b/aws-sdk-core/lib/aws-sdk-core/plugins/regional_endpoint.rb index 9832f931171..c5ff64d8ce8 100644 --- a/aws-sdk-core/lib/aws-sdk-core/plugins/regional_endpoint.rb +++ b/aws-sdk-core/lib/aws-sdk-core/plugins/regional_endpoint.rb @@ -26,6 +26,8 @@ class RegionalEndpoint < Seahorse::Client::Plugin endpoint_prefix = cfg.api.metadata['endpointPrefix'] if cfg.region && endpoint_prefix EndpointProvider.resolve(cfg.region, endpoint_prefix) + sts_regional = cfg.respond_to?(:sts_regional_endpoints) ? cfg.sts_regional_endpoints : nil + EndpointProvider.resolve(cfg.region, endpoint_prefix, sts_regional) end end diff --git a/aws-sdk-core/lib/aws-sdk-core/plugins/sts_regional_endpoints.rb b/aws-sdk-core/lib/aws-sdk-core/plugins/sts_regional_endpoints.rb new file mode 100644 index 00000000000..231120d60a8 --- /dev/null +++ b/aws-sdk-core/lib/aws-sdk-core/plugins/sts_regional_endpoints.rb @@ -0,0 +1,30 @@ +module Aws + module Plugins + + class STSRegionalEndpoints < Seahorse::Client::Plugin + + option(:sts_regional_endpoints, + default: 'legacy', + doc_type: String, + docstring: <<-DOCS) do |cfg| +Passing in 'regional' to enable regional endpoint for STS for all supported +regions (except 'aws-global'), defaults to 'legacy' mode, using global endpoint +for legacy regions. + DOCS + resolve_sts_regional_endpoints(cfg) + end + + private + + def self.resolve_sts_regional_endpoints(cfg) + env_mode = ENV['AWS_STS_REGIONAL_ENDPOINTS'] + env_mode = nil if env_mode == '' + cfg_mode = Aws.shared_config.sts_regional_endpoints( + profile: cfg.profile) + env_mode || cfg_mode || 'legacy' + end + + end + + end +end diff --git a/aws-sdk-core/lib/aws-sdk-core/shared_config.rb b/aws-sdk-core/lib/aws-sdk-core/shared_config.rb index 9239c372a51..71c1cc2d022 100644 --- a/aws-sdk-core/lib/aws-sdk-core/shared_config.rb +++ b/aws-sdk-core/lib/aws-sdk-core/shared_config.rb @@ -128,6 +128,21 @@ def region(opts = {}) end end + def sts_regional_endpoints(opts = {}) + p = opts[:profile] || @profile_name + if @config_enabled + if @parsed_credentials + mode = @parsed_credentials.fetch(p, {})["sts_regional_endpoints"] + end + if @parsed_config + mode ||= @parsed_config.fetch(p, {})["sts_regional_endpoints"] + end + mode + else + nil + end + end + def endpoint_discovery(opts = {}) p = opts[:profile] || @profile_name if @config_enabled && @parsed_config diff --git a/aws-sdk-core/spec/aws/shared_config_spec.rb b/aws-sdk-core/spec/aws/shared_config_spec.rb index 2d866e1ebe7..758baff0c89 100644 --- a/aws-sdk-core/spec/aws/shared_config_spec.rb +++ b/aws-sdk-core/spec/aws/shared_config_spec.rb @@ -110,6 +110,26 @@ module Aws expect(config.endpoint_discovery).to eq("false") end + context 'sts_regional_endpoints selection' do + + it 'can resolve sts_regional_endpoints from config file' do + config = SharedConfig.new( + config_path: mock_config_file, + config_enabled: true, + profile_name: "sts_regional" + ) + expect(config.sts_regional_endpoints).to eq('regional') + + config = SharedConfig.new( + config_path: mock_config_file, + config_enabled: true, + profile_name: "sts_legacy" + ) + expect(config.sts_regional_endpoints).to eq('legacy') + end + + end + end end end diff --git a/aws-sdk-core/spec/aws/sts/sts_regional_endpoints_spec.rb b/aws-sdk-core/spec/aws/sts/sts_regional_endpoints_spec.rb new file mode 100644 index 00000000000..074b8be10fa --- /dev/null +++ b/aws-sdk-core/spec/aws/sts/sts_regional_endpoints_spec.rb @@ -0,0 +1,165 @@ +require_relative '../../spec_helper' + +module Aws + module STS + describe Client do + + describe ':sts_regional_endpoints' do + + let(:mock_config_file) { + File.expand_path(File.join(File.dirname(__FILE__), + '..', 'fixtures', 'credentials', 'mock_shared_config')) + } + + it 'uses ENV before shared config' do + ENV['AWS_STS_REGIONAL_ENDPOINTS'] = 'regional' + config = SharedConfig.new( + config_path: mock_config_file, + config_enabled: true, + profile_name: "sts_legacy" + ) + + allow(Aws).to receive(:shared_config).and_return(config) + client = Client.new( + stub_responses: true, + region: 'us-west-2' + ) + expect(client.config.sts_regional_endpoints).to eq('regional') + end + + it 'defaults to `legacy`' do + client = Client.new( + stub_responses: true, + region: 'us-west-2' + ) + expect(client.config.sts_regional_endpoints).to eq('legacy') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.amazonaws.com') + end + + it 'can be set `regional` in the constructor' do + client = Client.new( + stub_responses: true, + sts_regional_endpoints: 'regional', + region: 'us-west-2' + ) + expect(client.config.sts_regional_endpoints).to eq('regional') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.us-west-2.amazonaws.com') + end + + it 'can be set fron ENV' do + ENV['AWS_STS_REGIONAL_ENDPOINTS'] = 'regional' + client = Client.new( + stub_responses: true, + region: 'us-west-2' + ) + expect(client.config.sts_regional_endpoints).to eq('regional') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.us-west-2.amazonaws.com') + end + + it 'has no effect on fips endpoint' do + client = Client.new( + stub_responses: true, + region: 'us-west-2-fips' + ) + expect(client.config.sts_regional_endpoints).to eq('legacy') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts-fips.us-west-2.amazonaws.com') + + client = Client.new( + stub_responses: true, + sts_regional_endpoints: 'regional', + region: 'us-west-2-fips' + ) + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts-fips.us-west-2.amazonaws.com') + end + + it 'has no effect on aws-global even when `regional`' do + client = Client.new( + stub_responses: true, + sts_regional_endpoints: 'regional', + region: 'aws-global' + ) + expect(client.config.sts_regional_endpoints).to eq('regional') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.amazonaws.com') + end + + it 'has no effect on current regionalized regions' do + client = Client.new( + stub_responses: true, + region: 'ap-east-1' + ) + expect(client.config.sts_regional_endpoints).to eq('legacy') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.ap-east-1.amazonaws.com') + + client = Client.new( + stub_responses: true, + sts_regional_endpoints: 'regional', + region: 'ap-east-1' + ) + expect(client.config.sts_regional_endpoints).to eq('regional') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.ap-east-1.amazonaws.com') + end + + it 'has no effect on cn-north-1 region' do + client = Client.new( + stub_responses: true, + region: 'cn-north-1' + ) + expect(client.config.sts_regional_endpoints).to eq('legacy') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.cn-north-1.amazonaws.com.cn') + + client = Client.new( + stub_responses: true, + sts_regional_endpoints: 'regional', + region: 'cn-north-1' + ) + expect(client.config.sts_regional_endpoints).to eq('regional') + resp = client.get_caller_identity + expect(resp.context.http_request.endpoint.to_s).to eq( + 'https://sts.cn-north-1.amazonaws.com.cn') + end + + it 'configures properly at config' do + client = Client.new( + stub_responses: true, + region: 'us-west-2' + ) + expect(client.config.sts_regional_endpoints).to eq('legacy') + expect(client.config.region).to eq('us-west-2') + expect(client.config.sigv4_region).to eq('us-east-1') + expect(client.config.endpoint.to_s).to eq('https://sts.amazonaws.com') + + client = Client.new( + stub_responses: true, + sts_regional_endpoints: 'regional', + region: 'us-west-2' + ) + expect(client.config.sts_regional_endpoints).to eq('regional') + expect(client.config.region).to eq('us-west-2') + expect(client.config.sigv4_region).to eq('us-west-2') + expect(client.config.endpoint.to_s).to eq( + 'https://sts.us-west-2.amazonaws.com') + end + + end + + end + end +end diff --git a/aws-sdk-core/spec/fixtures/credentials/mock_shared_config b/aws-sdk-core/spec/fixtures/credentials/mock_shared_config index 57a69ae8c50..e1a4f153aef 100644 --- a/aws-sdk-core/spec/fixtures/credentials/mock_shared_config +++ b/aws-sdk-core/spec/fixtures/credentials/mock_shared_config @@ -78,3 +78,13 @@ endpoint_discovery_enabled = true aws_access_key_id = AKID aws_secret_access_key = SECRET endpoint_discovery_enabled = false + +[profile sts_regional] +aws_access_key_id = AKID +aws_secret_access_key = SECRET +sts_regional_endpoints = regional + +[profile sts_legacy] +aws_access_key_id = AKID +aws_secret_access_key = SECRET +sts_regional_endpoints = legacy