Skip to content

Support bearer tokens through environment for Bedrock #3266

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

Merged
merged 10 commits into from
Jul 21, 2025
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