Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 2 additions & 0 deletions gems/aws-sdk-bedrock/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Feature - Support `ENV['AWS_BEARER_TOKEN_BEDROCK']` for authentication with Amazon Bedrock APIs.

1.53.0 (2025-06-30)
------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Aws::Bedrock
module Plugins
# @api private
class BearerAuthorization < Seahorse::Client::Plugin
def after_initialize(client)
return unless (token = ENV['AWS_BEARER_TOKEN_BEDROCK'])

token_provider = Aws::StaticTokenProvider.new(token)
token_provider.metrics = ['BEARER_SERVICE_ENV_VARS']
client.config.token_provider ||= token_provider
end

class Handler < Seahorse::Client::Handler
def call(context)
# This also sets the preferred auth scheme even if the code token has precedence.
context[:auth_scheme] = { 'name' => 'bearer' } if ENV['AWS_BEARER_TOKEN_BEDROCK']
@handler.call(context)
end
end

# After endpoint/auth but before builders.
handle(Handler, priority: 60)
end
end
end
67 changes: 67 additions & 0 deletions gems/aws-sdk-bedrock/spec/client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

require_relative 'spec_helper'

module Aws
module Bedrock
describe Client do
def metrics_from_user_agent_header(resp)
header = resp.context.http_request.headers['User-Agent']
header.match(%r{ m/([A-Za-z0-9+-,]+)})[1].split(',')
end

it 'uses a bearer token from the environment' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(stub_responses: true, token_provider: nil)
expect(client.config.token_provider.token.token).to eq('bedrock-token')
resp = client.list_imported_models
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
end

it 'does not use a token for a different service' do
ENV['AWS_BEARER_TOKEN_FOO'] = 'foo-token'
client = Client.new(stub_responses: true, token_provider: nil)
expect(client.config.token_provider).to be_nil
resp = client.list_imported_models
expect(resp.context.http_request.headers['Authorization']).to_not eq('Bearer foo-token')
end

it 'still prefers bearer token when given an auth scheme preference' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
ENV['AWS_AUTH_SCHEME_PREFERENCE'] = 'sigv4,httpBearerAuth'
client = Client.new(stub_responses: true, token_provider: nil)
resp = client.list_imported_models
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
end

it 'uses the token value from code over the environment token' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(
stub_responses: true,
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
)
resp = client.list_imported_models
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer explicit-code-token')
end

it 'sets a user agent metric' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(stub_responses: true, token_provider: nil)
resp = client.list_imported_models
metrics = metrics_from_user_agent_header(resp)
expect(metrics).to include('3')
end

it 'does not set a user agent metric when using a token from code' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(
stub_responses: true,
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
)
resp = client.list_imported_models
metrics = metrics_from_user_agent_header(resp)
expect(metrics).to_not include('3')
end
end
end
end
2 changes: 2 additions & 0 deletions gems/aws-sdk-bedrockruntime/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Feature - Support `ENV['AWS_BEARER_TOKEN_BEDROCK']` for authentication with Amazon Bedrock APIs.

1.50.0 (2025-06-30)
------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Aws::BedrockRuntime
module Plugins
# @api private
class BearerAuthorization < Seahorse::Client::Plugin
def after_initialize(client)
return unless (token = ENV['AWS_BEARER_TOKEN_BEDROCK'])

token_provider = Aws::StaticTokenProvider.new(token)
token_provider.metrics = ['BEARER_SERVICE_ENV_VARS']
client.config.token_provider ||= token_provider
end

class Handler < Seahorse::Client::Handler
def call(context)
# This also sets the preferred auth scheme even if the code token has precedence.
context[:auth_scheme] = { 'name' => 'bearer' } if ENV['AWS_BEARER_TOKEN_BEDROCK']
@handler.call(context)
end
end

# After endpoint/auth but before builders.
handle(Handler, priority: 60)
end
end
end
73 changes: 73 additions & 0 deletions gems/aws-sdk-bedrockruntime/spec/client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require_relative 'spec_helper'

module Aws
module BedrockRuntime
describe Client do
def metrics_from_user_agent_header(resp)
header = resp.context.http_request.headers['User-Agent']
header.match(%r{ m/([A-Za-z0-9+-,]+)})[1].split(',')
end

def invoke_model(client)
stub = client.stub_data(:invoke_model, body: 'test')
client.stub_responses(:invoke_model, stub)
client.invoke_model(model_id: 'test')
end

it 'uses a bearer token from the environment' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(stub_responses: true, token_provider: nil)
expect(client.config.token_provider.token.token).to eq('bedrock-token')
resp = invoke_model(client)
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
end

it 'does not use a token for a different service' do
ENV['AWS_BEARER_TOKEN_FOO'] = 'foo-token'
client = Client.new(stub_responses: true, token_provider: nil)
expect(client.config.token_provider).to be_nil
resp = invoke_model(client)
expect(resp.context.http_request.headers['Authorization']).to_not eq('Bearer foo-token')
end

it 'still prefers bearer token when given an auth scheme preference' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
ENV['AWS_AUTH_SCHEME_PREFERENCE'] = 'sigv4,httpBearerAuth'
client = Client.new(stub_responses: true, token_provider: nil)
resp = invoke_model(client)
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
end

it 'uses explicit config over the environment token' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(
stub_responses: true,
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
)
resp = invoke_model(client)
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer explicit-code-token')
end

it 'sets a user agent metric' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(stub_responses: true, token_provider: nil)
resp = invoke_model(client)
metrics = metrics_from_user_agent_header(resp)
expect(metrics).to include('3')
end

it 'does not set a user agent metric when using a token from code' do
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
client = Client.new(
stub_responses: true,
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
)
resp = invoke_model(client)
metrics = metrics_from_user_agent_header(resp)
expect(metrics).to_not include('3')
end
end
end
end
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 metric tracking for Bedrock Bearer tokens.

3.226.2 (2025-07-01)
------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,8 @@ class CredentialsConfiguration < Seahorse::Client::Plugin
will be used to search for tokens configured for your profile in shared configuration files.
DOCS
) do |config|
if config.stub_responses
StaticTokenProvider.new('token')
else
TokenProviderChain.new(config).resolve
end
TokenProviderChain.new(config).resolve
end

end
end
end
51 changes: 24 additions & 27 deletions gems/aws-sdk-core/lib/aws-sdk-core/plugins/sign.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ class Sign < Seahorse::Client::Plugin
option(:sigv4_region)
option(:unsigned_operations, default: [])

supported_auth_types = %w[sigv4 bearer sigv4-s3express sigv4a none]
SUPPORTED_AUTH_TYPES = supported_auth_types.freeze
SUPPORTED_AUTH_TYPES = %w[sigv4 bearer sigv4-s3express sigv4a none].freeze

def add_handlers(handlers, cfg)
operations = cfg.api.operation_names - cfg.unsigned_operations
Expand All @@ -32,7 +31,7 @@ def self.signer_for(auth_scheme, config, sigv4_region_override = nil, sigv4_cred
}
SignatureV4.new(auth_scheme, config, sigv4_overrides)
when 'bearer'
Bearer.new
Bearer.new(config)
else
NullSigner.new
end
Expand All @@ -41,26 +40,29 @@ def self.signer_for(auth_scheme, config, sigv4_region_override = nil, sigv4_cred
class Handler < Seahorse::Client::Handler
def call(context)
# Skip signing if using sigv2 signing from s3_signer in S3
credentials = nil
unless v2_signing?(context.config)
signer = Sign.signer_for(
context[:auth_scheme],
context.config,
context[:sigv4_region],
context[:sigv4_credentials]
)
credentials = signer.credentials if signer.is_a?(SignatureV4)
signer.sign(context)
end
with_metrics(credentials) { @handler.call(context) }
with_metrics(signer) { @handler.call(context) }
end

private

def with_metrics(credentials, &block)
return block.call unless credentials&.respond_to?(:metrics)

Aws::Plugins::UserAgent.metric(*credentials.metrics, &block)
def with_metrics(signer, &block)
case signer
when SignatureV4
Aws::Plugins::UserAgent.metric(*signer.credentials.metrics, &block)
when Bearer
Aws::Plugins::UserAgent.metric(*signer.token_provider.metrics, &block)
else
block.call
end
end

def v2_signing?(config)
Expand All @@ -72,21 +74,19 @@ def v2_signing?(config)

# @api private
class Bearer
def initialize
def initialize(config)
@token_provider = config.token_provider
end

attr_reader :token_provider

def sign(context)
if context.http_request.endpoint.scheme != 'https'
raise ArgumentError,
'Unable to use bearer authorization on non https endpoint.'
raise ArgumentError, 'Unable to use bearer authorization on non https endpoint.'
end
raise Errors::MissingBearerTokenError unless @token_provider && @token_provider.set?

token_provider = context.config.token_provider

raise Errors::MissingBearerTokenError unless token_provider&.set?

context.http_request.headers['Authorization'] =
"Bearer #{token_provider.token.token}"
context.http_request.headers['Authorization'] = "Bearer #{@token_provider.token.token}"
end

def presign_url(*args)
Expand All @@ -100,16 +100,11 @@ def sign_event(*args)

# @api private
class SignatureV4
attr_reader :signer

def initialize(auth_scheme, config, sigv4_overrides = {})
scheme_name = auth_scheme['name']

unless %w[sigv4 sigv4a sigv4-s3express].include?(scheme_name)
raise ArgumentError,
"Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
raise ArgumentError, "Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
end

region = if scheme_name == 'sigv4a'
auth_scheme['signingRegionSet'].join(',')
else
Expand All @@ -121,15 +116,17 @@ def initialize(auth_scheme, config, sigv4_overrides = {})
region: sigv4_overrides[:region] || config.sigv4_region || region,
credentials_provider: sigv4_overrides[:credentials] || config.credentials,
signing_algorithm: scheme_name.to_sym,
uri_escape_path: !!!auth_scheme['disableDoubleEncoding'],
normalize_path: !!!auth_scheme['disableNormalizePath'],
uri_escape_path: !auth_scheme['disableDoubleEncoding'],
normalize_path: !auth_scheme['disableNormalizePath'],
unsigned_headers: %w[content-length user-agent x-amzn-trace-id expect transfer-encoding connection]
)
rescue Aws::Sigv4::Errors::MissingCredentialsError
raise Aws::Errors::MissingCredentialsError
end
end

attr_reader :signer

def sign(context)
req = context.http_request

Expand Down
6 changes: 6 additions & 0 deletions gems/aws-sdk-core/lib/aws-sdk-core/plugins/stub_responses.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class StubResponses < Seahorse::Client::Plugin
end
end

option(:token_provider) do |config|
if config.stub_responses
StaticTokenProvider.new('stubbed-token')
end
end

option(:stubs) { {} }
option(:stubs_mutex) { Mutex.new }
option(:api_requests) { [] }
Expand Down
3 changes: 2 additions & 1 deletion gems/aws-sdk-core/lib/aws-sdk-core/plugins/user_agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ class UserAgent < Seahorse::Client::Plugin
"CREDENTIALS_HTTP" : "z",
"CREDENTIALS_IMDS" : "0",
"SSO_LOGIN_DEVICE" : "1",
"SSO_LOGIN_AUTH" : "2"
"SSO_LOGIN_AUTH" : "2",
"BEARER_SERVICE_ENV_VARS": "3"
}
METRICS

Expand Down
3 changes: 1 addition & 2 deletions gems/aws-sdk-core/lib/aws-sdk-core/static_token_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

module Aws
class StaticTokenProvider

include TokenProvider

# @param [String] token
# @param [Time] expiration
def initialize(token, expiration=nil)
def initialize(token, expiration = nil)
@token = Token.new(token, expiration)
end
end
Expand Down
Loading