Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[STS]Support opt-in regional STS endpoint #2090

Merged
merged 11 commits into from
Oct 23, 2019
4 changes: 3 additions & 1 deletion build_tools/services.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ def plugin_path(plugin_name)
end

gems_dir = File.expand_path('../../gems', __FILE__)
(["#{gems_dir}/aws-sdk-#{gem}/lib/aws-sdk-#{gem}"] + parts).join('/') + '.rb'
prefix = gem == 'sts' ? ["#{gems_dir}/aws-sdk-core/lib/aws-sdk-#{gem}"] :
["#{gems_dir}/aws-sdk-#{gem}/lib/aws-sdk-#{gem}"]
(prefix + parts).join('/') + '.rb'
end

def gem_version(gem_name)
Expand Down
42 changes: 36 additions & 6 deletions gems/aws-partitions/lib/aws-partitions/endpoint_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@ module Partitions
# @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
)

# Intentionally marked private. The format of the endpoint rules
# is an implementation detail.
# @api private
Expand All @@ -13,9 +35,12 @@ def initialize(rules)
# @param [String] region
# @param [String] service The endpoint prefix for the service, e.g. "monitoring" for
# cloudwatch.
# @param [String] sts_regional_endpoints [STS only] Whether to use `legacy` (global endpoint for
# legacy regions) or `regional` mode for using regional endpoint for supported regions
# except 'aws-global'
# @api private Use the static class methods instead.
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

# @api private Use the static class methods instead.
Expand All @@ -37,16 +62,21 @@ 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, {})

# 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

Expand Down Expand Up @@ -91,8 +121,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ class RegionalEndpoint < Seahorse::Client::Plugin
DOCS
endpoint_prefix = cfg.api.metadata['endpointPrefix']
if cfg.region && endpoint_prefix
Aws::Partitions::EndpointProvider.resolve(cfg.region, endpoint_prefix)
sts_regional = cfg.respond_to?(:sts_regional_endpoints) ? cfg.sts_regional_endpoints : nil
Aws::Partitions::EndpointProvider.resolve(
cfg.region, endpoint_prefix, sts_regional)
end
end

Expand Down
15 changes: 15 additions & 0 deletions gems/aws-sdk-core/lib/aws-sdk-core/shared_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,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
Expand Down
7 changes: 7 additions & 0 deletions gems/aws-sdk-core/lib/aws-sdk-sts/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
require 'aws-sdk-core/plugins/transfer_encoding.rb'
require 'aws-sdk-core/plugins/signature_v4.rb'
require 'aws-sdk-core/plugins/protocols/query.rb'
require 'aws-sdk-sts/plugins/sts_regional_endpoints.rb'

Aws::Plugins::GlobalConfiguration.add_identifier(:sts)

Expand Down Expand Up @@ -59,6 +60,7 @@ class Client < Seahorse::Client::Base
add_plugin(Aws::Plugins::TransferEncoding)
add_plugin(Aws::Plugins::SignatureV4)
add_plugin(Aws::Plugins::Protocols::Query)
add_plugin(Aws::STS::Plugins::STSRegionalEndpoints)

# @overload initialize(options)
# @param [Hash] options
Expand Down Expand Up @@ -192,6 +194,11 @@ class Client < Seahorse::Client::Base
#
# @option options [String] :session_token
#
# @option options [String] :sts_regional_endpoints ("legacy")
# 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.
#
# @option options [Boolean] :stub_responses (false)
# Causes the client to return stubbed responses. By default
# fake responses are generated and returned. You can specify
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Aws
module STS
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
end
20 changes: 20 additions & 0 deletions gems/aws-sdk-core/spec/aws/shared_config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,25 @@ module Aws

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
144 changes: 144 additions & 0 deletions gems/aws-sdk-core/spec/aws/sts/sts_regional_endpoints.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
require_relative '../../spec_helper'

module Aws
module STS
describe Client do

describe ':sts_regional_endpoints' do

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
10 changes: 10 additions & 0 deletions gems/aws-sdk-core/spec/fixtures/credentials/mock_shared_config
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,13 @@ source_profile = fooprofile
aws_access_key_id = ACCESS_KEY_ARPC
aws_secret_access_key = SECRET_KEY_ARPC
aws_session_token = TOKEN_ARPC

[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
3 changes: 3 additions & 0 deletions services.json
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,9 @@
},
"STS": {
"models": "sts/2011-06-15",
"addPlugins": [
"Aws::STS::Plugins::STSRegionalEndpoints"
],
"gemName": "aws-sdk-core"
},
"SWF": {
Expand Down