Skip to content

Commit da47b00

Browse files
authored
Support bearer tokens through environment for Bedrock (#3266)
1 parent 05a58a5 commit da47b00

File tree

24 files changed

+289
-99
lines changed

24 files changed

+289
-99
lines changed

build_tools/services.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ class ServiceEnumerator
99
MANIFEST_PATH = File.expand_path('../../services.json', __FILE__)
1010

1111
# Minimum `aws-sdk-core` version for new gem builds
12-
MINIMUM_CORE_VERSION = "3.225.0"
12+
MINIMUM_CORE_VERSION = "3.227.0"
1313

1414
# Minimum `aws-sdk-core` version for new S3 gem builds
15-
MINIMUM_CORE_VERSION_S3 = "3.225.0"
15+
MINIMUM_CORE_VERSION_S3 = "3.227.0"
1616

1717
EVENTSTREAM_PLUGIN = "Aws::Plugins::EventStreamConfiguration"
1818

gems/aws-sdk-bedrock/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Feature - Support `ENV['AWS_BEARER_TOKEN_BEDROCK']` for authentication with Amazon Bedrock APIs.
5+
46
1.54.0 (2025-07-16)
57
------------------
68

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
module Aws::Bedrock
4+
module Plugins
5+
# @api private
6+
class BearerAuthorization < Seahorse::Client::Plugin
7+
def after_initialize(client)
8+
return unless (token = ENV['AWS_BEARER_TOKEN_BEDROCK'])
9+
10+
token_provider = Aws::StaticTokenProvider.new(token)
11+
token_provider.metrics = ['BEARER_SERVICE_ENV_VARS']
12+
client.config.token_provider ||= token_provider
13+
end
14+
15+
class Handler < Seahorse::Client::Handler
16+
def call(context)
17+
# This also sets the preferred auth scheme even if the code token has precedence.
18+
context[:auth_scheme] = { 'name' => 'bearer' } if ENV['AWS_BEARER_TOKEN_BEDROCK']
19+
@handler.call(context)
20+
end
21+
end
22+
23+
# After endpoint/auth but before builders.
24+
handle(Handler, priority: 60)
25+
end
26+
end
27+
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'spec_helper'
4+
5+
module Aws
6+
module Bedrock
7+
describe Client do
8+
def metrics_from_user_agent_header(resp)
9+
header = resp.context.http_request.headers['User-Agent']
10+
header.match(%r{ m/([A-Za-z0-9+-,]+)})[1].split(',')
11+
end
12+
13+
it 'uses a bearer token from the environment' do
14+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
15+
client = Client.new(stub_responses: true, token_provider: nil)
16+
expect(client.config.token_provider.token.token).to eq('bedrock-token')
17+
resp = client.list_imported_models
18+
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
19+
end
20+
21+
it 'does not use a token for a different service' do
22+
ENV['AWS_BEARER_TOKEN_FOO'] = 'foo-token'
23+
client = Client.new(stub_responses: true, token_provider: nil)
24+
expect(client.config.token_provider).to be_nil
25+
resp = client.list_imported_models
26+
expect(resp.context.http_request.headers['Authorization']).to_not eq('Bearer foo-token')
27+
end
28+
29+
it 'still prefers bearer token when given an auth scheme preference' do
30+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
31+
ENV['AWS_AUTH_SCHEME_PREFERENCE'] = 'sigv4,httpBearerAuth'
32+
client = Client.new(stub_responses: true, token_provider: nil)
33+
resp = client.list_imported_models
34+
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
35+
end
36+
37+
it 'uses the token value from code over the environment token' do
38+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
39+
client = Client.new(
40+
stub_responses: true,
41+
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
42+
)
43+
resp = client.list_imported_models
44+
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer explicit-code-token')
45+
end
46+
47+
it 'sets a user agent metric' do
48+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
49+
client = Client.new(stub_responses: true, token_provider: nil)
50+
resp = client.list_imported_models
51+
metrics = metrics_from_user_agent_header(resp)
52+
expect(metrics).to include('3')
53+
end
54+
55+
it 'does not set a user agent metric when using a token from code' do
56+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
57+
client = Client.new(
58+
stub_responses: true,
59+
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
60+
)
61+
resp = client.list_imported_models
62+
metrics = metrics_from_user_agent_header(resp)
63+
expect(metrics).to_not include('3')
64+
end
65+
end
66+
end
67+
end

gems/aws-sdk-bedrockruntime/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Feature - Support `ENV['AWS_BEARER_TOKEN_BEDROCK']` for authentication with Amazon Bedrock APIs.
5+
46
1.51.0 (2025-07-16)
57
------------------
68

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
module Aws::BedrockRuntime
4+
module Plugins
5+
# @api private
6+
class BearerAuthorization < Seahorse::Client::Plugin
7+
def after_initialize(client)
8+
return unless (token = ENV['AWS_BEARER_TOKEN_BEDROCK'])
9+
10+
token_provider = Aws::StaticTokenProvider.new(token)
11+
token_provider.metrics = ['BEARER_SERVICE_ENV_VARS']
12+
client.config.token_provider ||= token_provider
13+
end
14+
15+
class Handler < Seahorse::Client::Handler
16+
def call(context)
17+
# This also sets the preferred auth scheme even if the code token has precedence.
18+
context[:auth_scheme] = { 'name' => 'bearer' } if ENV['AWS_BEARER_TOKEN_BEDROCK']
19+
@handler.call(context)
20+
end
21+
end
22+
23+
# After endpoint/auth but before builders.
24+
handle(Handler, priority: 60)
25+
end
26+
end
27+
end
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'spec_helper'
4+
5+
module Aws
6+
module BedrockRuntime
7+
describe Client do
8+
def metrics_from_user_agent_header(resp)
9+
header = resp.context.http_request.headers['User-Agent']
10+
header.match(%r{ m/([A-Za-z0-9+-,]+)})[1].split(',')
11+
end
12+
13+
def invoke_model(client)
14+
stub = client.stub_data(:invoke_model, body: 'test')
15+
client.stub_responses(:invoke_model, stub)
16+
client.invoke_model(model_id: 'test')
17+
end
18+
19+
it 'uses a bearer token from the environment' do
20+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
21+
client = Client.new(stub_responses: true, token_provider: nil)
22+
expect(client.config.token_provider.token.token).to eq('bedrock-token')
23+
resp = invoke_model(client)
24+
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
25+
end
26+
27+
it 'does not use a token for a different service' do
28+
ENV['AWS_BEARER_TOKEN_FOO'] = 'foo-token'
29+
client = Client.new(stub_responses: true, token_provider: nil)
30+
expect(client.config.token_provider).to be_nil
31+
resp = invoke_model(client)
32+
expect(resp.context.http_request.headers['Authorization']).to_not eq('Bearer foo-token')
33+
end
34+
35+
it 'still prefers bearer token when given an auth scheme preference' do
36+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
37+
ENV['AWS_AUTH_SCHEME_PREFERENCE'] = 'sigv4,httpBearerAuth'
38+
client = Client.new(stub_responses: true, token_provider: nil)
39+
resp = invoke_model(client)
40+
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer bedrock-token')
41+
end
42+
43+
it 'uses explicit config over the environment token' do
44+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
45+
client = Client.new(
46+
stub_responses: true,
47+
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
48+
)
49+
resp = invoke_model(client)
50+
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer explicit-code-token')
51+
end
52+
53+
it 'sets a user agent metric' do
54+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
55+
client = Client.new(stub_responses: true, token_provider: nil)
56+
resp = invoke_model(client)
57+
metrics = metrics_from_user_agent_header(resp)
58+
expect(metrics).to include('3')
59+
end
60+
61+
it 'does not set a user agent metric when using a token from code' do
62+
ENV['AWS_BEARER_TOKEN_BEDROCK'] = 'bedrock-token'
63+
client = Client.new(
64+
stub_responses: true,
65+
token_provider: Aws::StaticTokenProvider.new('explicit-code-token')
66+
)
67+
resp = invoke_model(client)
68+
metrics = metrics_from_user_agent_header(resp)
69+
expect(metrics).to_not include('3')
70+
end
71+
end
72+
end
73+
end

gems/aws-sdk-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Feature - Support metric tracking for Bedrock Bearer tokens.
5+
46
3.226.3 (2025-07-17)
57
------------------
68

gems/aws-sdk-core/lib/aws-sdk-core/plugins/credentials_configuration.rb

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,8 @@ class CredentialsConfiguration < Seahorse::Client::Plugin
9999
will be used to search for tokens configured for your profile in shared configuration files.
100100
DOCS
101101
) do |config|
102-
if config.stub_responses
103-
StaticTokenProvider.new('token')
104-
else
105-
TokenProviderChain.new(config).resolve
106-
end
102+
TokenProviderChain.new(config).resolve
107103
end
108-
109104
end
110105
end
111106
end

gems/aws-sdk-core/lib/aws-sdk-core/plugins/sign.rb

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ class Sign < Seahorse::Client::Plugin
1313
option(:sigv4_region)
1414
option(:unsigned_operations, default: [])
1515

16-
supported_auth_types = %w[sigv4 bearer sigv4-s3express sigv4a none]
17-
SUPPORTED_AUTH_TYPES = supported_auth_types.freeze
16+
SUPPORTED_AUTH_TYPES = %w[sigv4 bearer sigv4-s3express sigv4a none].freeze
1817

1918
def add_handlers(handlers, cfg)
2019
operations = cfg.api.operation_names - cfg.unsigned_operations
@@ -32,7 +31,7 @@ def self.signer_for(auth_scheme, config, sigv4_region_override = nil, sigv4_cred
3231
}
3332
SignatureV4.new(auth_scheme, config, sigv4_overrides)
3433
when 'bearer'
35-
Bearer.new
34+
Bearer.new(config)
3635
else
3736
NullSigner.new
3837
end
@@ -41,26 +40,29 @@ def self.signer_for(auth_scheme, config, sigv4_region_override = nil, sigv4_cred
4140
class Handler < Seahorse::Client::Handler
4241
def call(context)
4342
# Skip signing if using sigv2 signing from s3_signer in S3
44-
credentials = nil
4543
unless v2_signing?(context.config)
4644
signer = Sign.signer_for(
4745
context[:auth_scheme],
4846
context.config,
4947
context[:sigv4_region],
5048
context[:sigv4_credentials]
5149
)
52-
credentials = signer.credentials if signer.is_a?(SignatureV4)
5350
signer.sign(context)
5451
end
55-
with_metrics(credentials) { @handler.call(context) }
52+
with_metrics(signer) { @handler.call(context) }
5653
end
5754

5855
private
5956

60-
def with_metrics(credentials, &block)
61-
return block.call unless credentials&.respond_to?(:metrics)
62-
63-
Aws::Plugins::UserAgent.metric(*credentials.metrics, &block)
57+
def with_metrics(signer, &block)
58+
case signer
59+
when SignatureV4
60+
Aws::Plugins::UserAgent.metric(*signer.credentials.metrics, &block)
61+
when Bearer
62+
Aws::Plugins::UserAgent.metric(*signer.token_provider.metrics, &block)
63+
else
64+
block.call
65+
end
6466
end
6567

6668
def v2_signing?(config)
@@ -72,21 +74,19 @@ def v2_signing?(config)
7274

7375
# @api private
7476
class Bearer
75-
def initialize
77+
def initialize(config)
78+
@token_provider = config.token_provider
7679
end
7780

81+
attr_reader :token_provider
82+
7883
def sign(context)
7984
if context.http_request.endpoint.scheme != 'https'
80-
raise ArgumentError,
81-
'Unable to use bearer authorization on non https endpoint.'
85+
raise ArgumentError, 'Unable to use bearer authorization on non https endpoint.'
8286
end
87+
raise Errors::MissingBearerTokenError unless @token_provider && @token_provider.set?
8388

84-
token_provider = context.config.token_provider
85-
86-
raise Errors::MissingBearerTokenError unless token_provider&.set?
87-
88-
context.http_request.headers['Authorization'] =
89-
"Bearer #{token_provider.token.token}"
89+
context.http_request.headers['Authorization'] = "Bearer #{@token_provider.token.token}"
9090
end
9191

9292
def presign_url(*args)
@@ -100,16 +100,11 @@ def sign_event(*args)
100100

101101
# @api private
102102
class SignatureV4
103-
attr_reader :signer
104-
105103
def initialize(auth_scheme, config, sigv4_overrides = {})
106104
scheme_name = auth_scheme['name']
107-
108105
unless %w[sigv4 sigv4a sigv4-s3express].include?(scheme_name)
109-
raise ArgumentError,
110-
"Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
106+
raise ArgumentError, "Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
111107
end
112-
113108
region = if scheme_name == 'sigv4a'
114109
auth_scheme['signingRegionSet'].join(',')
115110
else
@@ -121,15 +116,17 @@ def initialize(auth_scheme, config, sigv4_overrides = {})
121116
region: sigv4_overrides[:region] || config.sigv4_region || region,
122117
credentials_provider: sigv4_overrides[:credentials] || config.credentials,
123118
signing_algorithm: scheme_name.to_sym,
124-
uri_escape_path: !!!auth_scheme['disableDoubleEncoding'],
125-
normalize_path: !!!auth_scheme['disableNormalizePath'],
119+
uri_escape_path: !auth_scheme['disableDoubleEncoding'],
120+
normalize_path: !auth_scheme['disableNormalizePath'],
126121
unsigned_headers: %w[content-length user-agent x-amzn-trace-id expect transfer-encoding connection]
127122
)
128123
rescue Aws::Sigv4::Errors::MissingCredentialsError
129124
raise Aws::Errors::MissingCredentialsError
130125
end
131126
end
132127

128+
attr_reader :signer
129+
133130
def sign(context)
134131
req = context.http_request
135132

0 commit comments

Comments
 (0)