smithy-aws-core 0.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.
- smithy_aws_core-0.0.1/.gitignore +36 -0
- smithy_aws_core-0.0.1/CHANGES.md +13 -0
- smithy_aws_core-0.0.1/NOTICE +1 -0
- smithy_aws_core-0.0.1/PKG-INFO +15 -0
- smithy_aws_core-0.0.1/README.md +4 -0
- smithy_aws_core-0.0.1/pyproject.toml +23 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/__init__.py +6 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/aio/__init__.py +2 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/aio/protocols.py +28 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/auth/__init__.py +6 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/auth/sigv4.py +54 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/credentials_resolvers/__init__.py +11 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/credentials_resolvers/environment.py +43 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/credentials_resolvers/imds.py +237 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/credentials_resolvers/static.py +20 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/endpoints/__init__.py +15 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/endpoints/standard_regional.py +33 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/identity.py +68 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/interceptors/__init__.py +2 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/interceptors/user_agent.py +72 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/interfaces/__init__.py +0 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/py.typed +1 -0
- smithy_aws_core-0.0.1/src/smithy_aws_core/traits.py +44 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Eclipse
|
|
2
|
+
.classpath
|
|
3
|
+
.project
|
|
4
|
+
.settings/
|
|
5
|
+
|
|
6
|
+
# Intellij
|
|
7
|
+
.idea/
|
|
8
|
+
*.iml
|
|
9
|
+
*.iws
|
|
10
|
+
|
|
11
|
+
# VS Code
|
|
12
|
+
.vscode/
|
|
13
|
+
|
|
14
|
+
# Mac
|
|
15
|
+
.DS_Store
|
|
16
|
+
|
|
17
|
+
# Maven
|
|
18
|
+
target/
|
|
19
|
+
**/dependency-reduced-pom.xml
|
|
20
|
+
|
|
21
|
+
# Gradle
|
|
22
|
+
**/.gradle
|
|
23
|
+
build/
|
|
24
|
+
*/out/
|
|
25
|
+
**/*/out/
|
|
26
|
+
|
|
27
|
+
# Python
|
|
28
|
+
__pycache__
|
|
29
|
+
*.swp
|
|
30
|
+
.mypy_cache
|
|
31
|
+
.coverage
|
|
32
|
+
coverage.xml
|
|
33
|
+
htmlcov
|
|
34
|
+
*.egg-info
|
|
35
|
+
dist
|
|
36
|
+
.venv
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
* <Add new items here>
|
|
6
|
+
|
|
7
|
+
## v0.0.1
|
|
8
|
+
|
|
9
|
+
### Feature
|
|
10
|
+
* Added support for Instance Metadata Service (IMDS) credential resolution.
|
|
11
|
+
* Added basic endpoint support.
|
|
12
|
+
* Added basic User Agent support.
|
|
13
|
+
* Added basic AWS specific protocol support for RestJson1 and HTTP bindings.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smithy-aws-core
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Core Smithy components for AWS services and protocols.
|
|
5
|
+
License-File: NOTICE
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: aws-sdk-signers
|
|
8
|
+
Requires-Dist: smithy-core
|
|
9
|
+
Requires-Dist: smithy-http
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# smithy-aws-core
|
|
13
|
+
|
|
14
|
+
This is the core package that provides AWS specific interfaces for both client and server
|
|
15
|
+
tooling generated by Smithy.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "smithy-aws-core"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Core Smithy components for AWS services and protocols."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"smithy-core",
|
|
9
|
+
"smithy-http",
|
|
10
|
+
"aws-sdk-signers"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["hatchling"]
|
|
15
|
+
build-backend = "hatchling.build"
|
|
16
|
+
|
|
17
|
+
[tool.hatch.build]
|
|
18
|
+
exclude = [
|
|
19
|
+
"tests",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.ruff]
|
|
23
|
+
src = ["src"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
from smithy_core.codecs import Codec
|
|
4
|
+
from smithy_core.shapes import ShapeID
|
|
5
|
+
from smithy_http.aio.protocols import HttpBindingClientProtocol
|
|
6
|
+
from smithy_json import JSONCodec
|
|
7
|
+
|
|
8
|
+
from ..traits import RestJson1Trait
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RestJsonClientProtocol(HttpBindingClientProtocol):
|
|
12
|
+
"""An implementation of the aws.protocols#restJson1 protocol."""
|
|
13
|
+
|
|
14
|
+
_id: ShapeID = RestJson1Trait.id
|
|
15
|
+
_codec: JSONCodec = JSONCodec()
|
|
16
|
+
_contentType: Final = "application/json"
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def id(self) -> ShapeID:
|
|
20
|
+
return self._id
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def payload_codec(self) -> Codec:
|
|
24
|
+
return self._codec
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def content_type(self) -> str:
|
|
28
|
+
return self._contentType
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from aws_sdk_signers import AsyncSigV4Signer, SigV4SigningProperties
|
|
7
|
+
from smithy_core.aio.interfaces.identity import IdentityResolver
|
|
8
|
+
from smithy_core.exceptions import SmithyIdentityException
|
|
9
|
+
from smithy_core.interfaces.identity import IdentityProperties
|
|
10
|
+
from smithy_http.aio.interfaces.auth import HTTPAuthScheme, HTTPSigner
|
|
11
|
+
|
|
12
|
+
from ..identity import AWSCredentialsIdentity
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SigV4Config(Protocol):
|
|
16
|
+
aws_credentials_identity_resolver: (
|
|
17
|
+
IdentityResolver[AWSCredentialsIdentity, IdentityProperties] | None
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(init=False)
|
|
22
|
+
class SigV4AuthScheme(
|
|
23
|
+
HTTPAuthScheme[
|
|
24
|
+
AWSCredentialsIdentity, SigV4Config, IdentityProperties, SigV4SigningProperties
|
|
25
|
+
]
|
|
26
|
+
):
|
|
27
|
+
"""SigV4 AuthScheme."""
|
|
28
|
+
|
|
29
|
+
scheme_id: str = "aws.auth#sigv4"
|
|
30
|
+
signer: HTTPSigner[AWSCredentialsIdentity, SigV4SigningProperties]
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
*,
|
|
35
|
+
signer: HTTPSigner[AWSCredentialsIdentity, SigV4SigningProperties]
|
|
36
|
+
| None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Constructor.
|
|
39
|
+
|
|
40
|
+
:param identity_resolver: The identity resolver to extract the api key identity.
|
|
41
|
+
:param signer: The signer used to sign the request.
|
|
42
|
+
"""
|
|
43
|
+
# TODO: There are type mismatches in the signature of the "sign" method.
|
|
44
|
+
self.signer = signer or AsyncSigV4Signer() # type: ignore
|
|
45
|
+
|
|
46
|
+
def identity_resolver(
|
|
47
|
+
self, *, config: SigV4Config
|
|
48
|
+
) -> IdentityResolver[AWSCredentialsIdentity, IdentityProperties]:
|
|
49
|
+
if not config.aws_credentials_identity_resolver:
|
|
50
|
+
raise SmithyIdentityException(
|
|
51
|
+
"Attempted to use SigV4 auth, but aws_credentials_identity_resolver was not "
|
|
52
|
+
"set on the config."
|
|
53
|
+
)
|
|
54
|
+
return config.aws_credentials_identity_resolver
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from .environment import EnvironmentCredentialsResolver
|
|
4
|
+
from .imds import IMDSCredentialsResolver
|
|
5
|
+
from .static import StaticCredentialsResolver
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"EnvironmentCredentialsResolver",
|
|
9
|
+
"IMDSCredentialsResolver",
|
|
10
|
+
"StaticCredentialsResolver",
|
|
11
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from smithy_core.aio.interfaces.identity import IdentityResolver
|
|
6
|
+
from smithy_core.exceptions import SmithyIdentityException
|
|
7
|
+
from smithy_core.interfaces.identity import IdentityProperties
|
|
8
|
+
|
|
9
|
+
from ..identity import AWSCredentialsIdentity
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EnvironmentCredentialsResolver(
|
|
13
|
+
IdentityResolver[AWSCredentialsIdentity, IdentityProperties]
|
|
14
|
+
):
|
|
15
|
+
"""Resolves AWS Credentials from system environment variables."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._credentials = None
|
|
19
|
+
|
|
20
|
+
async def get_identity(
|
|
21
|
+
self, *, identity_properties: IdentityProperties
|
|
22
|
+
) -> AWSCredentialsIdentity:
|
|
23
|
+
if self._credentials is not None:
|
|
24
|
+
return self._credentials
|
|
25
|
+
|
|
26
|
+
access_key_id = os.getenv("AWS_ACCESS_KEY_ID")
|
|
27
|
+
secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY")
|
|
28
|
+
session_token = os.getenv("AWS_SESSION_TOKEN")
|
|
29
|
+
account_id = os.getenv("AWS_ACCOUNT_ID")
|
|
30
|
+
|
|
31
|
+
if access_key_id is None or secret_access_key is None:
|
|
32
|
+
raise SmithyIdentityException(
|
|
33
|
+
"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self._credentials = AWSCredentialsIdentity(
|
|
37
|
+
access_key_id=access_key_id,
|
|
38
|
+
secret_access_key=secret_access_key,
|
|
39
|
+
session_token=session_token,
|
|
40
|
+
account_id=account_id,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return self._credentials
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import UTC, datetime, timedelta
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from smithy_core import URI
|
|
11
|
+
from smithy_core.aio.interfaces.identity import IdentityResolver
|
|
12
|
+
from smithy_core.exceptions import SmithyIdentityException
|
|
13
|
+
from smithy_core.interfaces.identity import IdentityProperties
|
|
14
|
+
from smithy_core.interfaces.retries import RetryStrategy
|
|
15
|
+
from smithy_core.retries import SimpleRetryStrategy
|
|
16
|
+
from smithy_http import Field, Fields
|
|
17
|
+
from smithy_http.aio import HTTPRequest
|
|
18
|
+
from smithy_http.aio.interfaces import HTTPClient
|
|
19
|
+
|
|
20
|
+
from .. import __version__
|
|
21
|
+
from ..identity import AWSCredentialsIdentity
|
|
22
|
+
|
|
23
|
+
_USER_AGENT_FIELD = Field(
|
|
24
|
+
name="User-Agent",
|
|
25
|
+
values=[f"aws-sdk-python-imds-client/{__version__}"],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(init=False)
|
|
30
|
+
class Config:
|
|
31
|
+
"""Configuration for EC2Metadata."""
|
|
32
|
+
|
|
33
|
+
_HOST_MAPPING = MappingProxyType(
|
|
34
|
+
{"IPv4": "169.254.169.254", "IPv6": "[fd00:ec2::254]"}
|
|
35
|
+
)
|
|
36
|
+
_MIN_TTL = 5
|
|
37
|
+
_MAX_TTL = 21600
|
|
38
|
+
|
|
39
|
+
retry_strategy: RetryStrategy
|
|
40
|
+
endpoint_uri: URI
|
|
41
|
+
endpoint_mode: Literal["IPv4", "IPv6"]
|
|
42
|
+
token_ttl: int
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
retry_strategy: RetryStrategy | None = None,
|
|
48
|
+
endpoint_uri: URI | None = None,
|
|
49
|
+
endpoint_mode: Literal["IPv4", "IPv6"] = "IPv4",
|
|
50
|
+
token_ttl: int = _MAX_TTL,
|
|
51
|
+
ec2_instance_profile_name: str | None = None,
|
|
52
|
+
):
|
|
53
|
+
# TODO: Implement retries.
|
|
54
|
+
self.retry_strategy = retry_strategy or SimpleRetryStrategy(max_attempts=3)
|
|
55
|
+
self.endpoint_mode = endpoint_mode
|
|
56
|
+
self.endpoint_uri = self._resolve_endpoint(endpoint_uri, endpoint_mode)
|
|
57
|
+
self.token_ttl = self._validate_token_ttl(token_ttl)
|
|
58
|
+
self.ec2_instance_profile_name = ec2_instance_profile_name
|
|
59
|
+
|
|
60
|
+
def _validate_token_ttl(self, ttl: int) -> int:
|
|
61
|
+
if not self._MIN_TTL <= ttl <= self._MAX_TTL:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Token TTL must be between {self._MIN_TTL} and {self._MAX_TTL} seconds."
|
|
64
|
+
)
|
|
65
|
+
return ttl
|
|
66
|
+
|
|
67
|
+
def _resolve_endpoint(
|
|
68
|
+
self, endpoint_uri: URI | None, endpoint_mode: Literal["IPv4", "IPv6"]
|
|
69
|
+
) -> URI:
|
|
70
|
+
if endpoint_uri is not None:
|
|
71
|
+
return endpoint_uri
|
|
72
|
+
|
|
73
|
+
return URI(
|
|
74
|
+
scheme="http",
|
|
75
|
+
host=self._HOST_MAPPING.get(endpoint_mode, self._HOST_MAPPING["IPv4"]),
|
|
76
|
+
port=80,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Token:
|
|
81
|
+
"""Represents an IMDSv2 session token with a value and method for checking
|
|
82
|
+
expiration."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, value: str, ttl: int):
|
|
85
|
+
self._value = value
|
|
86
|
+
self._ttl = ttl
|
|
87
|
+
self._created_time = datetime.now()
|
|
88
|
+
|
|
89
|
+
def is_expired(self) -> bool:
|
|
90
|
+
return datetime.now() - self._created_time >= timedelta(seconds=self._ttl)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def value(self) -> str:
|
|
94
|
+
return self._value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TokenCache:
|
|
98
|
+
"""Holds the token needed to fetch instance metadata.
|
|
99
|
+
|
|
100
|
+
In addition, it knows how to refresh itself.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
_TOKEN_PATH = "/latest/api/token" # noqa: S105
|
|
104
|
+
|
|
105
|
+
def __init__(self, http_client: HTTPClient, config: Config):
|
|
106
|
+
self._http_client = http_client
|
|
107
|
+
self._config = config
|
|
108
|
+
self._base_uri = config.endpoint_uri
|
|
109
|
+
self._refresh_lock = asyncio.Lock()
|
|
110
|
+
self._token = None
|
|
111
|
+
|
|
112
|
+
def _should_refresh(self) -> bool:
|
|
113
|
+
return self._token is None or self._token.is_expired()
|
|
114
|
+
|
|
115
|
+
async def _refresh(self) -> None:
|
|
116
|
+
async with self._refresh_lock:
|
|
117
|
+
if not self._should_refresh():
|
|
118
|
+
return
|
|
119
|
+
headers = Fields(
|
|
120
|
+
[
|
|
121
|
+
_USER_AGENT_FIELD,
|
|
122
|
+
Field(
|
|
123
|
+
name="x-aws-ec2-metadata-token-ttl-seconds",
|
|
124
|
+
values=[str(self._config.token_ttl)],
|
|
125
|
+
),
|
|
126
|
+
]
|
|
127
|
+
)
|
|
128
|
+
request = HTTPRequest(
|
|
129
|
+
method="PUT",
|
|
130
|
+
destination=URI(
|
|
131
|
+
scheme=self._base_uri.scheme,
|
|
132
|
+
host=self._base_uri.host,
|
|
133
|
+
port=self._base_uri.port,
|
|
134
|
+
path=self._TOKEN_PATH,
|
|
135
|
+
),
|
|
136
|
+
fields=headers,
|
|
137
|
+
)
|
|
138
|
+
response = await self._http_client.send(request)
|
|
139
|
+
token_value = await response.consume_body_async()
|
|
140
|
+
self._token = Token(token_value.decode("utf-8"), self._config.token_ttl)
|
|
141
|
+
|
|
142
|
+
async def get_token(self) -> Token:
|
|
143
|
+
if self._should_refresh():
|
|
144
|
+
await self._refresh()
|
|
145
|
+
assert self._token is not None # noqa: S101
|
|
146
|
+
return self._token
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class EC2Metadata:
|
|
150
|
+
def __init__(self, http_client: HTTPClient, config: Config | None = None):
|
|
151
|
+
self._http_client = http_client
|
|
152
|
+
self._config = config or Config()
|
|
153
|
+
self._token_cache = TokenCache(
|
|
154
|
+
http_client=self._http_client, config=self._config
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
async def get(self, *, path: str) -> str:
|
|
158
|
+
token = await self._token_cache.get_token()
|
|
159
|
+
headers = Fields(
|
|
160
|
+
[
|
|
161
|
+
_USER_AGENT_FIELD,
|
|
162
|
+
Field(
|
|
163
|
+
name="x-aws-ec2-metadata-token",
|
|
164
|
+
values=[token.value],
|
|
165
|
+
),
|
|
166
|
+
]
|
|
167
|
+
)
|
|
168
|
+
request = HTTPRequest(
|
|
169
|
+
method="GET",
|
|
170
|
+
destination=URI(
|
|
171
|
+
scheme=self._config.endpoint_uri.scheme,
|
|
172
|
+
host=self._config.endpoint_uri.host,
|
|
173
|
+
port=self._config.endpoint_uri.port,
|
|
174
|
+
path=path,
|
|
175
|
+
),
|
|
176
|
+
fields=headers,
|
|
177
|
+
)
|
|
178
|
+
response = await self._http_client.send(request=request)
|
|
179
|
+
body = await response.consume_body_async()
|
|
180
|
+
return body.decode("utf-8")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class IMDSCredentialsResolver(
|
|
184
|
+
IdentityResolver[AWSCredentialsIdentity, IdentityProperties]
|
|
185
|
+
):
|
|
186
|
+
"""Resolves AWS Credentials from an EC2 Instance Metadata Service (IMDS) client."""
|
|
187
|
+
|
|
188
|
+
_METADATA_PATH_BASE = "/latest/meta-data/iam/security-credentials"
|
|
189
|
+
|
|
190
|
+
def __init__(self, http_client: HTTPClient, config: Config | None = None):
|
|
191
|
+
# TODO: Respect IMDS specific config values from aws shared config file and environment.
|
|
192
|
+
self._http_client = http_client
|
|
193
|
+
self._ec2_metadata_client = EC2Metadata(http_client=http_client, config=config)
|
|
194
|
+
self._config = config or Config()
|
|
195
|
+
self._credentials = None
|
|
196
|
+
self._profile_name = self._config.ec2_instance_profile_name
|
|
197
|
+
|
|
198
|
+
async def get_identity(
|
|
199
|
+
self, *, identity_properties: IdentityProperties
|
|
200
|
+
) -> AWSCredentialsIdentity:
|
|
201
|
+
if (
|
|
202
|
+
self._credentials is not None
|
|
203
|
+
and self._credentials.expiration
|
|
204
|
+
and datetime.now(UTC) < self._credentials.expiration
|
|
205
|
+
):
|
|
206
|
+
return self._credentials
|
|
207
|
+
|
|
208
|
+
profile = self._profile_name
|
|
209
|
+
if profile is None:
|
|
210
|
+
profile = await self._ec2_metadata_client.get(path=self._METADATA_PATH_BASE)
|
|
211
|
+
|
|
212
|
+
creds_str = await self._ec2_metadata_client.get(
|
|
213
|
+
path=f"{self._METADATA_PATH_BASE}/{profile}"
|
|
214
|
+
)
|
|
215
|
+
creds = json.loads(creds_str)
|
|
216
|
+
|
|
217
|
+
access_key_id = creds.get("AccessKeyId")
|
|
218
|
+
secret_access_key = creds.get("SecretAccessKey")
|
|
219
|
+
session_token = creds.get("Token")
|
|
220
|
+
account_id = creds.get("AccountId")
|
|
221
|
+
expiration = creds.get("Expiration")
|
|
222
|
+
if expiration is not None:
|
|
223
|
+
expiration = datetime.fromisoformat(expiration).replace(tzinfo=UTC)
|
|
224
|
+
|
|
225
|
+
if access_key_id is None or secret_access_key is None:
|
|
226
|
+
raise SmithyIdentityException(
|
|
227
|
+
"AccessKeyId and SecretAccessKey are required"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
self._credentials = AWSCredentialsIdentity(
|
|
231
|
+
access_key_id=access_key_id,
|
|
232
|
+
secret_access_key=secret_access_key,
|
|
233
|
+
session_token=session_token,
|
|
234
|
+
expiration=expiration,
|
|
235
|
+
account_id=account_id,
|
|
236
|
+
)
|
|
237
|
+
return self._credentials
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from smithy_core.aio.interfaces.identity import IdentityResolver
|
|
4
|
+
from smithy_core.interfaces.identity import IdentityProperties
|
|
5
|
+
|
|
6
|
+
from smithy_aws_core.identity import AWSCredentialsIdentity
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StaticCredentialsResolver(
|
|
10
|
+
IdentityResolver[AWSCredentialsIdentity, IdentityProperties]
|
|
11
|
+
):
|
|
12
|
+
"""Resolve Static AWS Credentials."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, *, credentials: AWSCredentialsIdentity) -> None:
|
|
15
|
+
self._credentials = credentials
|
|
16
|
+
|
|
17
|
+
async def get_identity(
|
|
18
|
+
self, *, identity_properties: IdentityProperties
|
|
19
|
+
) -> AWSCredentialsIdentity:
|
|
20
|
+
return self._credentials
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from smithy_core.endpoints import StaticEndpointConfig
|
|
4
|
+
from smithy_core.types import PropertyKey
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RegionalEndpointConfig(StaticEndpointConfig):
|
|
8
|
+
"""Endpoint config for services with standard regional endpoints."""
|
|
9
|
+
|
|
10
|
+
region: str | None
|
|
11
|
+
"""The AWS region to address the request to."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
REGIONAL_ENDPOINT_CONFIG = PropertyKey(key="config", value_type=RegionalEndpointConfig)
|
|
15
|
+
"""Endpoint config for services with standard regional endpoints."""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from smithy_core import URI
|
|
6
|
+
from smithy_core.aio.interfaces import EndpointResolver
|
|
7
|
+
from smithy_core.endpoints import Endpoint, EndpointResolverParams, resolve_static_uri
|
|
8
|
+
from smithy_core.exceptions import EndpointResolutionError
|
|
9
|
+
|
|
10
|
+
from . import REGIONAL_ENDPOINT_CONFIG
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StandardRegionalEndpointsResolver(EndpointResolver):
|
|
14
|
+
"""Resolves endpoints for services with standard regional endpoints."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, endpoint_prefix: str = "bedrock-runtime"):
|
|
17
|
+
self._endpoint_prefix = endpoint_prefix
|
|
18
|
+
|
|
19
|
+
async def resolve_endpoint(self, params: EndpointResolverParams[Any]) -> Endpoint:
|
|
20
|
+
if (static_uri := resolve_static_uri(params)) is not None:
|
|
21
|
+
return Endpoint(uri=static_uri)
|
|
22
|
+
|
|
23
|
+
region_config = params.context.get(REGIONAL_ENDPOINT_CONFIG)
|
|
24
|
+
if region_config is not None and region_config.region is not None:
|
|
25
|
+
# TODO: use dns suffix determined from partition metadata
|
|
26
|
+
dns_suffix = "amazonaws.com"
|
|
27
|
+
hostname = f"{self._endpoint_prefix}.{region_config.region}.{dns_suffix}"
|
|
28
|
+
|
|
29
|
+
return Endpoint(uri=URI(host=hostname))
|
|
30
|
+
|
|
31
|
+
raise EndpointResolutionError(
|
|
32
|
+
"Unable to resolve endpoint - either endpoint_url or region are required."
|
|
33
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
from smithy_core.aio.interfaces.identity import IdentityResolver
|
|
16
|
+
from smithy_core.identity import Identity
|
|
17
|
+
from smithy_core.interfaces.identity import IdentityProperties
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AWSCredentialsIdentity(Identity):
|
|
21
|
+
"""Container for AWS authentication credentials."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
access_key_id: str,
|
|
27
|
+
secret_access_key: str,
|
|
28
|
+
session_token: str | None = None,
|
|
29
|
+
expiration: datetime | None = None,
|
|
30
|
+
account_id: str | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Initialize the AWSCredentialIdentity.
|
|
33
|
+
|
|
34
|
+
:param access_key_id: A unique identifier for an AWS user or role.
|
|
35
|
+
:param secret_access_key: A secret key used in conjunction with the access key
|
|
36
|
+
ID to authenticate programmatic access to AWS services.
|
|
37
|
+
:param session_token: A temporary token used to specify the current session for
|
|
38
|
+
the supplied credentials.
|
|
39
|
+
:param expiration: The expiration time of the identity. If time zone is
|
|
40
|
+
provided, it is updated to UTC. The value must always be in UTC.
|
|
41
|
+
:param account_id: The AWS account's ID.
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(expiration=expiration)
|
|
44
|
+
self._access_key_id: str = access_key_id
|
|
45
|
+
self._secret_access_key: str = secret_access_key
|
|
46
|
+
self._session_token: str | None = session_token
|
|
47
|
+
self._account_id: str | None = account_id
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def access_key_id(self) -> str:
|
|
51
|
+
return self._access_key_id
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def secret_access_key(self) -> str:
|
|
55
|
+
return self._secret_access_key
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def session_token(self) -> str | None:
|
|
59
|
+
return self._session_token
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def account_id(self) -> str | None:
|
|
63
|
+
return self._account_id
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
type AWSCredentialsResolver = IdentityResolver[
|
|
67
|
+
AWSCredentialsIdentity, IdentityProperties
|
|
68
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
# pyright: reportMissingTypeStubs=false
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import smithy_core
|
|
7
|
+
from smithy_core.interceptors import Interceptor, RequestContext
|
|
8
|
+
from smithy_http.interceptors.user_agent import USER_AGENT
|
|
9
|
+
from smithy_http.user_agent import RawStringUserAgentComponent, UserAgentComponent
|
|
10
|
+
|
|
11
|
+
from .. import __version__
|
|
12
|
+
|
|
13
|
+
_USERAGENT_SDK_NAME = "aws-sdk-python"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UserAgentInterceptor(Interceptor[Any, Any, Any, Any]):
|
|
17
|
+
"""Adds AWS fields to the UserAgent."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
*,
|
|
22
|
+
ua_suffix: str | None,
|
|
23
|
+
ua_app_id: str | None,
|
|
24
|
+
sdk_version: str,
|
|
25
|
+
service_id: str,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Initialize the UserAgentInterceptor.
|
|
28
|
+
|
|
29
|
+
:param ua_suffix: Additional suffix to be added to the UserAgent header.
|
|
30
|
+
:param ua_app_id: User defined and opaque application ID to be added to the
|
|
31
|
+
UserAgent header.
|
|
32
|
+
:param sdk_version: SDK version to be added to the UserAgent header.
|
|
33
|
+
:param service_id: ServiceId to be added to the UserAgent header.
|
|
34
|
+
"""
|
|
35
|
+
super().__init__()
|
|
36
|
+
self._ua_suffix = ua_suffix
|
|
37
|
+
self._ua_app_id = ua_app_id
|
|
38
|
+
self._sdk_version = sdk_version
|
|
39
|
+
self._service_id = service_id
|
|
40
|
+
|
|
41
|
+
def read_after_serialization(self, context: RequestContext[Any, Any]) -> None:
|
|
42
|
+
if USER_AGENT in context.properties:
|
|
43
|
+
user_agent = context.properties[USER_AGENT]
|
|
44
|
+
user_agent.sdk_metadata = self._build_sdk_metadata()
|
|
45
|
+
user_agent.api_metadata.append(
|
|
46
|
+
UserAgentComponent("api", self._service_id, self._sdk_version)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if self._ua_app_id is not None:
|
|
50
|
+
user_agent.additional_metadata.append(
|
|
51
|
+
UserAgentComponent("app", self._ua_app_id)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if self._ua_suffix is not None:
|
|
55
|
+
user_agent.additional_metadata.append(
|
|
56
|
+
RawStringUserAgentComponent(self._ua_suffix)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _build_sdk_metadata(self) -> list[UserAgentComponent]:
|
|
60
|
+
return [
|
|
61
|
+
UserAgentComponent(_USERAGENT_SDK_NAME, __version__),
|
|
62
|
+
UserAgentComponent("md", "smithy-core", smithy_core.__version__),
|
|
63
|
+
*self._crt_version(),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
def _crt_version(self) -> list[UserAgentComponent]:
|
|
67
|
+
try:
|
|
68
|
+
import awscrt
|
|
69
|
+
|
|
70
|
+
return [UserAgentComponent("md", "awscrt", awscrt.__version__)]
|
|
71
|
+
except (ImportError, AttributeError):
|
|
72
|
+
return []
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Marker
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
# This ruff check warns against using the assert statement, which can be stripped out
|
|
5
|
+
# when running Python with certain (common) optimization settings. Assert is used here
|
|
6
|
+
# for trait values. Since these are always generated, we can be fairly confident that
|
|
7
|
+
# they're correct regardless, so it's okay if the checks are stripped out.
|
|
8
|
+
# ruff: noqa: S101
|
|
9
|
+
|
|
10
|
+
from collections.abc import Mapping, Sequence
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
from smithy_core.shapes import ShapeID
|
|
14
|
+
from smithy_core.traits import DocumentValue, DynamicTrait, Trait
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(init=False, frozen=True)
|
|
18
|
+
class RestJson1Trait(Trait, id=ShapeID("aws.protocols#restJson1")):
|
|
19
|
+
http: Sequence[str] = field(
|
|
20
|
+
repr=False, hash=False, compare=False, default_factory=tuple
|
|
21
|
+
)
|
|
22
|
+
event_stream_http: Sequence[str] = field(
|
|
23
|
+
repr=False, hash=False, compare=False, default_factory=tuple
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def __init__(self, value: DocumentValue | DynamicTrait = None):
|
|
27
|
+
super().__init__(value)
|
|
28
|
+
assert isinstance(self.document_value, Mapping)
|
|
29
|
+
|
|
30
|
+
http_versions = self.document_value["http"]
|
|
31
|
+
assert isinstance(http_versions, Sequence)
|
|
32
|
+
for val in http_versions:
|
|
33
|
+
assert isinstance(val, str)
|
|
34
|
+
object.__setattr__(self, "http", tuple(http_versions))
|
|
35
|
+
event_stream_http_versions = self.document_value.get("eventStreamHttp")
|
|
36
|
+
if not event_stream_http_versions:
|
|
37
|
+
object.__setattr__(self, "event_stream_http", self.http)
|
|
38
|
+
else:
|
|
39
|
+
assert isinstance(event_stream_http_versions, Sequence)
|
|
40
|
+
for val in event_stream_http_versions:
|
|
41
|
+
assert isinstance(val, str)
|
|
42
|
+
object.__setattr__(
|
|
43
|
+
self, "event_stream_http", tuple(event_stream_http_versions)
|
|
44
|
+
)
|