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

Credential scope support #2932

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ def built_in_client_context_param_value(param_data)
else
'context.config.use_dualstack_endpoint'
end
when 'AWS::Auth::CredentialScope'
mullermp marked this conversation as resolved.
Show resolved Hide resolved
'context.config.credentials.credentials.credential_scope'
when 'AWS::STS::UseGlobalEndpoint'
"context.config.sts_regional_endpoints == 'legacy'"
when 'AWS::S3::UseGlobalEndpoint'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ def initialize(options)
operation_name: Underscore.underscore(
operation_inputs_test['operationName']
),
operation_params: operation_inputs_test['operationParams'] || [],
built_in_params: operation_inputs_test['builtInParams'] || [],
client_params: operation_inputs_test['clientParams'] || []
operation_params: operation_inputs_test['operationParams'] || {},
built_in_params: operation_inputs_test['builtInParams'] || {},
client_params: operation_inputs_test['clientParams'] || {}
)
end
end
Expand Down Expand Up @@ -113,12 +113,13 @@ def initialize(options)
@client_params = options[:client_params].map do |k,v|
Param.new(Underscore.underscore(k), v)
end

@client_params += options[:built_in_params].map do |k,v|
built_in_to_param(k, v)
end
# the expected default of UseGlobalEndpoint does not match the SDK's default value
if @service.identifier == 's3' && !options[:built_in_params].include?('AWS::S3::UseGlobalEndpoint')
# the expected default of UseGlobalEndpoint in rules
# does not match the Ruby SDK's default value
if @service.identifier == 's3' &&
!options[:built_in_params].include?('AWS::S3::UseGlobalEndpoint')
@client_params << built_in_to_param('AWS::S3::UseGlobalEndpoint', false)
end
end
Expand Down Expand Up @@ -154,6 +155,12 @@ def built_in_to_param(built_in, value)
Param.new('use_fips_endpoint', value)
when 'AWS::UseDualStack'
Param.new('use_dualstack_endpoint', value)
when 'AWS::Auth::CredentialScope'
Param.new(
'credentials',
"Aws::Credentials.new('stubbed-akid', 'stubbed-secret', credential_scope: '#{value}')",
true
)
when 'AWS::STS::UseGlobalEndpoint'
Param.new('sts_regional_endpoints', value ? 'legacy' : 'regional')
when 'AWS::S3::UseGlobalEndpoint'
Expand All @@ -162,9 +169,7 @@ def built_in_to_param(built_in, value)
Param.new('use_accelerate_endpoint', value)
when 'AWS::S3::ForcePathStyle'
Param.new('force_path_style', value)
when 'AWS::S3::UseArnRegion'
Param.new('s3_use_arn_region', value)
when 'AWS::S3Control::UseArnRegion'
when 'AWS::S3::UseArnRegion', 'AWS::S3Control::UseArnRegion'
Param.new('s3_use_arn_region', value)
when 'AWS::S3::DisableMultiRegionAccessPoints'
Param.new('s3_disable_multiregion_access_points', value)
Expand All @@ -177,14 +182,16 @@ def built_in_to_param(built_in, value)
end

class Param
def initialize(param, value)
def initialize(param, value, literal = false)
@param = param
@value = value
@literal = literal
end

attr_accessor :param

def value
if @value.is_a? String
if @value.is_a?(String) && !@literal
"'#{@value}'"
else
@value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module {{module_name}}
subject { {{module_name}}::EndpointProvider.new }

{{#endpoint_tests}}
context '{{documentation}}' do
context "{{{documentation}}}" do
let(:expected) do
{{{expect}}}
end
Expand Down
4 changes: 2 additions & 2 deletions build_tools/services.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ class ServiceEnumerator
MANIFEST_PATH = File.expand_path('../../services.json', __FILE__)

# Minimum `aws-sdk-core` version for new gem builds
MINIMUM_CORE_VERSION = "3.184.0"
MINIMUM_CORE_VERSION = "3.186.0"

# Minimum `aws-sdk-core` version for new S3 gem builds
MINIMUM_CORE_VERSION_S3 = "3.181.0"
MINIMUM_CORE_VERSION_S3 = "3.186.0"

EVENTSTREAM_PLUGIN = "Aws::Plugins::EventStreamConfiguration"

Expand Down
2 changes: 2 additions & 0 deletions gems/aws-sdk-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Feature - Support Credential scoping using `ENV['AWS_CREDENTIAL_SCOPE']`, `aws_credential_scope` shared config, or the `credential_scope` Client configuration option.

3.185.1 (2023-10-05)
------------------

Expand Down
2 changes: 1 addition & 1 deletion gems/aws-sdk-core/aws-sdk-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
spec.files = Dir['LICENSE.txt', 'CHANGELOG.md', 'VERSION', 'lib/**/*.rb', 'ca-bundle.crt']

spec.add_dependency('jmespath', '~> 1', '>= 1.6.1') # necessary for secure jmespath JSON parsing
spec.add_dependency('aws-partitions', '~> 1', '>= 1.651.0') # necessary for new endpoint resolution
spec.add_dependency('aws-partitions', '~> 1', '>= 1.823.0') # necessary for new endpoint metadata
spec.add_dependency('aws-sigv4', '~> 1.5') # necessary for making Aws::STS, SSO, SSOOIDC API calls
spec.add_dependency('aws-eventstream', '~> 1', '>= 1.0.2') # necessary for binary eventstream

Expand Down
11 changes: 9 additions & 2 deletions gems/aws-sdk-core/lib/aws-sdk-core/credential_provider_chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def static_credentials(options)
Credentials.new(
options[:config].access_key_id,
options[:config].secret_access_key,
options[:config].session_token
options[:config].session_token,
credential_scope: options[:config].credential_scope
)
end
end
Expand Down Expand Up @@ -94,7 +95,13 @@ def env_credentials(_options)
key = %w[AWS_ACCESS_KEY_ID AMAZON_ACCESS_KEY_ID AWS_ACCESS_KEY]
secret = %w[AWS_SECRET_ACCESS_KEY AMAZON_SECRET_ACCESS_KEY AWS_SECRET_KEY]
token = %w[AWS_SESSION_TOKEN AMAZON_SESSION_TOKEN]
Credentials.new(envar(key), envar(secret), envar(token))
scope = %w[AWS_CREDENTIAL_SCOPE]
Credentials.new(
envar(key),
envar(secret),
envar(token),
credential_scope: envar(scope)
)
end

def envar(keys)
Expand Down
19 changes: 13 additions & 6 deletions gems/aws-sdk-core/lib/aws-sdk-core/credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,28 @@ class Credentials
# @param [String] access_key_id
# @param [String] secret_access_key
# @param [String] session_token (nil)
def initialize(access_key_id, secret_access_key, session_token = nil)
# @param [Hash] kwargs
# @option kwargs [String] :credential_scope (nil)
def initialize(access_key_id, secret_access_key, session_token = nil,
**kwargs)
@access_key_id = access_key_id
@secret_access_key = secret_access_key
@session_token = session_token
@credential_scope = kwargs[:credential_scope]
end

# @return [String, nil]
# @return [String]
attr_reader :access_key_id

# @return [String, nil]
# @return [String]
attr_reader :secret_access_key

# @return [String, nil]
attr_reader :session_token

# @return [String, nil]
attr_reader :credential_scope

# @return [Credentials]
def credentials
self
Expand All @@ -30,9 +37,9 @@ def credentials
# access key are both set.
def set?
!access_key_id.nil? &&
!access_key_id.empty? &&
!secret_access_key.nil? &&
!secret_access_key.empty?
!access_key_id.empty? &&
!secret_access_key.nil? &&
!secret_access_key.empty?
end

# Removing the secret access key from the default inspect string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class CredentialsConfiguration < Seahorse::Client::Plugin

option(:session_token, doc_type: String, docstring: '')

option(:credential_scope, doc_type: String, docstring: '')

option(:profile,
doc_default: 'default',
doc_type: String,
Expand Down Expand Up @@ -57,13 +59,15 @@ class CredentialsConfiguration < Seahorse::Client::Plugin
locations will be searched for credentials:

* `Aws.config[:credentials]`
* The `:access_key_id`, `:secret_access_key`, and `:session_token` options.
* ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']
* The `:access_key_id`, `:secret_access_key`, `:session_token`, and
`credential_scope` options.
mullermp marked this conversation as resolved.
Show resolved Hide resolved
* ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'],
ENV['AWS_SESSION_TOKEN'], and ENV['AWS_CREDENTIAL_SCOPE']
* `~/.aws/credentials`
* `~/.aws/config`
* EC2/ECS IMDS instance profile - When used by default, the timeouts
are very aggressive. Construct and pass an instance of
`Aws::InstanceProfileCredentails` or `Aws::ECSCredentials` to
`Aws::InstanceProfileCredentials` or `Aws::ECSCredentials` to
enable retries and extended timeouts. Instance profile credential
fetching can be disabled by setting ENV['AWS_EC2_METADATA_DISABLED']
to true.
Expand Down
3 changes: 2 additions & 1 deletion gems/aws-sdk-core/lib/aws-sdk-core/shared_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,8 @@ def credentials_from_profile(prof_config)
creds = Credentials.new(
prof_config['aws_access_key_id'],
prof_config['aws_secret_access_key'],
prof_config['aws_session_token']
prof_config['aws_session_token'],
credential_scope: prof_config['aws_credential_scope']
)
creds if creds.set?
end
Expand Down
7 changes: 0 additions & 7 deletions gems/aws-sdk-core/lib/aws-sdk-core/shared_credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ class SharedCredentials

include CredentialProvider

# @api private
KEY_MAP = {
'aws_access_key_id' => 'access_key_id',
'aws_secret_access_key' => 'secret_access_key',
'aws_session_token' => 'session_token',
}

# Constructs a new SharedCredentials object. This will load static
# (access_key_id, secret_access_key and session_token) AWS access
# credentials from an ini file, which supports profiles. The default
Expand Down
49 changes: 28 additions & 21 deletions gems/aws-sdk-core/spec/aws/credential_provider_chain_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
module Aws
describe CredentialProviderChain do
def random_creds
{ access_key_id: SecureRandom.hex,
secret_access_key: SecureRandom.hex, session_token: SecureRandom.hex }
{
access_key_id: SecureRandom.hex,
secret_access_key: SecureRandom.hex,
session_token: SecureRandom.hex,
credential_scope: SecureRandom.hex
}
end

def with_shared_credentials(profile_name = SecureRandom.hex, credentials_file = nil)
Expand All @@ -19,6 +23,7 @@ def with_shared_credentials(profile_name = SecureRandom.hex, credentials_file =
aws_access_key_id = #{creds[:access_key_id]}
aws_secret_access_key = #{creds[:secret_access_key]}
aws_session_token = #{creds[:session_token]}
aws_credential_scope = #{creds[:credential_scope]}
CREDS
allow(Dir).to receive(:home).and_return('HOME')
allow(File).to receive(:exist?).with(path).and_return(true)
Expand All @@ -29,9 +34,10 @@ def with_shared_credentials(profile_name = SecureRandom.hex, credentials_file =

def with_env_credentials
creds = random_creds
env['AWS_ACCESS_KEY_ID'] = creds[:access_key_id]
env['AWS_SECRET_ACCESS_KEY'] = creds[:secret_access_key]
env['AWS_SESSION_TOKEN'] = creds[:session_token]
ENV['AWS_ACCESS_KEY_ID'] = creds[:access_key_id]
ENV['AWS_SECRET_ACCESS_KEY'] = creds[:secret_access_key]
ENV['AWS_SESSION_TOKEN'] = creds[:session_token]
ENV['AWS_CREDENTIAL_SCOPE'] = creds[:credential_scope]
creds
end

Expand All @@ -40,6 +46,7 @@ def with_config_credentials
allow(config).to receive(:access_key_id).and_return(creds[:access_key_id])
allow(config).to receive(:secret_access_key).and_return(creds[:secret_access_key])
allow(config).to receive(:session_token).and_return(creds[:session_token])
allow(config).to receive(:credential_scope).and_return(creds[:credential_scope])
creds
end

Expand All @@ -49,15 +56,15 @@ def validate_credentials(expected_creds)
expect(creds.access_key_id).to eq(expected_creds[:access_key_id])
expect(creds.secret_access_key).to eq(expected_creds[:secret_access_key])
expect(creds.session_token).to eq(expected_creds[:session_token])
expect(creds.credential_scope).to eq(expected_creds[:credential_scope])
end

let(:env) { {} }

let(:config) do
double('config',
access_key_id: nil,
secret_access_key: nil,
session_token: nil,
credential_scope: nil,
profile: nil,
region: nil,
instance_profile_credentials_timeout: 1,
Expand All @@ -71,7 +78,6 @@ def validate_credentials(expected_creds)
let(:credentials) { chain.resolve }

before(:each) do
stub_const('ENV', env)
allow(InstanceProfileCredentials).to receive(:new).and_return(mock_instance_creds)
end

Expand All @@ -82,31 +88,32 @@ def validate_credentials(expected_creds)

it 'hydrates credentials from ENV with prefix AWS_' do
expected_creds = random_creds
env['AWS_ACCESS_KEY_ID'] = expected_creds[:access_key_id]
env['AWS_SECRET_ACCESS_KEY'] = expected_creds[:secret_access_key]
env['AWS_SESSION_TOKEN'] = expected_creds[:session_token]
ENV['AWS_ACCESS_KEY_ID'] = expected_creds[:access_key_id]
ENV['AWS_SECRET_ACCESS_KEY'] = expected_creds[:secret_access_key]
ENV['AWS_SESSION_TOKEN'] = expected_creds[:session_token]
ENV['AWS_CREDENTIAL_SCOPE'] = expected_creds[:credential_scope]
validate_credentials(expected_creds)
end

it 'hydrates credentials from ENV with prefix AMAZON_' do
expected_creds = random_creds
env['AMAZON_ACCESS_KEY_ID'] = expected_creds[:access_key_id]
env['AMAZON_SECRET_ACCESS_KEY'] = expected_creds[:secret_access_key]
env['AMAZON_SESSION_TOKEN'] = expected_creds[:session_token]
expected_creds = random_creds.merge(credential_scope: nil)
ENV['AMAZON_ACCESS_KEY_ID'] = expected_creds[:access_key_id]
ENV['AMAZON_SECRET_ACCESS_KEY'] = expected_creds[:secret_access_key]
ENV['AMAZON_SESSION_TOKEN'] = expected_creds[:session_token]
validate_credentials(expected_creds)
end

it 'hydrates credentials from ENV at AWS_ACCESS_KEY & AWS_SECRET_KEY' do
expected_creds = random_creds.merge(session_token: nil)
env['AWS_ACCESS_KEY'] = expected_creds[:access_key_id]
env['AWS_SECRET_KEY'] = expected_creds[:secret_access_key]
expected_creds = random_creds.merge(session_token: nil, credential_scope: nil)
ENV['AWS_ACCESS_KEY'] = expected_creds[:access_key_id]
ENV['AWS_SECRET_KEY'] = expected_creds[:secret_access_key]
validate_credentials(expected_creds)
end

it 'hydrates credentials from ENV at AWS_ACCESS_KEY_ID & AWS_SECRET_KEY' do
expected_creds = random_creds.merge(session_token: nil)
env['AWS_ACCESS_KEY_ID'] = expected_creds[:access_key_id]
env['AWS_SECRET_KEY'] = expected_creds[:secret_access_key]
expected_creds = random_creds.merge(session_token: nil, credential_scope: nil)
ENV['AWS_ACCESS_KEY_ID'] = expected_creds[:access_key_id]
ENV['AWS_SECRET_KEY'] = expected_creds[:secret_access_key]
validate_credentials(expected_creds)
end

Expand Down
Loading
Loading