boto3-refresh-session 1.1.2__py3-none-any.whl → 1.2.0__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.
@@ -1,6 +1,7 @@
1
+ from .ecs import ECSRefreshableSession
1
2
  from .session import RefreshableSession
2
3
  from .sts import STSRefreshableSession
3
4
 
4
5
  __all__ = ["RefreshableSession"]
5
- __version__ = "1.1.2"
6
+ __version__ = "1.2.0"
6
7
  __author__ = "Mike Letts"
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = ["ECSRefreshableSession"]
4
+
5
+ import os
6
+
7
+ import requests
8
+
9
+ from .exceptions import BRSError
10
+ from .session import BaseRefreshableSession
11
+
12
+ _ECS_CREDENTIALS_RELATIVE_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
13
+ _ECS_CREDENTIALS_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
14
+ _ECS_AUTHORIZATION_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
15
+ _DEFAULT_ENDPOINT_BASE = "http://169.254.170.2"
16
+
17
+
18
+ class ECSRefreshableSession(BaseRefreshableSession, method="ecs"):
19
+ """A boto3 session that automatically refreshes temporary AWS credentials
20
+ from the ECS container credentials metadata endpoint.
21
+
22
+ Parameters
23
+ ----------
24
+ defer_refresh : bool, optional
25
+ If ``True`` then temporary credentials are not automatically refreshed until
26
+ they are explicitly needed. If ``False`` then temporary credentials refresh
27
+ immediately upon expiration. It is highly recommended that you use ``True``.
28
+ Default is ``True``.
29
+
30
+ Other Parameters
31
+ ----------------
32
+ kwargs : dict
33
+ Optional keyword arguments passed to :class:`boto3.session.Session`.
34
+ """
35
+
36
+ def __init__(self, defer_refresh: bool | None = None, **kwargs):
37
+ super().__init__(**kwargs)
38
+
39
+ self._endpoint = self._resolve_endpoint()
40
+ self._headers = self._build_headers()
41
+ self._http = self._init_http_session()
42
+
43
+ self._refresh_using(
44
+ credentials_method=self._get_credentials,
45
+ defer_refresh=defer_refresh is not False,
46
+ refresh_method="ecs-container-metadata",
47
+ )
48
+
49
+ def _resolve_endpoint(self) -> str:
50
+ uri = os.environ.get(_ECS_CREDENTIALS_FULL_URI) or os.environ.get(
51
+ _ECS_CREDENTIALS_RELATIVE_URI
52
+ )
53
+ if not uri:
54
+ raise BRSError(
55
+ "Neither AWS_CONTAINER_CREDENTIALS_FULL_URI nor "
56
+ "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set. "
57
+ "Are you running inside an ECS container?"
58
+ )
59
+ if uri.startswith("http://") or uri.startswith("https://"):
60
+ return uri
61
+ return f"{_DEFAULT_ENDPOINT_BASE}{uri}"
62
+
63
+ def _build_headers(self) -> dict[str, str]:
64
+ token = os.environ.get(_ECS_AUTHORIZATION_TOKEN)
65
+ if token:
66
+ return {"Authorization": f"Bearer {token}"}
67
+ return {}
68
+
69
+ def _init_http_session(self) -> requests.Session:
70
+ session = requests.Session()
71
+ session.headers.update(self._headers)
72
+ return session
73
+
74
+ def _get_credentials(self) -> dict[str, str]:
75
+ try:
76
+ response = self._http.get(self._endpoint, timeout=3)
77
+ response.raise_for_status()
78
+ except requests.RequestException as exc:
79
+ raise BRSError(
80
+ f"Failed to retrieve ECS credentials from {self._endpoint}"
81
+ ) from exc
82
+
83
+ credentials = response.json()
84
+ required = {
85
+ "AccessKeyId",
86
+ "SecretAccessKey",
87
+ "SessionToken",
88
+ "Expiration",
89
+ }
90
+ if not required.issubset(credentials):
91
+ raise BRSError(f"Incomplete credentials received: {credentials}")
92
+ return {
93
+ "access_key": credentials.get("AccessKeyId"),
94
+ "secret_key": credentials.get("SecretAccessKey"),
95
+ "token": credentials.get("SessionToken"),
96
+ "expiry_time": credentials.get("Expiration"), # already ISO8601
97
+ }
98
+
99
+ @staticmethod
100
+ def get_identity() -> dict[str, str]:
101
+ """Returns metadata about ECS.
102
+
103
+ Returns
104
+ -------
105
+ dict[str, str]
106
+ Dict containing metadata about ECS.
107
+ """
108
+
109
+ return {"method": "ecs", "source": "ecs-container-metadata"}
@@ -0,0 +1,38 @@
1
+ class BRSError(Exception):
2
+ """The base exception for boto3-refresh-session.
3
+
4
+ Parameters
5
+ ----------
6
+ message : str, optional
7
+ The message to raise.
8
+ """
9
+
10
+ def __init__(self, message: str | None = None):
11
+ self.message = "" if message is None else message
12
+ super().__init__(self.message)
13
+
14
+ def __str__(self) -> str:
15
+ return self.message
16
+
17
+ def __repr__(self) -> str:
18
+ return f"{self.__class__.__name__}({repr(self.message)})"
19
+
20
+
21
+ class BRSWarning(UserWarning):
22
+ """The base warning for boto3-refresh-session.
23
+
24
+ Parameters
25
+ ----------
26
+ message : str, optional
27
+ The message to raise.
28
+ """
29
+
30
+ def __init__(self, message: str | None = None):
31
+ self.message = "" if message is None else message
32
+ super().__init__(self.message)
33
+
34
+ def __str__(self) -> str:
35
+ return self.message
36
+
37
+ def __repr__(self) -> str:
38
+ return f"{self.__class__.__name__}({repr(self.message)})"
@@ -1,44 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- __doc__ = """
4
- boto3_refresh_session.session
5
- =============================
6
-
7
- This module provides the main interface for constructing refreshable boto3 sessions.
8
-
9
- The ``RefreshableSession`` class serves as a factory that dynamically selects the appropriate
10
- credential refresh strategy based on the ``method`` parameter, e.g., ``sts``.
11
-
12
- Users can interact with AWS services just like they would with a normal :class:`boto3.session.Session`,
13
- with the added benefit of automatic credential refreshing.
14
-
15
- Examples
16
- --------
17
- >>> from boto3_refresh_session import RefreshableSession
18
- >>> session = RefreshableSession(
19
- ... assume_role_kwargs={"RoleArn": "...", "RoleSessionName": "..."},
20
- ... region_name="us-east-1"
21
- ... )
22
- >>> s3 = session.client("s3")
23
- >>> s3.list_buckets()
24
-
25
- .. seealso::
26
- :class:`boto3_refresh_session.sts.STSRefreshableSession`
27
-
28
- Factory interface
29
- -----------------
30
- .. autosummary::
31
- :toctree: generated/
32
- :nosignatures:
33
-
34
- RefreshableSession
35
- """
36
-
37
3
  __all__ = ["RefreshableSession"]
38
4
 
39
5
  from abc import ABC, abstractmethod
40
6
  from typing import Any, Callable, ClassVar, Literal, get_args
41
- from warnings import warn
42
7
 
43
8
  from boto3.session import Session
44
9
  from botocore.credentials import (
@@ -46,9 +11,11 @@ from botocore.credentials import (
46
11
  RefreshableCredentials,
47
12
  )
48
13
 
14
+ from .exceptions import BRSError, BRSWarning
15
+
49
16
  #: Type alias for all currently available credential refresh methods.
50
- Method = Literal["sts"]
51
- RefreshMethod = Literal["sts-assume-role"]
17
+ Method = Literal["sts", "ecs"]
18
+ RefreshMethod = Literal["sts-assume-role", "ecs-container-metadata"]
52
19
 
53
20
 
54
21
  class BaseRefreshableSession(ABC, Session):
@@ -77,7 +44,9 @@ class BaseRefreshableSession(ABC, Session):
77
44
 
78
45
  # guarantees that methods are unique
79
46
  if method in BaseRefreshableSession.registry:
80
- warn(f"Method '{method}' is already registered. Overwriting.")
47
+ BRSWarning(
48
+ f"Method {repr(method)} is already registered. Overwriting."
49
+ )
81
50
 
82
51
  BaseRefreshableSession.registry[method] = cls
83
52
 
@@ -108,6 +77,34 @@ class BaseRefreshableSession(ABC, Session):
108
77
  refresh_using=credentials_method, method=refresh_method
109
78
  )
110
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
+ """
94
+
95
+ creds = self.get_credentials().get_frozen_credentials()
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
+ }
101
+
102
+ @property
103
+ def credentials(self) -> dict[str, str]:
104
+ """The current temporary AWS security credentials."""
105
+
106
+ return self.refreshable_credentials()
107
+
111
108
 
112
109
  class RefreshableSession:
113
110
  """Factory class for constructing refreshable boto3 sessions using various authentication
@@ -134,11 +131,18 @@ class RefreshableSession:
134
131
  See Also
135
132
  --------
136
133
  boto3_refresh_session.sts.STSRefreshableSession
134
+ boto3_refresh_session.ecs.ECSRefreshableSession
137
135
  """
138
136
 
139
137
  def __new__(
140
138
  cls, method: Method = "sts", **kwargs
141
139
  ) -> BaseRefreshableSession:
140
+ if method not in (methods := cls.get_available_methods()):
141
+ raise BRSError(
142
+ f"{repr(method)} is an invalid method parameter. Available methods are "
143
+ f"{', '.join(repr(meth) for meth in methods)}."
144
+ )
145
+
142
146
  obj = BaseRefreshableSession.registry[method]
143
147
  return obj(**kwargs)
144
148
 
@@ -1,49 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- __doc__ = """
4
- boto3_refresh_session.sts
5
- =========================
6
-
7
- Implements the STS-based credential refresh strategy for use with
8
- :class:`boto3_refresh_session.session.RefreshableSession`.
9
-
10
- This module defines the :class:`STSRefreshableSession` class, which uses
11
- IAM role assumption via STS to automatically refresh temporary credentials
12
- in the background.
13
-
14
- .. versionadded:: 1.1.0
15
-
16
- Examples
17
- --------
18
- >>> from boto3_refresh_session import RefreshableSession
19
- >>> session = RefreshableSession(
20
- ... method="sts",
21
- ... assume_role_kwargs={
22
- ... "RoleArn": "arn:aws:iam::123456789012:role/MyRole",
23
- ... "RoleSessionName": "my-session"
24
- ... },
25
- ... region_name="us-east-1"
26
- ... )
27
- >>> s3 = session.client("s3")
28
- >>> s3.list_buckets()
29
-
30
- .. seealso::
31
- :class:`boto3_refresh_session.session.RefreshableSession`
32
-
33
- STS
34
- ---
35
-
36
- .. autosummary::
37
- :toctree: generated/
38
- :nosignatures:
39
-
40
- STSRefreshableSession
41
- """
42
3
  __all__ = ["STSRefreshableSession"]
43
4
 
44
5
  from typing import Any
45
- from warnings import warn
46
6
 
7
+ from .exceptions import BRSWarning
47
8
  from .session import BaseRefreshableSession
48
9
 
49
10
 
@@ -73,7 +34,7 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
73
34
  def __init__(
74
35
  self,
75
36
  assume_role_kwargs: dict,
76
- defer_refresh: bool = None,
37
+ defer_refresh: bool | None = None,
77
38
  sts_client_kwargs: dict | None = None,
78
39
  **kwargs,
79
40
  ):
@@ -84,8 +45,8 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
84
45
  if sts_client_kwargs is not None:
85
46
  # overwriting 'service_name' in case it appears in sts_client_kwargs
86
47
  if "service_name" in sts_client_kwargs:
87
- warn(
88
- "The sts_client_kwargs parameter cannot contain values for service_name. Reverting to service_name = 'sts'."
48
+ BRSWarning(
49
+ "'sts_client_kwargs' cannot contain values for 'service_name'. Reverting to service_name = 'sts'."
89
50
  )
90
51
  del sts_client_kwargs["service_name"]
91
52
  self._sts_client = self.client(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: boto3-refresh-session
3
- Version: 1.1.2
3
+ Version: 1.2.0
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
@@ -56,7 +56,7 @@ Description-Content-Type: text/markdown
56
56
  </a>
57
57
 
58
58
  <a href="https://pepy.tech/project/boto3-refresh-session">
59
- <img src="https://img.shields.io/badge/downloads-58.3K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
59
+ <img src="https://img.shields.io/badge/downloads-59.6K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
60
60
  </a>
61
61
 
62
62
  <a href="https://michaelthomasletts.github.io/boto3-refresh-session/index.html">
@@ -77,6 +77,7 @@ Description-Content-Type: text/markdown
77
77
 
78
78
  - Auto-refreshing credentials for long-lived `boto3` sessions
79
79
  - Drop-in replacement for `boto3.session.Session`
80
+ - Supports automatic refresh methods for STS and ECS
80
81
  - Supports `assume_role` configuration, custom STS clients, and profile / region configuration, as well as all other parameters supported by `boto3.session.Session`
81
82
  - Tested, documented, and published to PyPI
82
83
 
@@ -0,0 +1,10 @@
1
+ boto3_refresh_session/__init__.py,sha256=hJpUIAjfhv-ByWbDCqJfw0sEESVxVnxvZ-aOnVNXu90,200
2
+ boto3_refresh_session/ecs.py,sha256=WIC5mlbcEnM1oo-QXmmtiw2mjFDn01hBfcFh67ku42A,3713
3
+ boto3_refresh_session/exceptions.py,sha256=qcFzdIuK5PZirs77H_Kb64S9QFb6cn2OJtirjvaRLiY,972
4
+ boto3_refresh_session/session.py,sha256=MtaTVdAy-Yx7x_x9SUJ0FlQs0w_8D3kHjvOS8C3bK2E,5226
5
+ boto3_refresh_session/sts.py,sha256=paIgbmn9a3cATNX-6AEGxnSGNZnX1pj4rRQmh8gQSKs,3132
6
+ boto3_refresh_session-1.2.0.dist-info/LICENSE,sha256=I3ZYTXAjbIly6bm6J-TvFTuuHwTKws4h89QaY5c5HiY,1067
7
+ boto3_refresh_session-1.2.0.dist-info/METADATA,sha256=PafbLjHvZ4mZKyVduLho8Szruoq0P4lB_2Nv0aMFTyE,7586
8
+ boto3_refresh_session-1.2.0.dist-info/NOTICE,sha256=1s8r33qbl1z0YvPB942iWgvbkP94P_e8AnROr1qXXuw,939
9
+ boto3_refresh_session-1.2.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
10
+ boto3_refresh_session-1.2.0.dist-info/RECORD,,
@@ -1,44 +0,0 @@
1
- s = """
2
- lbh orybat gb zr,
3
- naq v, lbh.
4
-
5
- jr ner abg jub jr fnl jr ner.
6
- abg jub jr guvax jr ner.
7
- jr ner jub bguref guvax jr ner --
8
- jung bhe npgvbaf fnl jr ner.
9
-
10
- fb znxr jung lbh ohvyq frysyrff.
11
- ornhgvshy. ebohfg. cresrpg.
12
-
13
- zrqvbpevgl yherf hf sebz qvivavgl.
14
- gur pybfre gb cresrpgvba jr nfpraq,
15
- gur zber fgevqrag rivy tebjf --
16
- orpnhfr rivy pnaabg rkvfg
17
- vs nyy vf cresrpg.
18
-
19
- n guvat vf jung vg vf abg.
20
- n guvat jvgu ab bgure vf abguvat.
21
- naq abguvat
22
- vf gur zbfg cresrpg guvat bs nyy.
23
-
24
- ubjrire lbh hfr guvf fbsgjner,
25
- jungrire lbh ohvyq --
26
- znxr vg cresrpg.
27
- qb fb sbe rirelbar ohg lbhefrys.
28
-
29
- znl jr or sbetbggra
30
- naq bhe npgvbaf svanyyl fvyrag.
31
- """
32
-
33
-
34
- def how(text: str, shift: int = 13) -> str:
35
- def rotate(c: str) -> str:
36
- if 'a' <= c <= 'z':
37
- return chr((ord(c) - ord('a') + shift) % 26 + ord('a'))
38
- elif 'A' <= c <= 'Z':
39
- return chr((ord(c) - ord('A') + shift) % 26 + ord('A'))
40
- return c
41
- return ''.join(rotate(c) for c in text)
42
-
43
-
44
- print(how(s.strip()))
@@ -1,9 +0,0 @@
1
- boto3_refresh_session/__init__.py,sha256=WPNU8U79usNSOl7FKoeBQrUhdaX4Yvtpwl8eJbQcTXI,161
2
- boto3_refresh_session/how.py,sha256=zBfxfvQCzqJT8VJ8VTmUUzQUupRkSYXP8yOzvIXruUU,994
3
- boto3_refresh_session/session.py,sha256=J3FW6nkc_s9C0gj1Xs1wKaCWQMzR6S4ncDKDqu1DYxk,4879
4
- boto3_refresh_session/sts.py,sha256=P8lWxQLslXDeYSuvHuy0Le6CC5SIXxQ9D5DZFmwwE1Q,4057
5
- boto3_refresh_session-1.1.2.dist-info/LICENSE,sha256=I3ZYTXAjbIly6bm6J-TvFTuuHwTKws4h89QaY5c5HiY,1067
6
- boto3_refresh_session-1.1.2.dist-info/METADATA,sha256=25RIB9z4wwR1D3Gts08W6tNDkxH-5hW0Hz8hBX9KOYk,7533
7
- boto3_refresh_session-1.1.2.dist-info/NOTICE,sha256=1s8r33qbl1z0YvPB942iWgvbkP94P_e8AnROr1qXXuw,939
8
- boto3_refresh_session-1.1.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
9
- boto3_refresh_session-1.1.2.dist-info/RECORD,,