boto3-refresh-session 1.3.22__tar.gz → 2.0.1__tar.gz
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.
- {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/PKG-INFO +13 -2
- {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/README.md +11 -1
- {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/boto3_refresh_session/__init__.py +4 -4
- {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/boto3_refresh_session/exceptions.py +2 -2
- boto3_refresh_session-2.0.1/boto3_refresh_session/methods/__init__.py +0 -0
- {boto3_refresh_session-1.3.22/boto3_refresh_session → boto3_refresh_session-2.0.1/boto3_refresh_session/methods}/custom.py +5 -4
- {boto3_refresh_session-1.3.22/boto3_refresh_session → boto3_refresh_session-2.0.1/boto3_refresh_session/methods}/ecs.py +5 -4
- boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/__init__.typed +4 -0
- boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/certificate.typed +54 -0
- boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/cognito.typed +16 -0
- boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/core.typed +50 -0
- {boto3_refresh_session-1.3.22/boto3_refresh_session → boto3_refresh_session-2.0.1/boto3_refresh_session/methods}/sts.py +10 -10
- boto3_refresh_session-2.0.1/boto3_refresh_session/session.py +94 -0
- boto3_refresh_session-2.0.1/boto3_refresh_session/utils.py +212 -0
- {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/pyproject.toml +3 -2
- boto3_refresh_session-1.3.22/boto3_refresh_session/session.py +0 -175
- {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/LICENSE +0 -0
- {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/NOTICE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: boto3-refresh-session
|
3
|
-
Version:
|
3
|
+
Version: 2.0.1
|
4
4
|
Summary: A simple Python package for refreshing the temporary security credentials in a boto3.session.Session object automatically.
|
5
5
|
License: MIT
|
6
6
|
Keywords: boto3,botocore,aws,sts,ecs,credentials,token,refresh
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Requires-Dist: boto3
|
19
19
|
Requires-Dist: botocore
|
20
20
|
Requires-Dist: requests
|
21
|
+
Requires-Dist: typing-extensions
|
21
22
|
Project-URL: Documentation, https://michaelthomasletts.github.io/boto3-refresh-session/index.html
|
22
23
|
Project-URL: Repository, https://github.com/michaelthomasletts/boto3-refresh-session
|
23
24
|
Description-Content-Type: text/markdown
|
@@ -57,7 +58,7 @@ Description-Content-Type: text/markdown
|
|
57
58
|
</a>
|
58
59
|
|
59
60
|
<a href="https://pepy.tech/projects/boto3-refresh-session">
|
60
|
-
<img src="https://img.shields.io/badge/downloads-
|
61
|
+
<img src="https://img.shields.io/badge/downloads-85.0K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
|
61
62
|
</a>
|
62
63
|
|
63
64
|
<a href="https://michaelthomasletts.github.io/boto3-refresh-session/index.html">
|
@@ -83,6 +84,16 @@ Description-Content-Type: text/markdown
|
|
83
84
|
|
84
85
|
</div>
|
85
86
|
|
87
|
+
---
|
88
|
+
|
89
|
+
## ⚠️ Important Update
|
90
|
+
|
91
|
+
I am currently grappling with a serious medical condition that negatively impacts my vision. Accordingly, development of the `iot` and `ec2` modules has been delayed. Expect delayed responses to issues and pull requests until my health stabilizes.
|
92
|
+
|
93
|
+
Thank you for supporting this project.
|
94
|
+
|
95
|
+
---
|
96
|
+
|
86
97
|
## Features
|
87
98
|
|
88
99
|
- Drop-in replacement for `boto3.session.Session`
|
@@ -33,7 +33,7 @@
|
|
33
33
|
</a>
|
34
34
|
|
35
35
|
<a href="https://pepy.tech/projects/boto3-refresh-session">
|
36
|
-
<img src="https://img.shields.io/badge/downloads-
|
36
|
+
<img src="https://img.shields.io/badge/downloads-85.0K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
|
37
37
|
</a>
|
38
38
|
|
39
39
|
<a href="https://michaelthomasletts.github.io/boto3-refresh-session/index.html">
|
@@ -59,6 +59,16 @@
|
|
59
59
|
|
60
60
|
</div>
|
61
61
|
|
62
|
+
---
|
63
|
+
|
64
|
+
## ⚠️ Important Update
|
65
|
+
|
66
|
+
I am currently grappling with a serious medical condition that negatively impacts my vision. Accordingly, development of the `iot` and `ec2` modules has been delayed. Expect delayed responses to issues and pull requests until my health stabilizes.
|
67
|
+
|
68
|
+
Thank you for supporting this project.
|
69
|
+
|
70
|
+
---
|
71
|
+
|
62
72
|
## Features
|
63
73
|
|
64
74
|
- Drop-in replacement for `boto3.session.Session`
|
{boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/boto3_refresh_session/__init__.py
RENAMED
@@ -1,10 +1,10 @@
|
|
1
|
-
from .custom import CustomRefreshableSession
|
2
|
-
from .ecs import ECSRefreshableSession
|
1
|
+
from .methods.custom import CustomRefreshableSession
|
2
|
+
from .methods.ecs import ECSRefreshableSession
|
3
|
+
from .methods.sts import STSRefreshableSession
|
3
4
|
from .session import RefreshableSession
|
4
|
-
from .sts import STSRefreshableSession
|
5
5
|
|
6
6
|
__all__ = ["RefreshableSession"]
|
7
|
-
__version__ = "
|
7
|
+
__version__ = "2.0.1"
|
8
8
|
__title__ = "boto3-refresh-session"
|
9
9
|
__author__ = "Mike Letts"
|
10
10
|
__maintainer__ = "Mike Letts"
|
{boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/boto3_refresh_session/exceptions.py
RENAMED
@@ -15,7 +15,7 @@ class BRSError(Exception):
|
|
15
15
|
return self.message
|
16
16
|
|
17
17
|
def __repr__(self) -> str:
|
18
|
-
return f"{self.__class__.__name__}({
|
18
|
+
return f"{self.__class__.__name__}({self.message!r})"
|
19
19
|
|
20
20
|
|
21
21
|
class BRSWarning(UserWarning):
|
@@ -35,4 +35,4 @@ class BRSWarning(UserWarning):
|
|
35
35
|
return self.message
|
36
36
|
|
37
37
|
def __repr__(self) -> str:
|
38
|
-
return f"{self.__class__.__name__}({
|
38
|
+
return f"{self.__class__.__name__}({self.message!r})"
|
File without changes
|
@@ -4,11 +4,12 @@ __all__ = ["CustomRefreshableSession"]
|
|
4
4
|
|
5
5
|
from typing import Any, Callable
|
6
6
|
|
7
|
-
from
|
8
|
-
from
|
7
|
+
from ..exceptions import BRSError
|
8
|
+
from ..session import BaseRefreshableSession
|
9
|
+
from ..utils import TemporaryCredentials
|
9
10
|
|
10
11
|
|
11
|
-
class CustomRefreshableSession(BaseRefreshableSession,
|
12
|
+
class CustomRefreshableSession(BaseRefreshableSession, registry_key="custom"):
|
12
13
|
"""A :class:`boto3.session.Session` object that automatically refreshes
|
13
14
|
temporary credentials returned by a custom credential getter provided
|
14
15
|
by the user. Useful for users with highly sophisticated or idiosyncratic
|
@@ -74,7 +75,7 @@ class CustomRefreshableSession(BaseRefreshableSession, method="custom"):
|
|
74
75
|
else {}
|
75
76
|
)
|
76
77
|
|
77
|
-
self.
|
78
|
+
self.initialize(
|
78
79
|
credentials_method=self._get_credentials,
|
79
80
|
defer_refresh=defer_refresh is not False,
|
80
81
|
refresh_method="custom",
|
@@ -6,8 +6,9 @@ import os
|
|
6
6
|
|
7
7
|
import requests
|
8
8
|
|
9
|
-
from
|
10
|
-
from
|
9
|
+
from ..exceptions import BRSError
|
10
|
+
from ..session import BaseRefreshableSession
|
11
|
+
from ..utils import TemporaryCredentials
|
11
12
|
|
12
13
|
_ECS_CREDENTIALS_RELATIVE_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
13
14
|
_ECS_CREDENTIALS_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
|
@@ -15,7 +16,7 @@ _ECS_AUTHORIZATION_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
|
|
15
16
|
_DEFAULT_ENDPOINT_BASE = "http://169.254.170.2"
|
16
17
|
|
17
18
|
|
18
|
-
class ECSRefreshableSession(BaseRefreshableSession,
|
19
|
+
class ECSRefreshableSession(BaseRefreshableSession, registry_key="ecs"):
|
19
20
|
"""A boto3 session that automatically refreshes temporary AWS credentials
|
20
21
|
from the ECS container credentials metadata endpoint.
|
21
22
|
|
@@ -40,7 +41,7 @@ class ECSRefreshableSession(BaseRefreshableSession, method="ecs"):
|
|
40
41
|
self._headers = self._build_headers()
|
41
42
|
self._http = self._init_http_session()
|
42
43
|
|
43
|
-
self.
|
44
|
+
self.initialize(
|
44
45
|
credentials_method=self._get_credentials,
|
45
46
|
defer_refresh=defer_refresh is not False,
|
46
47
|
refresh_method="ecs-container-metadata",
|
@@ -0,0 +1,54 @@
|
|
1
|
+
__all__ = ["IoTCertificateRefreshableSession"]
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from ...exceptions import BRSError
|
7
|
+
from ...utils import PKCS11, TemporaryCredentials
|
8
|
+
from .core import BaseIoTRefreshableSession
|
9
|
+
|
10
|
+
|
11
|
+
class IoTCertificateRefreshableSession(
|
12
|
+
BaseIoTRefreshableSession, registry_key="certificate"
|
13
|
+
):
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
endpoint: str,
|
17
|
+
role_alias: str,
|
18
|
+
thing_name: str,
|
19
|
+
certificate: str | bytes,
|
20
|
+
private_key: str | bytes | None = None,
|
21
|
+
pkcs11: PKCS11 | None = None,
|
22
|
+
ca: bytes | None = None,
|
23
|
+
verify_peer: bool = True,
|
24
|
+
):
|
25
|
+
self.endpoint = endpoint
|
26
|
+
self.role_alias = role_alias
|
27
|
+
self.thing_name = thing_name
|
28
|
+
self.certificate = certificate
|
29
|
+
self.private_key = private_key
|
30
|
+
self.pkcs11 = pkcs11
|
31
|
+
self.ca = ca
|
32
|
+
self.verify_peer = verify_peer
|
33
|
+
|
34
|
+
if self.certificate and isinstance(self.certificate, str):
|
35
|
+
with open(Path(self.certificate), "rb") as cert_pem_file:
|
36
|
+
self.certificate = cert_pem_file.read()
|
37
|
+
|
38
|
+
if self.private_key is None and self.pkcs11 is None:
|
39
|
+
raise BRSError(
|
40
|
+
"Either 'private_key' or 'pkcs11' must be provided."
|
41
|
+
)
|
42
|
+
|
43
|
+
if self.private_key is not None and self.pkcs11 is not None:
|
44
|
+
raise BRSError(
|
45
|
+
"Only one of 'private_key' or 'pkcs11' can be provided."
|
46
|
+
)
|
47
|
+
|
48
|
+
if self.private_key and isinstance(self.private_key, str):
|
49
|
+
with open(Path(self.private_key), "rb") as private_key_pem_file:
|
50
|
+
self.private_key = private_key_pem_file.read()
|
51
|
+
|
52
|
+
def _get_credentials(self) -> TemporaryCredentials: ...
|
53
|
+
|
54
|
+
def get_identity(self) -> dict[str, Any]: ...
|
@@ -0,0 +1,16 @@
|
|
1
|
+
__all__ = ["IoTCognitoRefreshableSession"]
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from ...utils import TemporaryCredentials
|
6
|
+
from .core import BaseIoTRefreshableSession
|
7
|
+
|
8
|
+
|
9
|
+
class IoTCognitoRefreshableSession(
|
10
|
+
BaseIoTRefreshableSession, registry_key="cognito"
|
11
|
+
):
|
12
|
+
def __init__(self): ...
|
13
|
+
|
14
|
+
def _get_credentials(self) -> TemporaryCredentials: ...
|
15
|
+
|
16
|
+
def get_identity(self) -> dict[str, Any]: ...
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
__all__ = ["IoTRefreshableSession"]
|
4
|
+
|
5
|
+
from typing import get_args
|
6
|
+
|
7
|
+
from ...exceptions import BRSError
|
8
|
+
from ...session import BaseRefreshableSession
|
9
|
+
from ...utils import (
|
10
|
+
IoTAuthenticationMethod,
|
11
|
+
BRSSession,
|
12
|
+
CredentialProvider,
|
13
|
+
Registry,
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class BaseIoTRefreshableSession(
|
18
|
+
Registry[IoTAuthenticationMethod],
|
19
|
+
CredentialProvider,
|
20
|
+
BRSSession,
|
21
|
+
registry_key="__iot_sentinel__",
|
22
|
+
):
|
23
|
+
def __init__(self, **kwargs):
|
24
|
+
super().__init__(**kwargs)
|
25
|
+
|
26
|
+
|
27
|
+
class IoTRefreshableSession(BaseRefreshableSession, registry_key="iot"):
|
28
|
+
def __new__(
|
29
|
+
cls,
|
30
|
+
authentication_method: IoTAuthenticationMethod = "certificate",
|
31
|
+
**kwargs,
|
32
|
+
) -> BaseIoTRefreshableSession:
|
33
|
+
if authentication_method not in (
|
34
|
+
methods := cls.get_available_authentication_methods()
|
35
|
+
):
|
36
|
+
raise BRSError(
|
37
|
+
f"{authentication_method!r} is an invalid authentication "
|
38
|
+
"method parameter. Available authentication methods are "
|
39
|
+
f"{', '.join(repr(meth) for meth in methods)}."
|
40
|
+
)
|
41
|
+
|
42
|
+
return BaseIoTRefreshableSession.registry[authentication_method](
|
43
|
+
**kwargs
|
44
|
+
)
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def get_available_authentication_methods(cls) -> list[str]:
|
48
|
+
args = list(get_args(IoTAuthenticationMethod))
|
49
|
+
args.remove("__iot_sentinel__")
|
50
|
+
return args
|
@@ -4,17 +4,18 @@ __all__ = ["STSRefreshableSession"]
|
|
4
4
|
|
5
5
|
from typing import Any
|
6
6
|
|
7
|
-
from
|
8
|
-
from
|
7
|
+
from ..exceptions import BRSWarning
|
8
|
+
from ..session import BaseRefreshableSession
|
9
|
+
from ..utils import AssumeRoleParams, STSClientParams, TemporaryCredentials
|
9
10
|
|
10
11
|
|
11
|
-
class STSRefreshableSession(BaseRefreshableSession,
|
12
|
+
class STSRefreshableSession(BaseRefreshableSession, registry_key="sts"):
|
12
13
|
"""A :class:`boto3.session.Session` object that automatically refreshes
|
13
14
|
temporary AWS credentials using an IAM role that is assumed via STS.
|
14
15
|
|
15
16
|
Parameters
|
16
17
|
----------
|
17
|
-
assume_role_kwargs :
|
18
|
+
assume_role_kwargs : AssumeRoleParams
|
18
19
|
Required keyword arguments for :meth:`STS.Client.assume_role` (i.e.
|
19
20
|
boto3 STS client).
|
20
21
|
defer_refresh : bool, optional
|
@@ -22,7 +23,7 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
|
|
22
23
|
until they are explicitly needed. If ``False`` then temporary
|
23
24
|
credentials refresh immediately upon expiration. It is highly
|
24
25
|
recommended that you use ``True``. Default is ``True``.
|
25
|
-
sts_client_kwargs :
|
26
|
+
sts_client_kwargs : STSClientParams, optional
|
26
27
|
Optional keyword arguments for the :class:`STS.Client` object. Do not
|
27
28
|
provide values for ``service_name`` as they are unnecessary. Default
|
28
29
|
is None.
|
@@ -36,13 +37,12 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
|
|
36
37
|
|
37
38
|
def __init__(
|
38
39
|
self,
|
39
|
-
assume_role_kwargs:
|
40
|
+
assume_role_kwargs: AssumeRoleParams,
|
40
41
|
defer_refresh: bool | None = None,
|
41
|
-
sts_client_kwargs:
|
42
|
+
sts_client_kwargs: STSClientParams | None = None,
|
42
43
|
**kwargs,
|
43
44
|
):
|
44
45
|
super().__init__(**kwargs)
|
45
|
-
defer_refresh = defer_refresh is not False
|
46
46
|
self.assume_role_kwargs = assume_role_kwargs
|
47
47
|
|
48
48
|
if sts_client_kwargs is not None:
|
@@ -60,9 +60,9 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
|
|
60
60
|
self._sts_client = self.client(service_name="sts")
|
61
61
|
|
62
62
|
# mounting refreshable credentials
|
63
|
-
self.
|
63
|
+
self.initialize(
|
64
64
|
credentials_method=self._get_credentials,
|
65
|
-
defer_refresh=defer_refresh,
|
65
|
+
defer_refresh=defer_refresh is not False,
|
66
66
|
refresh_method="sts-assume-role",
|
67
67
|
)
|
68
68
|
|
@@ -0,0 +1,94 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
__all__ = ["RefreshableSession"]
|
4
|
+
|
5
|
+
from typing import get_args
|
6
|
+
|
7
|
+
from .exceptions import BRSError
|
8
|
+
from .utils import BRSSession, CredentialProvider, Method, Registry
|
9
|
+
|
10
|
+
|
11
|
+
class BaseRefreshableSession(
|
12
|
+
Registry[Method],
|
13
|
+
CredentialProvider,
|
14
|
+
BRSSession,
|
15
|
+
registry_key="__sentinel__",
|
16
|
+
):
|
17
|
+
"""Abstract base class for implementing refreshable AWS sessions.
|
18
|
+
|
19
|
+
Provides a common interface and factory registration mechanism
|
20
|
+
for subclasses that generate temporary credentials using various
|
21
|
+
AWS authentication methods (e.g., STS).
|
22
|
+
|
23
|
+
Subclasses must implement ``_get_credentials()`` and ``get_identity()``.
|
24
|
+
They should also register themselves using the ``method=...`` argument
|
25
|
+
to ``__init_subclass__``.
|
26
|
+
|
27
|
+
Parameters
|
28
|
+
----------
|
29
|
+
registry : dict[str, type[BaseRefreshableSession]]
|
30
|
+
Class-level registry mapping method names to registered session types.
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(self, **kwargs):
|
34
|
+
super().__init__(**kwargs)
|
35
|
+
|
36
|
+
|
37
|
+
class RefreshableSession:
|
38
|
+
"""Factory class for constructing refreshable boto3 sessions using various
|
39
|
+
authentication methods, e.g. STS.
|
40
|
+
|
41
|
+
This class provides a unified interface for creating boto3 sessions whose
|
42
|
+
credentials are automatically refreshed in the background.
|
43
|
+
|
44
|
+
Use ``RefreshableSession(method="...")`` to construct an instance using
|
45
|
+
the desired method.
|
46
|
+
|
47
|
+
For additional information on required parameters, refer to the See Also
|
48
|
+
section below.
|
49
|
+
|
50
|
+
Parameters
|
51
|
+
----------
|
52
|
+
method : Method
|
53
|
+
The authentication and refresh method to use for the session. Must
|
54
|
+
match a registered method name. Default is "sts".
|
55
|
+
|
56
|
+
Other Parameters
|
57
|
+
----------------
|
58
|
+
**kwargs : dict
|
59
|
+
Additional keyword arguments forwarded to the constructor of the
|
60
|
+
selected session class.
|
61
|
+
|
62
|
+
See Also
|
63
|
+
--------
|
64
|
+
boto3_refresh_session.methods.custom.CustomRefreshableSession
|
65
|
+
boto3_refresh_session.methods.sts.STSRefreshableSession
|
66
|
+
boto3_refresh_session.methods.ecs.ECSRefreshableSession
|
67
|
+
"""
|
68
|
+
|
69
|
+
def __new__(
|
70
|
+
cls, method: Method = "sts", **kwargs
|
71
|
+
) -> BaseRefreshableSession:
|
72
|
+
if method not in (methods := cls.get_available_methods()):
|
73
|
+
raise BRSError(
|
74
|
+
f"{method!r} is an invalid method parameter. "
|
75
|
+
"Available methods are "
|
76
|
+
f"{', '.join(repr(meth) for meth in methods)}."
|
77
|
+
)
|
78
|
+
|
79
|
+
return BaseRefreshableSession.registry[method](**kwargs)
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def get_available_methods(cls) -> list[str]:
|
83
|
+
"""Lists all currently available credential refresh methods.
|
84
|
+
|
85
|
+
Returns
|
86
|
+
-------
|
87
|
+
list[str]
|
88
|
+
A list of all currently available credential refresh methods,
|
89
|
+
e.g. 'sts', 'ecs', 'custom'.
|
90
|
+
"""
|
91
|
+
|
92
|
+
args = list(get_args(Method))
|
93
|
+
args.remove("__sentinel__")
|
94
|
+
return args
|
@@ -0,0 +1,212 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import (
|
4
|
+
Any,
|
5
|
+
Callable,
|
6
|
+
ClassVar,
|
7
|
+
Generic,
|
8
|
+
List,
|
9
|
+
Literal,
|
10
|
+
TypedDict,
|
11
|
+
TypeVar,
|
12
|
+
)
|
13
|
+
|
14
|
+
from boto3.session import Session
|
15
|
+
from botocore.credentials import (
|
16
|
+
DeferredRefreshableCredentials,
|
17
|
+
RefreshableCredentials,
|
18
|
+
)
|
19
|
+
|
20
|
+
from .exceptions import BRSWarning
|
21
|
+
|
22
|
+
try:
|
23
|
+
from typing import NotRequired # type: ignore[import]
|
24
|
+
except ImportError:
|
25
|
+
from typing_extensions import NotRequired
|
26
|
+
|
27
|
+
#: Type alias for all currently available IoT authentication methods.
|
28
|
+
IoTAuthenticationMethod = Literal["certificate", "cognito", "__iot_sentinel__"]
|
29
|
+
|
30
|
+
#: Type alias for all currently available credential refresh methods.
|
31
|
+
Method = Literal[
|
32
|
+
"sts",
|
33
|
+
"ecs",
|
34
|
+
"custom",
|
35
|
+
"__sentinel__",
|
36
|
+
] # TODO: Add iot when implemented
|
37
|
+
|
38
|
+
#: Type alias for all refresh method names.
|
39
|
+
RefreshMethod = Literal[
|
40
|
+
"sts-assume-role",
|
41
|
+
"ecs-container-metadata",
|
42
|
+
"custom",
|
43
|
+
] # Add iot-certificate and iot-cognito when iot implemented
|
44
|
+
|
45
|
+
#: Type alias for all currently registered credential refresh methods.
|
46
|
+
RegistryKey = TypeVar("RegistryKey", bound=str)
|
47
|
+
|
48
|
+
|
49
|
+
class Registry(Generic[RegistryKey]):
|
50
|
+
"""Gives any hierarchy a class-level registry."""
|
51
|
+
|
52
|
+
registry: ClassVar[dict[str, type]] = {}
|
53
|
+
|
54
|
+
def __init_subclass__(cls, *, registry_key: RegistryKey, **kwargs: Any):
|
55
|
+
super().__init_subclass__(**kwargs)
|
56
|
+
|
57
|
+
if registry_key in cls.registry:
|
58
|
+
BRSWarning(f"{registry_key!r} already registered. Overwriting.")
|
59
|
+
|
60
|
+
if "sentinel" not in registry_key:
|
61
|
+
cls.registry[registry_key] = cls
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def items(cls) -> dict[str, type]:
|
65
|
+
"""Typed accessor for introspection / debugging."""
|
66
|
+
|
67
|
+
return dict(cls.registry)
|
68
|
+
|
69
|
+
|
70
|
+
class TemporaryCredentials(TypedDict):
|
71
|
+
"""Temporary IAM credentials."""
|
72
|
+
|
73
|
+
access_key: str
|
74
|
+
secret_key: str
|
75
|
+
token: str
|
76
|
+
expiry_time: datetime | str
|
77
|
+
|
78
|
+
|
79
|
+
class RefreshableTemporaryCredentials(TypedDict):
|
80
|
+
"""Refreshable IAM credentials.
|
81
|
+
|
82
|
+
Parameters
|
83
|
+
----------
|
84
|
+
AWS_ACCESS_KEY_ID : str
|
85
|
+
AWS access key identifier.
|
86
|
+
AWS_SECRET_ACCESS_KEY : str
|
87
|
+
AWS secret access key.
|
88
|
+
AWS_SESSION_TOKEN : str
|
89
|
+
AWS session token.
|
90
|
+
"""
|
91
|
+
|
92
|
+
AWS_ACCESS_KEY_ID: str
|
93
|
+
AWS_SECRET_ACCESS_KEY: str
|
94
|
+
AWS_SESSION_TOKEN: str
|
95
|
+
|
96
|
+
|
97
|
+
class CredentialProvider(ABC):
|
98
|
+
"""Defines the abstract surface every refreshable session must expose."""
|
99
|
+
|
100
|
+
@abstractmethod
|
101
|
+
def _get_credentials(self) -> TemporaryCredentials: ...
|
102
|
+
|
103
|
+
@abstractmethod
|
104
|
+
def get_identity(self) -> dict[str, Any]: ...
|
105
|
+
|
106
|
+
|
107
|
+
class BRSSession(Session):
|
108
|
+
"""Wrapper for boto3.session.Session.
|
109
|
+
|
110
|
+
Other Parameters
|
111
|
+
----------------
|
112
|
+
kwargs : Any
|
113
|
+
Optional keyword arguments for initializing boto3.session.Session."""
|
114
|
+
|
115
|
+
def __init__(self, **kwargs):
|
116
|
+
super().__init__(**kwargs)
|
117
|
+
|
118
|
+
def initialize(
|
119
|
+
self,
|
120
|
+
credentials_method: Callable,
|
121
|
+
defer_refresh: bool,
|
122
|
+
refresh_method: RefreshMethod,
|
123
|
+
):
|
124
|
+
# determining how exactly to refresh expired temporary credentials
|
125
|
+
if not defer_refresh:
|
126
|
+
self._credentials = RefreshableCredentials.create_from_metadata(
|
127
|
+
metadata=credentials_method(),
|
128
|
+
refresh_using=credentials_method,
|
129
|
+
method=refresh_method,
|
130
|
+
)
|
131
|
+
else:
|
132
|
+
self._credentials = DeferredRefreshableCredentials(
|
133
|
+
refresh_using=credentials_method, method=refresh_method
|
134
|
+
)
|
135
|
+
|
136
|
+
def refreshable_credentials(self) -> RefreshableTemporaryCredentials:
|
137
|
+
"""The current temporary AWS security credentials.
|
138
|
+
|
139
|
+
Returns
|
140
|
+
-------
|
141
|
+
RefreshableTemporaryCredentials
|
142
|
+
Temporary AWS security credentials containing:
|
143
|
+
AWS_ACCESS_KEY_ID : str
|
144
|
+
AWS access key identifier.
|
145
|
+
AWS_SECRET_ACCESS_KEY : str
|
146
|
+
AWS secret access key.
|
147
|
+
AWS_SESSION_TOKEN : str
|
148
|
+
AWS session token.
|
149
|
+
"""
|
150
|
+
|
151
|
+
creds = self.get_credentials().get_frozen_credentials()
|
152
|
+
return {
|
153
|
+
"AWS_ACCESS_KEY_ID": creds.access_key,
|
154
|
+
"AWS_SECRET_ACCESS_KEY": creds.secret_key,
|
155
|
+
"AWS_SESSION_TOKEN": creds.token,
|
156
|
+
}
|
157
|
+
|
158
|
+
@property
|
159
|
+
def credentials(self) -> RefreshableTemporaryCredentials:
|
160
|
+
"""The current temporary AWS security credentials."""
|
161
|
+
|
162
|
+
return self.refreshable_credentials()
|
163
|
+
|
164
|
+
|
165
|
+
class Tag(TypedDict):
|
166
|
+
Key: str
|
167
|
+
Value: str
|
168
|
+
|
169
|
+
|
170
|
+
class PolicyDescriptorType(TypedDict):
|
171
|
+
arn: str
|
172
|
+
|
173
|
+
|
174
|
+
class ProvidedContext(TypedDict):
|
175
|
+
ProviderArn: str
|
176
|
+
ContextAssertion: str
|
177
|
+
|
178
|
+
|
179
|
+
class AssumeRoleParams(TypedDict):
|
180
|
+
RoleArn: str
|
181
|
+
RoleSessionName: str
|
182
|
+
PolicyArns: NotRequired[List[PolicyDescriptorType]]
|
183
|
+
Policy: NotRequired[str]
|
184
|
+
DurationSeconds: NotRequired[int]
|
185
|
+
ExternalId: NotRequired[str]
|
186
|
+
SerialNumber: NotRequired[str]
|
187
|
+
TokenCode: NotRequired[str]
|
188
|
+
Tags: NotRequired[List[Tag]]
|
189
|
+
TransitiveTagKeys: NotRequired[List[str]]
|
190
|
+
SourceIdentity: NotRequired[str]
|
191
|
+
ProvidedContexts: NotRequired[List[ProvidedContext]]
|
192
|
+
|
193
|
+
|
194
|
+
class STSClientParams(TypedDict):
|
195
|
+
region_name: NotRequired[str]
|
196
|
+
api_version: NotRequired[str]
|
197
|
+
use_ssl: NotRequired[bool]
|
198
|
+
verify: NotRequired[bool | str]
|
199
|
+
endpoint_url: NotRequired[str]
|
200
|
+
aws_access_key_id: NotRequired[str]
|
201
|
+
aws_secret_access_key: NotRequired[str]
|
202
|
+
aws_session_token: NotRequired[str]
|
203
|
+
config: NotRequired[Any]
|
204
|
+
aws_account_id: NotRequired[str]
|
205
|
+
|
206
|
+
|
207
|
+
class PKCS11(TypedDict):
|
208
|
+
pkcs11_loc: str
|
209
|
+
user_pin: NotRequired[str]
|
210
|
+
slot_id: NotRequired[int]
|
211
|
+
token_label: NotRequired[str | None]
|
212
|
+
private_key_label: NotRequired[str | None]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "boto3-refresh-session"
|
3
|
-
version = "
|
3
|
+
version = "2.0.1"
|
4
4
|
description = "A simple Python package for refreshing the temporary security credentials in a boto3.session.Session object automatically."
|
5
5
|
authors = [
|
6
6
|
{name = "Mike Letts",email = "lettsmt@gmail.com"}
|
@@ -8,7 +8,7 @@ authors = [
|
|
8
8
|
license = {text = "MIT"}
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.10"
|
11
|
-
dependencies = ["boto3", "botocore", "requests"]
|
11
|
+
dependencies = ["boto3", "botocore", "requests", "typing-extensions"]
|
12
12
|
keywords = ["boto3", "botocore", "aws", "sts", "ecs", "credentials", "token", "refresh"]
|
13
13
|
maintainers = [
|
14
14
|
{name="Michael Letts", email="lettsmt@gmail.com"},
|
@@ -37,6 +37,7 @@ numpydoc = "^1.8.0"
|
|
37
37
|
tomlkit = "^0.13.2"
|
38
38
|
jinja2 = "^3.1.6"
|
39
39
|
flask = "^3.1.1"
|
40
|
+
pepy-chart = {git = "https://github.com/michaelthomasletts/pepy-chart.git"}
|
40
41
|
|
41
42
|
[tool.black]
|
42
43
|
line-length = 79
|
@@ -1,175 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
__all__ = ["RefreshableSession"]
|
4
|
-
|
5
|
-
from abc import ABC, abstractmethod
|
6
|
-
from datetime import datetime
|
7
|
-
from typing import Any, Callable, ClassVar, Literal, TypedDict, get_args
|
8
|
-
|
9
|
-
from boto3.session import Session
|
10
|
-
from botocore.credentials import (
|
11
|
-
DeferredRefreshableCredentials,
|
12
|
-
RefreshableCredentials,
|
13
|
-
)
|
14
|
-
|
15
|
-
from .exceptions import BRSError, BRSWarning
|
16
|
-
|
17
|
-
#: Type alias for all currently available credential refresh methods.
|
18
|
-
Method = Literal["sts", "ecs", "custom"]
|
19
|
-
RefreshMethod = Literal["sts-assume-role", "ecs-container-metadata", "custom"]
|
20
|
-
|
21
|
-
|
22
|
-
class TemporaryCredentials(TypedDict):
|
23
|
-
"""Temporary IAM credentials."""
|
24
|
-
|
25
|
-
access_key: str
|
26
|
-
secret_key: str
|
27
|
-
token: str
|
28
|
-
expiry_time: datetime | str
|
29
|
-
|
30
|
-
|
31
|
-
class BaseRefreshableSession(ABC, Session):
|
32
|
-
"""Abstract base class for implementing refreshable AWS sessions.
|
33
|
-
|
34
|
-
Provides a common interface and factory registration mechanism
|
35
|
-
for subclasses that generate temporary credentials using various
|
36
|
-
AWS authentication methods (e.g., STS).
|
37
|
-
|
38
|
-
Subclasses must implement ``_get_credentials()`` and ``get_identity()``.
|
39
|
-
They should also register themselves using the ``method=...`` argument
|
40
|
-
to ``__init_subclass__``.
|
41
|
-
|
42
|
-
Parameters
|
43
|
-
----------
|
44
|
-
registry : dict[str, type[BaseRefreshableSession]]
|
45
|
-
Class-level registry mapping method names to registered session types.
|
46
|
-
"""
|
47
|
-
|
48
|
-
# adding this and __init_subclass__ to avoid circular imports
|
49
|
-
# as well as simplify future addition of new methods
|
50
|
-
registry: ClassVar[dict[Method, type[BaseRefreshableSession]]] = {}
|
51
|
-
|
52
|
-
def __init_subclass__(cls, method: Method):
|
53
|
-
super().__init_subclass__()
|
54
|
-
|
55
|
-
# guarantees that methods are unique
|
56
|
-
if method in BaseRefreshableSession.registry:
|
57
|
-
BRSWarning(
|
58
|
-
f"Method {repr(method)} is already registered. Overwriting."
|
59
|
-
)
|
60
|
-
|
61
|
-
BaseRefreshableSession.registry[method] = cls
|
62
|
-
|
63
|
-
def __init__(self, **kwargs):
|
64
|
-
super().__init__(**kwargs)
|
65
|
-
|
66
|
-
@abstractmethod
|
67
|
-
def _get_credentials(self) -> TemporaryCredentials: ...
|
68
|
-
|
69
|
-
@abstractmethod
|
70
|
-
def get_identity(self) -> dict[str, Any]: ...
|
71
|
-
|
72
|
-
def _refresh_using(
|
73
|
-
self,
|
74
|
-
credentials_method: Callable,
|
75
|
-
defer_refresh: bool,
|
76
|
-
refresh_method: RefreshMethod,
|
77
|
-
):
|
78
|
-
# determining how exactly to refresh expired temporary credentials
|
79
|
-
if not defer_refresh:
|
80
|
-
self._credentials = RefreshableCredentials.create_from_metadata(
|
81
|
-
metadata=credentials_method(),
|
82
|
-
refresh_using=credentials_method,
|
83
|
-
method=refresh_method,
|
84
|
-
)
|
85
|
-
else:
|
86
|
-
self._credentials = DeferredRefreshableCredentials(
|
87
|
-
refresh_using=credentials_method, method=refresh_method
|
88
|
-
)
|
89
|
-
|
90
|
-
def refreshable_credentials(self) -> dict[str, str]:
|
91
|
-
"""The current temporary AWS security credentials.
|
92
|
-
|
93
|
-
Returns
|
94
|
-
-------
|
95
|
-
dict[str, str]
|
96
|
-
Temporary AWS security credentials containing:
|
97
|
-
AWS_ACCESS_KEY_ID : str
|
98
|
-
AWS access key identifier.
|
99
|
-
AWS_SECRET_ACCESS_KEY : str
|
100
|
-
AWS secret access key.
|
101
|
-
AWS_SESSION_TOKEN : str
|
102
|
-
AWS session token.
|
103
|
-
"""
|
104
|
-
|
105
|
-
creds = self.get_credentials().get_frozen_credentials()
|
106
|
-
return {
|
107
|
-
"AWS_ACCESS_KEY_ID": creds.access_key,
|
108
|
-
"AWS_SECRET_ACCESS_KEY": creds.secret_key,
|
109
|
-
"AWS_SESSION_TOKEN": creds.token,
|
110
|
-
}
|
111
|
-
|
112
|
-
@property
|
113
|
-
def credentials(self) -> dict[str, str]:
|
114
|
-
"""The current temporary AWS security credentials."""
|
115
|
-
|
116
|
-
return self.refreshable_credentials()
|
117
|
-
|
118
|
-
|
119
|
-
class RefreshableSession:
|
120
|
-
"""Factory class for constructing refreshable boto3 sessions using various
|
121
|
-
authentication methods, e.g. STS.
|
122
|
-
|
123
|
-
This class provides a unified interface for creating boto3 sessions whose
|
124
|
-
credentials are automatically refreshed in the background.
|
125
|
-
|
126
|
-
Use ``RefreshableSession(method="...")`` to construct an instance using
|
127
|
-
the desired method.
|
128
|
-
|
129
|
-
For additional information on required parameters, refer to the See Also
|
130
|
-
section below.
|
131
|
-
|
132
|
-
Parameters
|
133
|
-
----------
|
134
|
-
method : Method
|
135
|
-
The authentication and refresh method to use for the session. Must
|
136
|
-
match a registered method name. Default is "sts".
|
137
|
-
|
138
|
-
Other Parameters
|
139
|
-
----------------
|
140
|
-
**kwargs : dict
|
141
|
-
Additional keyword arguments forwarded to the constructor of the
|
142
|
-
selected session class.
|
143
|
-
|
144
|
-
See Also
|
145
|
-
--------
|
146
|
-
boto3_refresh_session.custom.CustomRefreshableSession
|
147
|
-
boto3_refresh_session.sts.STSRefreshableSession
|
148
|
-
boto3_refresh_session.ecs.ECSRefreshableSession
|
149
|
-
"""
|
150
|
-
|
151
|
-
def __new__(
|
152
|
-
cls, method: Method = "sts", **kwargs
|
153
|
-
) -> BaseRefreshableSession:
|
154
|
-
if method not in (methods := cls.get_available_methods()):
|
155
|
-
raise BRSError(
|
156
|
-
f"{repr(method)} is an invalid method parameter. "
|
157
|
-
"Available methods are "
|
158
|
-
f"{', '.join(repr(meth) for meth in methods)}."
|
159
|
-
)
|
160
|
-
|
161
|
-
obj = BaseRefreshableSession.registry[method]
|
162
|
-
return obj(**kwargs)
|
163
|
-
|
164
|
-
@classmethod
|
165
|
-
def get_available_methods(cls) -> list[str]:
|
166
|
-
"""Lists all currently available credential refresh methods.
|
167
|
-
|
168
|
-
Returns
|
169
|
-
-------
|
170
|
-
list[str]
|
171
|
-
A list of all currently available credential refresh methods,
|
172
|
-
e.g. 'sts'.
|
173
|
-
"""
|
174
|
-
|
175
|
-
return list(get_args(Method))
|
File without changes
|
File without changes
|