Skip to content

Commit d50d3ce

Browse files
committed
Adds support for loading AWS bearer tokens via session constructor params
1 parent 39a4122 commit d50d3ce

3 files changed

Lines changed: 159 additions & 0 deletions

File tree

.changes/next-version.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"category": "Session",
4+
"description": "Add support for ``aws_bearer_token`` parameter in Session, Client, and Resource to enable per-session/client bearer token authentication for services that support it (e.g. Bedrock). This allows safe multi-tenant usage without relying on global environment variables like ``AWS_BEARER_TOKEN_BEDROCK``.",
5+
"type": "feature"
6+
}
7+
]

boto3/session.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class Session:
5050
the default profile is used.
5151
:type aws_account_id: string
5252
:param aws_account_id: AWS account ID
53+
:type aws_bearer_token: string
54+
:param aws_bearer_token: Bearer token for authentication with services
55+
that support bearer token auth (e.g. Bedrock).
5356
"""
5457

5558
def __init__(
@@ -61,6 +64,7 @@ def __init__(
6164
botocore_session=None,
6265
profile_name=None,
6366
aws_account_id=None,
67+
aws_bearer_token=None,
6468
):
6569
if botocore_session is not None:
6670
self._session = botocore_session
@@ -100,6 +104,9 @@ def __init__(
100104
if region_name is not None:
101105
self._session.set_config_variable('region', region_name)
102106

107+
# Store the bearer token for per-session bearer token authentication
108+
self._aws_bearer_token = aws_bearer_token
109+
103110
self.resource_factory = ResourceFactory(
104111
self._session.get_component('event_emitter')
105112
)
@@ -243,6 +250,7 @@ def client(
243250
aws_session_token=None,
244251
config=None,
245252
aws_account_id=None,
253+
aws_bearer_token=None,
246254
):
247255
"""
248256
Create a low-level service client by name.
@@ -314,9 +322,25 @@ def client(
314322
:param aws_account_id: The account id to use when creating
315323
the client. Same semantics as aws_access_key_id above.
316324
325+
:type aws_bearer_token: string
326+
:param aws_bearer_token: The bearer token to use for
327+
authentication with services that support bearer token auth
328+
(e.g. Bedrock). If provided, this takes precedence over any
329+
bearer token resolved from environment variables and any
330+
session-level token. Same semantics as aws_access_key_id
331+
above.
332+
317333
:return: Service client instance
318334
319335
"""
336+
# Determine the effective bearer token to use
337+
# Precedence: client parameter > session parameter > environment variable
338+
effective_aws_bearer_token = (
339+
aws_bearer_token
340+
if aws_bearer_token is not None
341+
else self._aws_bearer_token
342+
)
343+
320344
create_client_kwargs = {
321345
'region_name': region_name,
322346
'api_version': api_version,
@@ -328,11 +352,16 @@ def client(
328352
'aws_session_token': aws_session_token,
329353
'config': config,
330354
'aws_account_id': aws_account_id,
355+
'aws_bearer_token': effective_aws_bearer_token,
331356
}
332357
if aws_account_id is None:
333358
# Remove aws_account_id for arbitrary
334359
# botocore version mismatches in AWS Lambda.
335360
del create_client_kwargs['aws_account_id']
361+
if effective_aws_bearer_token is None:
362+
# Remove aws_bearer_token for arbitrary
363+
# botocore version mismatches in AWS Lambda.
364+
del create_client_kwargs['aws_bearer_token']
336365

337366
return self._session.create_client(
338367
service_name, **create_client_kwargs
@@ -350,6 +379,7 @@ def resource(
350379
aws_secret_access_key=None,
351380
aws_session_token=None,
352381
config=None,
382+
aws_bearer_token=None,
353383
):
354384
"""
355385
Create a resource service client by name.
@@ -419,6 +449,14 @@ def resource(
419449
<https://docs.aws.amazon.com/botocore/latest/reference/config.html>`_
420450
for more details.
421451
452+
:type aws_bearer_token: string
453+
:param aws_bearer_token: The bearer token to use for
454+
authentication with services that support bearer token auth
455+
(e.g. Bedrock). If provided, this takes precedence over any
456+
bearer token resolved from environment variables and any
457+
session-level token. Same semantics as aws_access_key_id
458+
above.
459+
422460
:return: Subclass of :py:class:`~boto3.resources.base.ServiceResource`
423461
"""
424462
try:
@@ -483,6 +521,7 @@ def resource(
483521
aws_secret_access_key=aws_secret_access_key,
484522
aws_session_token=aws_session_token,
485523
config=config,
524+
aws_bearer_token=aws_bearer_token,
486525
)
487526
service_model = client.meta.service_model
488527

tests/unit/test_session.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,116 @@ def test_create_client_with_args(self):
272272
config=None,
273273
)
274274

275+
def test_session_with_aws_bearer_token(self):
276+
session = Session(aws_bearer_token='test-token')
277+
assert session._aws_bearer_token == 'test-token'
278+
279+
def test_session_with_aws_bearer_token_none(self):
280+
session = Session()
281+
assert session._aws_bearer_token is None
282+
283+
def test_create_client_with_aws_bearer_token(self):
284+
bc_session = self.bc_session_cls.return_value
285+
286+
session = Session(region_name='us-east-1')
287+
session.client(
288+
'bedrock-runtime',
289+
region_name='us-west-2',
290+
aws_bearer_token='test-bearer-token',
291+
)
292+
293+
bc_session.create_client.assert_called_with(
294+
'bedrock-runtime',
295+
aws_secret_access_key=None,
296+
aws_access_key_id=None,
297+
endpoint_url=None,
298+
use_ssl=True,
299+
aws_session_token=None,
300+
verify=None,
301+
region_name='us-west-2',
302+
api_version=None,
303+
config=None,
304+
aws_bearer_token='test-bearer-token',
305+
)
306+
307+
def test_create_client_with_session_level_aws_bearer_token(self):
308+
bc_session = self.bc_session_cls.return_value
309+
310+
session = Session(
311+
region_name='us-east-1',
312+
aws_bearer_token='session-level-token',
313+
)
314+
session.client('bedrock-runtime', region_name='us-west-2')
315+
316+
bc_session.create_client.assert_called_with(
317+
'bedrock-runtime',
318+
aws_secret_access_key=None,
319+
aws_access_key_id=None,
320+
endpoint_url=None,
321+
use_ssl=True,
322+
aws_session_token=None,
323+
verify=None,
324+
region_name='us-west-2',
325+
api_version=None,
326+
config=None,
327+
aws_bearer_token='session-level-token',
328+
)
329+
330+
def test_create_client_aws_bearer_token_client_overrides_session(self):
331+
bc_session = self.bc_session_cls.return_value
332+
333+
session = Session(
334+
region_name='us-east-1',
335+
aws_bearer_token='session-level-token',
336+
)
337+
session.client(
338+
'bedrock-runtime',
339+
region_name='us-west-2',
340+
aws_bearer_token='client-level-token',
341+
)
342+
343+
bc_session.create_client.assert_called_with(
344+
'bedrock-runtime',
345+
aws_secret_access_key=None,
346+
aws_access_key_id=None,
347+
endpoint_url=None,
348+
use_ssl=True,
349+
aws_session_token=None,
350+
verify=None,
351+
region_name='us-west-2',
352+
api_version=None,
353+
config=None,
354+
aws_bearer_token='client-level-token',
355+
)
356+
357+
def test_create_client_aws_bearer_token_empty_string_overrides_session(self):
358+
bc_session = self.bc_session_cls.return_value
359+
360+
session = Session(
361+
region_name='us-east-1',
362+
aws_bearer_token='session-level-token',
363+
)
364+
# Empty string is an explicit value and should override session token
365+
session.client(
366+
'bedrock-runtime',
367+
region_name='us-west-2',
368+
aws_bearer_token='',
369+
)
370+
371+
bc_session.create_client.assert_called_with(
372+
'bedrock-runtime',
373+
aws_secret_access_key=None,
374+
aws_access_key_id=None,
375+
endpoint_url=None,
376+
use_ssl=True,
377+
aws_session_token=None,
378+
verify=None,
379+
region_name='us-west-2',
380+
api_version=None,
381+
config=None,
382+
aws_bearer_token='',
383+
)
384+
275385
def test_create_client_with_aws_account_id(self):
276386
bc_session = self.bc_session_cls.return_value
277387

@@ -324,6 +434,7 @@ def test_create_resource_with_args(self):
324434
region_name=None,
325435
api_version='2014-11-02',
326436
config=mock.ANY,
437+
aws_bearer_token=None,
327438
)
328439
client_config = session.client.call_args[1]['config']
329440
assert client_config.user_agent_extra == 'Resource'
@@ -356,6 +467,7 @@ def test_create_resource_with_config(self):
356467
region_name=None,
357468
api_version='2014-11-02',
358469
config=mock.ANY,
470+
aws_bearer_token=None,
359471
)
360472
client_config = session.client.call_args[1]['config']
361473
assert client_config.user_agent_extra == 'Resource'
@@ -388,6 +500,7 @@ def test_create_resource_with_config_override_user_agent_extra(self):
388500
region_name=None,
389501
api_version='2014-11-02',
390502
config=mock.ANY,
503+
aws_bearer_token=None,
391504
)
392505
client_config = session.client.call_args[1]['config']
393506
assert client_config.user_agent_extra == 'foo'

0 commit comments

Comments
 (0)