Skip to content

Commit 7ec73c3

Browse files
committed
Migrate examples directory from original project
1 parent 12c3e0a commit 7ec73c3

File tree

5 files changed

+383
-0
lines changed

5 files changed

+383
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Example Signers for aws-sdk-signers
2+
3+
## Requests
4+
We utilize the `AuthBase` construct provided by Requests to apply our signature
5+
to each request. Our `SigV4Auth` class takes two arguments
6+
[`SigV4SigningProperties`](https://github.com/smithy-lang/smithy-python/blob/9c0225b2810b3f68a84aa074e9b4e728a3043721/packages/aws-sdk-signers/src/aws_sdk_signers/signers.py#L44-L50)
7+
and an [`AWSCredentialIdentity`](https://github.com/smithy-lang/smithy-python/blob/9c0225b2810b3f68a84aa074e9b4e728a3043721/packages/aws-sdk-signers/src/aws_sdk_signers/_identity.py#L10-L15).
8+
These will be used across requests as "immutable" input. This is currently an
9+
intentional design decision to work with Requests auth design. We'd love to
10+
hear feedback on how you feel about the current approach, we recommend checking
11+
the AIOHTTP section below for an alternative design.
12+
13+
### Requests Sample
14+
```python
15+
from os import environ
16+
17+
import requests
18+
19+
from examples import requests_signer
20+
from aws_sdk_signers import SigV4SigningProperties, AWSCredentialIdentity
21+
22+
SERVICE="lambda"
23+
REGION="us-west-2"
24+
25+
# A GET request to this URL performs a "ListFunctions" invocation.
26+
# Full API documentation can be found here:
27+
# https://docs.aws.amazon.com/lambda/latest/api/API_ListFunctions.html
28+
URL='https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/'
29+
30+
def get_credentials_from_env():
31+
"""You will need to pull credentials from some source to use the signer.
32+
This will auto-populate an AWSCredentialIdentity when credentials are
33+
available through the env.
34+
35+
You may also consider using another SDK to assume a role or pull
36+
credentials from another source.
37+
"""
38+
return AWSCredentialIdentity(
39+
access_key_id=environ["AWS_ACCESS_KEY_ID"],
40+
secret_access_key=environ["AWS_SECRET_ACCESS_KEY"],
41+
session_token=environ.get("AWS_SESSION_TOKEN"),
42+
)
43+
44+
# Set up our properties and identity
45+
identity = get_credentials_from_env()
46+
properties = SigV4SigningProperties(region=REGION, service=SERVICE)
47+
48+
# Configure the auth class for signing
49+
sigv4_auth = requests_signer.SigV4Auth(properties, identity)
50+
51+
r = requests.get(URL, auth=sigv4_auth)
52+
```
53+
54+
## AIOHTTP
55+
For AIOHTTP, we don't have a concept of a Request object, or option to subclass an
56+
existing auth mechanism. Instead, we'll take parameters you normally pass to a Session
57+
method and use them to generate signing headers before passing them on to AIOHTTP.
58+
59+
This signer will be configured the same way as Requests and provides an Async signing
60+
interface to be used alongside AIOHTTP. This is still a work in progress and will likely
61+
have some amount of iteration to improve performance and ergonomics as we collect feedback.
62+
63+
### AIOHTTP Sample
64+
```python
65+
import asyncio
66+
from collections.abc import AsyncIterable, Mapping
67+
from os import environ
68+
69+
import aiohttp
70+
71+
from examples import aiohttp_signer
72+
from aws_sdk_signers import SigV4SigningProperties, AWSCredentialIdentity
73+
74+
75+
SERVICE="lambda"
76+
REGION="us-west-2"
77+
78+
# A GET request to this URL performs a "ListFunctions" invocation.
79+
# Full API documentation can be found here:
80+
# https://docs.aws.amazon.com/lambda/latest/api/API_ListFunctions.html
81+
URL='https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/'
82+
83+
def get_credentials_from_env():
84+
"""You will need to pull credentials from some source to use the signer.
85+
This will auto-populate an AWSCredentialIdentity when credentials are
86+
available through the env.
87+
88+
You may also consider using another SDK to assume a role or pull
89+
credentials from another source.
90+
"""
91+
return AWSCredentialIdentity(
92+
access_key_id=environ["AWS_ACCESS_KEY_ID"],
93+
secret_access_key=environ["AWS_SECRET_ACCESS_KEY"],
94+
session_token=environ.get("AWS_SESSION_TOKEN"),
95+
)
96+
97+
# Set up our signing_properties and identity
98+
identity = get_credentials_from_env()
99+
properties = SigV4SigningProperties(region=REGION, service=SERVICE)
100+
101+
signer = aiohttp_signer.SigV4Signer(properties, identity)
102+
103+
async def make_request(
104+
method: str,
105+
url: str,
106+
headers: Mapping[str, str],
107+
body: AsyncIterable[bytes] | None,
108+
) -> None:
109+
# For more robust applications, you'll likely want to reuse this session.
110+
async with aiohttp.ClientSession() as session:
111+
signing_headers = await signer.generate_signature(method, url, headers, body)
112+
headers.update(signing_headers)
113+
async with session.request(method, url, headers=headers, data=body) as response:
114+
print("Status:", response.status)
115+
print("Content-Type:", response.headers['content-type'])
116+
117+
body_content = await response.text()
118+
print(body_content)
119+
120+
asyncio.run(make_request("GET", URL, {}, None))
121+
```
122+
123+
## Curl Signer
124+
For curl, we're generating a string to be used in a terminal or invoked subprocess.
125+
This currently only supports known arguments like defining the method, headers,
126+
and a request body. We can expand this to support arbitrary curl arguments in
127+
a future version if there's demand.
128+
129+
### Curl Sample
130+
```python
131+
from examples import curl_signer
132+
from aws_sdk_signers import SigV4SigningProperties, AWSCredentialIdentity
133+
134+
from os import environ
135+
136+
137+
SERVICE="lambda"
138+
REGION="us-west-2"
139+
140+
# A GET request to this URL performs a "ListFunctions" invocation.
141+
# Full API documentation can be found here:
142+
# https://docs.aws.amazon.com/lambda/latest/api/API_ListFunctions.html
143+
URL='https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/'
144+
145+
146+
properties = SigV4SigningProperties(region=REGION, service=SERVICE)
147+
identity = AWSCredentialIdentity(
148+
access_key_id=environ["AWS_ACCESS_KEY_ID"],
149+
secret_access_key=environ["AWS_SECRET_ACCESS_KEY"],
150+
session_token=environ["AWS_SESSION_TOKEN"]
151+
)
152+
153+
# Our curl signer doesn't need state so we
154+
# can call classmethods directly on the signer.
155+
signer = curl_signer.SigV4Curl
156+
curl_cmd = signer.generate_signed_curl_cmd(
157+
properties=properties,
158+
identity=identity,
159+
method="GET",
160+
url=URL,
161+
headers={},
162+
body=None,
163+
)
164+
print(curl_cmd)
165+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Sample signer using aiohttp.
6+
"""
7+
8+
import typing
9+
from collections.abc import Mapping
10+
from urllib.parse import urlparse
11+
12+
from aws_sdk_signers import AsyncSigV4Signer, AWSRequest, Field, Fields, URI
13+
14+
if typing.TYPE_CHECKING:
15+
from aws_sdk_signers import AWSCredentialIdentity, SigV4SigningProperties
16+
17+
SIGNING_HEADERS = (
18+
"Authorization",
19+
"Date",
20+
"X-Amz-Date",
21+
"X-Amz-Security-Token",
22+
"X-Amz-Content-SHA256",
23+
)
24+
25+
26+
class SigV4Signer:
27+
"""Minimal Signer implementation to be used with AIOHTTP."""
28+
29+
def __init__(
30+
self,
31+
properties: "SigV4SigningProperties",
32+
identity: "AWSCredentialIdentity",
33+
):
34+
self._properties = properties
35+
self._identity = identity
36+
self._signer = AsyncSigV4Signer()
37+
38+
async def generate_signature(
39+
self,
40+
method: str,
41+
url: str,
42+
headers: Mapping[str, str],
43+
body: typing.AsyncIterable[bytes] | None,
44+
) -> Mapping[str, str]:
45+
"""Generate signature headers for applying to request."""
46+
url_parts = urlparse(url)
47+
uri = URI(
48+
scheme=url_parts.scheme,
49+
host=url_parts.hostname,
50+
port=url_parts.port,
51+
path=url_parts.path,
52+
query=url_parts.query,
53+
fragment=url_parts.fragment,
54+
)
55+
fields = Fields([Field(name=k, values=[v]) for k, v in headers.items()])
56+
awsrequest = AWSRequest(
57+
destination=uri,
58+
method=method,
59+
body=body,
60+
fields=fields,
61+
)
62+
signed_request = await self._signer.sign(
63+
properties=self._properties,
64+
request=awsrequest,
65+
identity=self._identity,
66+
)
67+
return {
68+
header: signed_request.fields[header].as_string()
69+
for header in SIGNING_HEADERS
70+
if header in signed_request.fields
71+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Sample signer using Requests.
6+
"""
7+
8+
import typing
9+
from collections.abc import Iterable, Mapping
10+
from urllib.parse import urlparse
11+
12+
from aws_sdk_signers import AWSRequest, Field, Fields, SigV4Signer, URI
13+
14+
if typing.TYPE_CHECKING:
15+
from aws_sdk_signers import AWSCredentialIdentity, SigV4SigningProperties
16+
17+
18+
class SigV4Curl:
19+
"""Generates a curl command with a SigV4 signature applied."""
20+
21+
signer = SigV4Signer()
22+
23+
@classmethod
24+
def generate_signed_curl_cmd(
25+
cls,
26+
properties: "SigV4SigningProperties",
27+
identity: "AWSCredentialIdentity",
28+
method: str,
29+
url: str,
30+
headers: Mapping[str, str],
31+
body: Iterable[bytes] | None,
32+
) -> str:
33+
url_parts = urlparse(url)
34+
uri = URI(
35+
scheme=url_parts.scheme,
36+
host=url_parts.hostname,
37+
port=url_parts.port,
38+
path=url_parts.path,
39+
query=url_parts.query,
40+
fragment=url_parts.fragment,
41+
)
42+
fields = Fields([Field(name=k, values=[v]) for k, v in headers.items()])
43+
awsrequest = AWSRequest(
44+
destination=uri,
45+
method=method,
46+
body=body,
47+
fields=fields,
48+
)
49+
signed_request = cls.signer.sign(
50+
properties=properties,
51+
request=awsrequest,
52+
identity=identity,
53+
)
54+
return cls._construct_curl_cmd(request=signed_request)
55+
56+
@classmethod
57+
def _construct_curl_cmd(self, request: AWSRequest) -> str:
58+
cmd_list = ["curl"]
59+
cmd_list.append(f"-X {request.method.upper()}")
60+
for header in request.fields:
61+
cmd_list.append(f'-H "{header.name}: {header.as_string()}"')
62+
if request.body is not None:
63+
# Forcing bytes to a utf-8 string, if we need arbitrary bytes for the
64+
# terminal we should add an option to write to file and use that
65+
# in the command.
66+
cmd_list.append(f"-d {b''.join(list(request.body)).decode()}")
67+
cmd_list.append(request.destination.build())
68+
return " ".join(cmd_list)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Sample signer using Requests.
6+
"""
7+
8+
import typing
9+
from urllib.parse import urlparse
10+
11+
from aws_sdk_signers import AWSRequest, Field, Fields, SigV4Signer, URI
12+
from requests import PreparedRequest
13+
from requests.auth import AuthBase
14+
15+
if typing.TYPE_CHECKING:
16+
from aws_sdk_signers import AWSCredentialIdentity, SigV4SigningProperties
17+
18+
SIGNING_HEADERS = (
19+
"Authorization",
20+
"Date",
21+
"X-Amz-Date",
22+
"X-Amz-Security-Token",
23+
"X-Amz-Content-SHA256",
24+
)
25+
26+
27+
class SigV4Auth(AuthBase):
28+
"""Attaches SigV4Authentication to the given Request object."""
29+
30+
def __init__(
31+
self,
32+
properties: "SigV4SigningProperties",
33+
identity: "AWSCredentialIdentity",
34+
):
35+
self._properties = properties
36+
self._identity = identity
37+
self._signer = SigV4Signer()
38+
39+
def __eq__(self, other):
40+
return self.properties == getattr(other, "properties", None)
41+
42+
def __ne__(self, other):
43+
return not self == other
44+
45+
def __call__(self, r):
46+
self.sign_request(r)
47+
return r
48+
49+
def sign_request(self, r: PreparedRequest):
50+
request = self.convert_to_awsrequest(r)
51+
signed_request = self._signer.sign(
52+
properties=self._properties,
53+
request=request,
54+
identity=self._identity,
55+
)
56+
for header in SIGNING_HEADERS:
57+
if header in signed_request.fields:
58+
r.headers[header] = signed_request.fields[header].as_string()
59+
return r
60+
61+
def convert_to_awsrequest(self, r: PreparedRequest) -> AWSRequest:
62+
url_parts = urlparse(r.url)
63+
uri = URI(
64+
scheme=url_parts.scheme,
65+
host=url_parts.hostname,
66+
port=url_parts.port,
67+
path=url_parts.path,
68+
query=url_parts.query,
69+
fragment=url_parts.fragment,
70+
)
71+
fields = Fields([Field(name=k, values=[v]) for k, v in r.headers.items()])
72+
return AWSRequest(
73+
destination=uri,
74+
method=r.method,
75+
body=r.body,
76+
fields=fields,
77+
)

0 commit comments

Comments
 (0)