boto3-refresh-session 1.3.11__py3-none-any.whl → 7.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of boto3-refresh-session might be problematic. Click here for more details.
- boto3_refresh_session/__init__.py +19 -6
- boto3_refresh_session/exceptions.py +116 -4
- boto3_refresh_session/methods/__init__.py +14 -0
- boto3_refresh_session/methods/custom.py +145 -0
- boto3_refresh_session/methods/iot/__init__.py +11 -0
- boto3_refresh_session/methods/iot/core.py +46 -0
- boto3_refresh_session/methods/iot/x509.py +614 -0
- boto3_refresh_session/methods/sts.py +276 -0
- boto3_refresh_session/session.py +58 -120
- boto3_refresh_session/utils/__init__.py +16 -0
- boto3_refresh_session/utils/cache.py +98 -0
- boto3_refresh_session/utils/constants.py +16 -0
- boto3_refresh_session/utils/internal.py +441 -0
- boto3_refresh_session/utils/typing.py +137 -0
- boto3_refresh_session-7.0.1.dist-info/METADATA +168 -0
- boto3_refresh_session-7.0.1.dist-info/RECORD +19 -0
- {boto3_refresh_session-1.3.11.dist-info → boto3_refresh_session-7.0.1.dist-info}/WHEEL +1 -1
- boto3_refresh_session-7.0.1.dist-info/licenses/LICENSE +373 -0
- boto3_refresh_session-7.0.1.dist-info/licenses/NOTICE +21 -0
- boto3_refresh_session/custom.py +0 -108
- boto3_refresh_session/ecs.py +0 -109
- boto3_refresh_session/sts.py +0 -85
- boto3_refresh_session-1.3.11.dist-info/LICENSE +0 -21
- boto3_refresh_session-1.3.11.dist-info/METADATA +0 -178
- boto3_refresh_session-1.3.11.dist-info/NOTICE +0 -12
- boto3_refresh_session-1.3.11.dist-info/RECORD +0 -11
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
"""STS assume-role refreshable session implementation."""
|
|
6
|
+
|
|
7
|
+
__all__ = ["STSRefreshableSession"]
|
|
8
|
+
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
from ..exceptions import BRSConfigurationError, BRSValidationError, BRSWarning
|
|
12
|
+
from ..utils import (
|
|
13
|
+
MFA_SERIAL_PATTERN,
|
|
14
|
+
ROLE_ARN_PATTERN,
|
|
15
|
+
ROLE_SESSION_NAME_PATTERN,
|
|
16
|
+
AssumeRoleParams,
|
|
17
|
+
BaseRefreshableSession,
|
|
18
|
+
Identity,
|
|
19
|
+
STSClientParams,
|
|
20
|
+
TemporaryCredentials,
|
|
21
|
+
refreshable_session,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@refreshable_session
|
|
26
|
+
class STSRefreshableSession(BaseRefreshableSession, registry_key="sts"):
|
|
27
|
+
"""A :class:`boto3.session.Session` object that automatically refreshes
|
|
28
|
+
temporary AWS credentials using an IAM role that is assumed via STS.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
assume_role_kwargs : AssumeRoleParams
|
|
33
|
+
Required keyword arguments for :meth:`STS.Client.assume_role` (i.e.
|
|
34
|
+
boto3 STS client). ``RoleArn`` is required. ``RoleSessionName`` will
|
|
35
|
+
default to 'boto3-refresh-session' if not provided.
|
|
36
|
+
|
|
37
|
+
For MFA authentication, two modalities are supported:
|
|
38
|
+
|
|
39
|
+
1. **Dynamic tokens (recommended)**: Provide ``SerialNumber`` in
|
|
40
|
+
``assume_role_kwargs`` and pass ``mfa_token_provider`` callable.
|
|
41
|
+
The provider callable will be invoked on each refresh to obtain
|
|
42
|
+
fresh MFA tokens. Do not include ``TokenCode`` in this case.
|
|
43
|
+
|
|
44
|
+
2. **Static/injectable tokens**: Provide both ``SerialNumber`` and
|
|
45
|
+
``TokenCode`` in ``assume_role_kwargs``. You are responsible for
|
|
46
|
+
updating ``assume_role_kwargs["TokenCode"]`` before the token
|
|
47
|
+
expires.
|
|
48
|
+
sts_client_kwargs : STSClientParams, optional
|
|
49
|
+
Optional keyword arguments for the :class:`STS.Client` object. Do not
|
|
50
|
+
provide values for ``service_name`` as they are unnecessary. Default
|
|
51
|
+
is None.
|
|
52
|
+
mfa_token_provider : Callable[[], str], optional
|
|
53
|
+
An optional callable that returns a string representing a fresh MFA
|
|
54
|
+
token code. If provided, this will be called during each credential
|
|
55
|
+
refresh to obtain a new token, which overrides any ``TokenCode`` in
|
|
56
|
+
``assume_role_kwargs``. When using this parameter, ``SerialNumber``
|
|
57
|
+
must be provided in ``assume_role_kwargs``. Default is None.
|
|
58
|
+
mfa_token_provider_kwargs : dict, optional
|
|
59
|
+
Optional keyword arguments to pass to the ``mfa_token_provider``
|
|
60
|
+
callable. Default is None.
|
|
61
|
+
defer_refresh : bool, optional
|
|
62
|
+
If ``True`` then temporary credentials are not automatically refreshed
|
|
63
|
+
until they are explicitly needed. If ``False`` then temporary
|
|
64
|
+
credentials refresh immediately upon expiration. It is highly
|
|
65
|
+
recommended that you use ``True``. Default is ``True``.
|
|
66
|
+
advisory_timeout : int, optional
|
|
67
|
+
USE THIS ARGUMENT WITH CAUTION!!!
|
|
68
|
+
|
|
69
|
+
Botocore will attempt to refresh credentials early according to
|
|
70
|
+
this value (in seconds), but will continue using the existing
|
|
71
|
+
credentials if refresh fails. Default is 15 minutes (900 seconds).
|
|
72
|
+
mandatory_timeout : int, optional
|
|
73
|
+
USE THIS ARGUMENT WITH CAUTION!!!
|
|
74
|
+
|
|
75
|
+
Botocore requires a successful refresh before continuing. If
|
|
76
|
+
refresh fails in this window (in seconds), API calls may fail.
|
|
77
|
+
Default is 10 minutes (600 seconds).
|
|
78
|
+
cache_clients : bool, optional
|
|
79
|
+
If ``True`` then clients created by this session will be cached and
|
|
80
|
+
reused for subsequent calls to :meth:`client()` with the same
|
|
81
|
+
parameter signatures. Due to the memory overhead of clients, the
|
|
82
|
+
default is ``True`` in order to protect system resources.
|
|
83
|
+
|
|
84
|
+
Other Parameters
|
|
85
|
+
----------------
|
|
86
|
+
kwargs : dict
|
|
87
|
+
Optional keyword arguments for the :class:`boto3.session.Session`
|
|
88
|
+
object.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
assume_role_kwargs: AssumeRoleParams,
|
|
94
|
+
sts_client_kwargs: STSClientParams | None = None,
|
|
95
|
+
mfa_token_provider: Callable[[], str] | None = None,
|
|
96
|
+
mfa_token_provider_kwargs: dict | None = None,
|
|
97
|
+
**kwargs,
|
|
98
|
+
):
|
|
99
|
+
# ensuring 'refresh_method' is not set manually
|
|
100
|
+
if "refresh_method" in kwargs:
|
|
101
|
+
BRSWarning.warn(
|
|
102
|
+
"'refresh_method' cannot be set manually. "
|
|
103
|
+
"Reverting to 'sts-assume-role'."
|
|
104
|
+
)
|
|
105
|
+
del kwargs["refresh_method"]
|
|
106
|
+
|
|
107
|
+
# verifying 'RoleArn' is provided in 'assume_role_kwargs'
|
|
108
|
+
if "RoleArn" not in assume_role_kwargs:
|
|
109
|
+
raise BRSConfigurationError(
|
|
110
|
+
"'RoleArn' must be provided in 'assume_role_kwargs'!",
|
|
111
|
+
param="RoleArn",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# verifying 'RoleArn' format
|
|
115
|
+
if not ROLE_ARN_PATTERN.match(assume_role_kwargs["RoleArn"]):
|
|
116
|
+
raise BRSValidationError(
|
|
117
|
+
"'RoleArn' in 'assume_role_kwargs' is not a valid AWS "
|
|
118
|
+
"Role ARN!",
|
|
119
|
+
param="RoleArn",
|
|
120
|
+
value=assume_role_kwargs.get("RoleArn"),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# setting default 'RoleSessionName' if not provided
|
|
124
|
+
if "RoleSessionName" not in assume_role_kwargs:
|
|
125
|
+
BRSWarning.warn(
|
|
126
|
+
"'RoleSessionName' not provided in "
|
|
127
|
+
"'assume_role_kwargs'! Defaulting to "
|
|
128
|
+
"'boto3-refresh-session'."
|
|
129
|
+
)
|
|
130
|
+
assume_role_kwargs["RoleSessionName"] = "boto3-refresh-session"
|
|
131
|
+
|
|
132
|
+
# verifying 'RoleSessionName' format
|
|
133
|
+
if not ROLE_SESSION_NAME_PATTERN.match(
|
|
134
|
+
assume_role_kwargs["RoleSessionName"]
|
|
135
|
+
):
|
|
136
|
+
raise BRSValidationError(
|
|
137
|
+
"'RoleSessionName' in 'assume_role_kwargs' is not valid! "
|
|
138
|
+
"It must be 2-64 characters long and can contain only "
|
|
139
|
+
"alphanumeric characters and the following symbols: "
|
|
140
|
+
"'+=,.@-'.",
|
|
141
|
+
param="RoleSessionName",
|
|
142
|
+
value=assume_role_kwargs.get("RoleSessionName"),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# store MFA token provider
|
|
146
|
+
try:
|
|
147
|
+
# verifying type of mfa_token_provider
|
|
148
|
+
assert (
|
|
149
|
+
isinstance(mfa_token_provider, Callable)
|
|
150
|
+
or mfa_token_provider is None
|
|
151
|
+
)
|
|
152
|
+
self.mfa_token_provider = mfa_token_provider
|
|
153
|
+
except AssertionError as err:
|
|
154
|
+
raise BRSValidationError(
|
|
155
|
+
"'mfa_token_provider' must be a callable that returns a "
|
|
156
|
+
"string representing an MFA token code!",
|
|
157
|
+
param="mfa_token_provider",
|
|
158
|
+
) from err
|
|
159
|
+
|
|
160
|
+
# storing mfa_token_provider_kwargs
|
|
161
|
+
self.mfa_token_provider_kwargs = (
|
|
162
|
+
mfa_token_provider_kwargs if mfa_token_provider_kwargs else {}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# verifying 'SerialNumber' format if provided
|
|
166
|
+
if "SerialNumber" in assume_role_kwargs:
|
|
167
|
+
if not MFA_SERIAL_PATTERN.match(
|
|
168
|
+
assume_role_kwargs["SerialNumber"]
|
|
169
|
+
):
|
|
170
|
+
raise BRSValidationError(
|
|
171
|
+
"'SerialNumber' in 'assume_role_kwargs' is not a valid "
|
|
172
|
+
"AWS MFA device ARN!",
|
|
173
|
+
param="SerialNumber",
|
|
174
|
+
value=assume_role_kwargs.get("SerialNumber"),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# ensure SerialNumber is set appropriately with mfa_token_provider
|
|
178
|
+
if (
|
|
179
|
+
self.mfa_token_provider
|
|
180
|
+
and "SerialNumber" not in assume_role_kwargs
|
|
181
|
+
):
|
|
182
|
+
raise BRSConfigurationError(
|
|
183
|
+
"'SerialNumber' must be provided in 'assume_role_kwargs' "
|
|
184
|
+
"when using 'mfa_token_provider'!",
|
|
185
|
+
param="SerialNumber",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# ensure SerialNumber and TokenCode are set without mfa_token_provider
|
|
189
|
+
if (
|
|
190
|
+
self.mfa_token_provider is None
|
|
191
|
+
and (
|
|
192
|
+
"SerialNumber" in assume_role_kwargs
|
|
193
|
+
and "TokenCode" not in assume_role_kwargs
|
|
194
|
+
)
|
|
195
|
+
or (
|
|
196
|
+
"SerialNumber" not in assume_role_kwargs
|
|
197
|
+
and "TokenCode" in assume_role_kwargs
|
|
198
|
+
)
|
|
199
|
+
):
|
|
200
|
+
raise BRSConfigurationError(
|
|
201
|
+
"'SerialNumber' and 'TokenCode' must be provided in "
|
|
202
|
+
"'assume_role_kwargs' when 'mfa_token_provider' is not set!",
|
|
203
|
+
param="SerialNumber/TokenCode",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# warn if TokenCode provided with mfa_token_provider
|
|
207
|
+
if self.mfa_token_provider and "TokenCode" in assume_role_kwargs:
|
|
208
|
+
BRSWarning.warn(
|
|
209
|
+
"'TokenCode' provided in 'assume_role_kwargs' will be "
|
|
210
|
+
"ignored and overridden by 'mfa_token_provider' on each "
|
|
211
|
+
"refresh."
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# initializing assume role kwargs attribute
|
|
215
|
+
self.assume_role_kwargs = assume_role_kwargs
|
|
216
|
+
|
|
217
|
+
# initializing BRSSession
|
|
218
|
+
super().__init__(refresh_method="sts-assume-role", **kwargs)
|
|
219
|
+
|
|
220
|
+
if sts_client_kwargs is not None:
|
|
221
|
+
# overwriting 'service_name' if if appears in sts_client_kwargs
|
|
222
|
+
if "service_name" in sts_client_kwargs:
|
|
223
|
+
BRSWarning.warn(
|
|
224
|
+
"'sts_client_kwargs' cannot contain values for "
|
|
225
|
+
"'service_name'. Reverting to service_name = 'sts'."
|
|
226
|
+
)
|
|
227
|
+
del sts_client_kwargs["service_name"]
|
|
228
|
+
self._sts_client = self.client(
|
|
229
|
+
service_name="sts", **sts_client_kwargs
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
self._sts_client = self.client(service_name="sts")
|
|
233
|
+
|
|
234
|
+
def _get_credentials(self) -> TemporaryCredentials:
|
|
235
|
+
params = dict(self.assume_role_kwargs)
|
|
236
|
+
|
|
237
|
+
# override TokenCode with fresh token from provider if configured
|
|
238
|
+
if self.mfa_token_provider:
|
|
239
|
+
params["TokenCode"] = self.mfa_token_provider(
|
|
240
|
+
**self.mfa_token_provider_kwargs
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# validating TokenCode format
|
|
244
|
+
if (token_code := params.get("TokenCode")) is not None:
|
|
245
|
+
if (
|
|
246
|
+
not isinstance(token_code, str)
|
|
247
|
+
or len(token_code) != 6
|
|
248
|
+
or not token_code.isdigit()
|
|
249
|
+
):
|
|
250
|
+
raise BRSValidationError(
|
|
251
|
+
"'TokenCode' must be a 6-digit string per AWS MFA "
|
|
252
|
+
"token specifications!",
|
|
253
|
+
param="TokenCode",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
temporary_credentials = self._sts_client.assume_role(**params)[
|
|
257
|
+
"Credentials"
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
"access_key": temporary_credentials.get("AccessKeyId"),
|
|
262
|
+
"secret_key": temporary_credentials.get("SecretAccessKey"),
|
|
263
|
+
"token": temporary_credentials.get("SessionToken"),
|
|
264
|
+
"expiry_time": temporary_credentials.get("Expiration").isoformat(),
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
def get_identity(self) -> Identity:
|
|
268
|
+
"""Returns metadata about the identity assumed.
|
|
269
|
+
|
|
270
|
+
Returns
|
|
271
|
+
-------
|
|
272
|
+
Identity
|
|
273
|
+
Dict containing caller identity according to AWS STS.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
return self._sts_client.get_caller_identity()
|
boto3_refresh_session/session.py
CHANGED
|
@@ -1,151 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from abc import ABC, abstractmethod
|
|
6
|
-
from typing import Any, Callable, ClassVar, Literal, get_args
|
|
7
|
-
|
|
8
|
-
from boto3.session import Session
|
|
9
|
-
from botocore.credentials import (
|
|
10
|
-
DeferredRefreshableCredentials,
|
|
11
|
-
RefreshableCredentials,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
from .exceptions import BRSError, BRSWarning
|
|
15
|
-
|
|
16
|
-
#: Type alias for all currently available credential refresh methods.
|
|
17
|
-
Method = Literal["sts", "ecs", "custom"]
|
|
18
|
-
RefreshMethod = Literal["sts-assume-role", "ecs-container-metadata", "custom"]
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
19
4
|
|
|
5
|
+
"""Public factory for constructing refreshable boto3 sessions."""
|
|
20
6
|
|
|
21
|
-
|
|
22
|
-
"""Abstract base class for implementing refreshable AWS sessions.
|
|
23
|
-
|
|
24
|
-
Provides a common interface and factory registration mechanism
|
|
25
|
-
for subclasses that generate temporary credentials using various
|
|
26
|
-
AWS authentication methods (e.g., STS).
|
|
27
|
-
|
|
28
|
-
Subclasses must implement ``_get_credentials()`` and ``get_identity()``.
|
|
29
|
-
They should also register themselves using the ``method=...`` argument
|
|
30
|
-
to ``__init_subclass__``.
|
|
31
|
-
|
|
32
|
-
Parameters
|
|
33
|
-
----------
|
|
34
|
-
registry : dict[str, type[BaseRefreshableSession]]
|
|
35
|
-
Class-level registry mapping method names to registered session types.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
# adding this and __init_subclass__ to avoid circular imports
|
|
39
|
-
# as well as simplify future addition of new methods
|
|
40
|
-
registry: ClassVar[dict[Method, type[BaseRefreshableSession]]] = {}
|
|
41
|
-
|
|
42
|
-
def __init_subclass__(cls, method: Method):
|
|
43
|
-
super().__init_subclass__()
|
|
44
|
-
|
|
45
|
-
# guarantees that methods are unique
|
|
46
|
-
if method in BaseRefreshableSession.registry:
|
|
47
|
-
BRSWarning(
|
|
48
|
-
f"Method {repr(method)} is already registered. Overwriting."
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
BaseRefreshableSession.registry[method] = cls
|
|
52
|
-
|
|
53
|
-
def __init__(self, **kwargs):
|
|
54
|
-
super().__init__(**kwargs)
|
|
55
|
-
|
|
56
|
-
@abstractmethod
|
|
57
|
-
def _get_credentials(self) -> dict[str, str]: ...
|
|
58
|
-
|
|
59
|
-
@abstractmethod
|
|
60
|
-
def get_identity(self) -> dict[str, Any]: ...
|
|
61
|
-
|
|
62
|
-
def _refresh_using(
|
|
63
|
-
self,
|
|
64
|
-
credentials_method: Callable,
|
|
65
|
-
defer_refresh: bool,
|
|
66
|
-
refresh_method: RefreshMethod,
|
|
67
|
-
):
|
|
68
|
-
# determining how exactly to refresh expired temporary credentials
|
|
69
|
-
if not defer_refresh:
|
|
70
|
-
self._credentials = RefreshableCredentials.create_from_metadata(
|
|
71
|
-
metadata=credentials_method(),
|
|
72
|
-
refresh_using=credentials_method,
|
|
73
|
-
method=refresh_method,
|
|
74
|
-
)
|
|
75
|
-
else:
|
|
76
|
-
self._credentials = DeferredRefreshableCredentials(
|
|
77
|
-
refresh_using=credentials_method, method=refresh_method
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
def refreshable_credentials(self) -> dict[str, str]:
|
|
81
|
-
"""The current temporary AWS security credentials.
|
|
82
|
-
|
|
83
|
-
Returns
|
|
84
|
-
-------
|
|
85
|
-
dict[str, str]
|
|
86
|
-
Temporary AWS security credentials containing:
|
|
87
|
-
AWS_ACCESS_KEY_ID : str
|
|
88
|
-
AWS access key identifier.
|
|
89
|
-
AWS_SECRET_ACCESS_KEY : str
|
|
90
|
-
AWS secret access key.
|
|
91
|
-
AWS_SESSION_TOKEN : str
|
|
92
|
-
AWS session token.
|
|
93
|
-
"""
|
|
7
|
+
from __future__ import annotations
|
|
94
8
|
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
"AWS_ACCESS_KEY_ID": creds.access_key,
|
|
98
|
-
"AWS_SECRET_ACCESS_KEY": creds.secret_key,
|
|
99
|
-
"AWS_SESSION_TOKEN": creds.token,
|
|
100
|
-
}
|
|
9
|
+
__all__ = ["RefreshableSession"]
|
|
101
10
|
|
|
102
|
-
|
|
103
|
-
def credentials(self) -> dict[str, str]:
|
|
104
|
-
"""The current temporary AWS security credentials."""
|
|
11
|
+
from typing import get_args
|
|
105
12
|
|
|
106
|
-
|
|
13
|
+
from .exceptions import BRSValidationError
|
|
14
|
+
from .utils import BaseRefreshableSession, Method
|
|
107
15
|
|
|
108
16
|
|
|
109
17
|
class RefreshableSession:
|
|
110
|
-
"""Factory class for constructing refreshable boto3 sessions using various
|
|
111
|
-
methods, e.g. STS.
|
|
18
|
+
"""Factory class for constructing refreshable boto3 sessions using various
|
|
19
|
+
authentication methods, e.g. STS.
|
|
112
20
|
|
|
113
|
-
This class provides a unified interface for creating boto3 sessions whose
|
|
114
|
-
automatically refreshed in the background.
|
|
21
|
+
This class provides a unified interface for creating boto3 sessions whose
|
|
22
|
+
credentials are automatically refreshed in the background.
|
|
115
23
|
|
|
116
|
-
Use ``RefreshableSession(method="...")`` to construct an instance using
|
|
24
|
+
Use ``RefreshableSession(method="...")`` to construct an instance using
|
|
25
|
+
the desired method.
|
|
117
26
|
|
|
118
|
-
For additional information on required parameters, refer to the See Also
|
|
27
|
+
For additional information on required parameters, refer to the See Also
|
|
28
|
+
section below.
|
|
119
29
|
|
|
120
30
|
Parameters
|
|
121
31
|
----------
|
|
122
32
|
method : Method
|
|
123
|
-
The authentication and refresh method to use for the session. Must
|
|
124
|
-
Default is "sts".
|
|
33
|
+
The authentication and refresh method to use for the session. Must
|
|
34
|
+
match a registered method name. Default is "sts".
|
|
35
|
+
defer_refresh : bool, optional
|
|
36
|
+
If ``True`` then temporary credentials are not automatically refreshed
|
|
37
|
+
until they are explicitly needed. If ``False`` then temporary
|
|
38
|
+
credentials refresh immediately upon expiration. It is highly
|
|
39
|
+
recommended that you use ``True``. Default is ``True``.
|
|
40
|
+
advisory_timeout : int, optional
|
|
41
|
+
USE THIS ARGUMENT WITH CAUTION!!!
|
|
42
|
+
|
|
43
|
+
Botocore will attempt to refresh credentials early according to
|
|
44
|
+
this value (in seconds), but will continue using the existing
|
|
45
|
+
credentials if refresh fails. Default is 15 minutes (900 seconds).
|
|
46
|
+
mandatory_timeout : int, optional
|
|
47
|
+
USE THIS ARGUMENT WITH CAUTION!!!
|
|
48
|
+
|
|
49
|
+
Botocore requires a successful refresh before continuing. If
|
|
50
|
+
refresh fails in this window (in seconds), API calls may fail.
|
|
51
|
+
Default is 10 minutes (600 seconds).
|
|
52
|
+
cache_clients : bool, optional
|
|
53
|
+
If ``True`` then clients created by this session will be cached and
|
|
54
|
+
reused for subsequent calls to :meth:`client()` with the same
|
|
55
|
+
parameter signatures. Due to the memory overhead of clients, the
|
|
56
|
+
default is ``True`` in order to protect system resources.
|
|
125
57
|
|
|
126
58
|
Other Parameters
|
|
127
59
|
----------------
|
|
128
60
|
**kwargs : dict
|
|
129
|
-
Additional keyword arguments forwarded to the constructor of the
|
|
61
|
+
Additional keyword arguments forwarded to the constructor of the
|
|
62
|
+
selected session class.
|
|
130
63
|
|
|
131
64
|
See Also
|
|
132
65
|
--------
|
|
133
|
-
boto3_refresh_session.custom.CustomRefreshableSession
|
|
134
|
-
boto3_refresh_session.
|
|
135
|
-
boto3_refresh_session.
|
|
66
|
+
boto3_refresh_session.methods.custom.CustomRefreshableSession
|
|
67
|
+
boto3_refresh_session.methods.iot.x509.IOTX509RefreshableSession
|
|
68
|
+
boto3_refresh_session.methods.sts.STSRefreshableSession
|
|
136
69
|
"""
|
|
137
70
|
|
|
138
71
|
def __new__(
|
|
139
72
|
cls, method: Method = "sts", **kwargs
|
|
140
73
|
) -> BaseRefreshableSession:
|
|
141
74
|
if method not in (methods := cls.get_available_methods()):
|
|
142
|
-
raise
|
|
143
|
-
f"{
|
|
144
|
-
|
|
75
|
+
raise BRSValidationError(
|
|
76
|
+
f"{method!r} is an invalid method parameter. "
|
|
77
|
+
"Available methods are "
|
|
78
|
+
f"{', '.join(repr(meth) for meth in methods)}.",
|
|
79
|
+
param="method",
|
|
80
|
+
value=method,
|
|
145
81
|
)
|
|
146
82
|
|
|
147
|
-
|
|
148
|
-
return obj(**kwargs)
|
|
83
|
+
return BaseRefreshableSession.registry[method](**kwargs)
|
|
149
84
|
|
|
150
85
|
@classmethod
|
|
151
86
|
def get_available_methods(cls) -> list[str]:
|
|
@@ -154,7 +89,10 @@ class RefreshableSession:
|
|
|
154
89
|
Returns
|
|
155
90
|
-------
|
|
156
91
|
list[str]
|
|
157
|
-
A list of all currently available credential refresh methods,
|
|
92
|
+
A list of all currently available credential refresh methods,
|
|
93
|
+
e.g. 'sts', 'custom'.
|
|
158
94
|
"""
|
|
159
95
|
|
|
160
|
-
|
|
96
|
+
args = list(get_args(Method))
|
|
97
|
+
args.remove("__sentinel__")
|
|
98
|
+
return args
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
__all__ = []
|
|
6
|
+
|
|
7
|
+
from . import cache, constants, internal, typing
|
|
8
|
+
from .cache import *
|
|
9
|
+
from .constants import *
|
|
10
|
+
from .internal import *
|
|
11
|
+
from .typing import *
|
|
12
|
+
|
|
13
|
+
__all__.extend(cache.__all__)
|
|
14
|
+
__all__.extend(constants.__all__)
|
|
15
|
+
__all__.extend(internal.__all__)
|
|
16
|
+
__all__.extend(typing.__all__)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
"""Cache primitives for memoizing boto3 client instances.
|
|
6
|
+
|
|
7
|
+
`ClientCache` provides a thread-safe mapping for cached clients and raises
|
|
8
|
+
`BRSCacheError` when lookups or mutations violate the expected cache contract.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__all__ = ["ClientCache"]
|
|
12
|
+
|
|
13
|
+
from threading import Lock
|
|
14
|
+
from typing import Hashable, Optional
|
|
15
|
+
|
|
16
|
+
from botocore.client import BaseClient
|
|
17
|
+
|
|
18
|
+
from ..exceptions import BRSCacheExistsError, BRSCacheNotFoundError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClientCache:
|
|
22
|
+
"""A thread-safe cache for storing boto3 clients which can be used like a
|
|
23
|
+
dictionary."""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._cache: dict[Hashable, BaseClient] = {}
|
|
27
|
+
self._lock = Lock()
|
|
28
|
+
|
|
29
|
+
def __len__(self) -> int:
|
|
30
|
+
with self._lock:
|
|
31
|
+
return len(self._cache)
|
|
32
|
+
|
|
33
|
+
def __contains__(self, hash: Hashable) -> bool:
|
|
34
|
+
with self._lock:
|
|
35
|
+
return hash in self._cache
|
|
36
|
+
|
|
37
|
+
def __iter__(self):
|
|
38
|
+
with self._lock:
|
|
39
|
+
return iter(self._cache.keys())
|
|
40
|
+
|
|
41
|
+
def __getitem__(self, hash: Hashable) -> BaseClient:
|
|
42
|
+
with self._lock:
|
|
43
|
+
try:
|
|
44
|
+
return self._cache[hash]
|
|
45
|
+
except KeyError as err:
|
|
46
|
+
raise BRSCacheNotFoundError(
|
|
47
|
+
"The client you requested has not been cached."
|
|
48
|
+
) from err
|
|
49
|
+
|
|
50
|
+
def __setitem__(self, hash: Hashable, client: BaseClient) -> None:
|
|
51
|
+
with self._lock:
|
|
52
|
+
if hash in self._cache:
|
|
53
|
+
raise BRSCacheExistsError("Client already exists in cache.")
|
|
54
|
+
|
|
55
|
+
self._cache[hash] = client
|
|
56
|
+
|
|
57
|
+
def __delitem__(self, hash: Hashable) -> None:
|
|
58
|
+
with self._lock:
|
|
59
|
+
if hash not in self._cache:
|
|
60
|
+
raise BRSCacheNotFoundError("Client not found in cache.")
|
|
61
|
+
del self._cache[hash]
|
|
62
|
+
|
|
63
|
+
def keys(self) -> tuple[Hashable, ...]:
|
|
64
|
+
"""Returns the keys in the cache."""
|
|
65
|
+
|
|
66
|
+
with self._lock:
|
|
67
|
+
return tuple(self._cache.keys())
|
|
68
|
+
|
|
69
|
+
def values(self) -> tuple[BaseClient, ...]:
|
|
70
|
+
"""Returns the clients from the cache."""
|
|
71
|
+
|
|
72
|
+
with self._lock:
|
|
73
|
+
return tuple(self._cache.values())
|
|
74
|
+
|
|
75
|
+
def items(self) -> tuple[tuple[Hashable, BaseClient], ...]:
|
|
76
|
+
"""Returns the items in the cache as (hash, BaseClient) tuples."""
|
|
77
|
+
|
|
78
|
+
with self._lock:
|
|
79
|
+
return tuple(self._cache.items())
|
|
80
|
+
|
|
81
|
+
def get(
|
|
82
|
+
self, hash: Hashable, default: Optional[BaseClient] = None
|
|
83
|
+
) -> Optional[BaseClient]:
|
|
84
|
+
"""Gets the client using the given signature, or returns None if no
|
|
85
|
+
default is provided.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
with self._lock:
|
|
89
|
+
return self._cache.get(hash, default)
|
|
90
|
+
|
|
91
|
+
def pop(self, hash: Hashable) -> BaseClient:
|
|
92
|
+
"""Pops and returns the client using the given signature."""
|
|
93
|
+
|
|
94
|
+
with self._lock:
|
|
95
|
+
if (client := self._cache.get(hash)) is None:
|
|
96
|
+
raise BRSCacheNotFoundError("Client not found in cache.")
|
|
97
|
+
del self._cache[hash]
|
|
98
|
+
return client
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ROLE_ARN_PATTERN",
|
|
7
|
+
"MFA_SERIAL_PATTERN",
|
|
8
|
+
"ROLE_SESSION_NAME_PATTERN",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
from re import compile
|
|
12
|
+
|
|
13
|
+
# AWS ARN validation patterns
|
|
14
|
+
ROLE_ARN_PATTERN = compile(r"^arn:aws[a-z-]*:iam::\d{12}:role/[\w+=,.@-]+$")
|
|
15
|
+
MFA_SERIAL_PATTERN = compile(r"^arn:aws[a-z-]*:iam::\d{12}:mfa/[\w+=,.@-]+$")
|
|
16
|
+
ROLE_SESSION_NAME_PATTERN = compile(r"^[a-zA-Z0-9+=,.@-]{2,64}$")
|