From 8751c1aa90b17e959c50ae2ed5a68a6feaf6b896 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sun, 11 Jun 2023 09:02:42 -0400 Subject: [PATCH 01/21] adding sigv4 --- .../utilities/iam/__init__.py | 6 +++ aws_lambda_powertools/utilities/iam/auth.py | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 aws_lambda_powertools/utilities/iam/__init__.py create mode 100644 aws_lambda_powertools/utilities/iam/auth.py diff --git a/aws_lambda_powertools/utilities/iam/__init__.py b/aws_lambda_powertools/utilities/iam/__init__.py new file mode 100644 index 00000000000..77a837e5761 --- /dev/null +++ b/aws_lambda_powertools/utilities/iam/__init__.py @@ -0,0 +1,6 @@ +"""Advanced feature flags utility""" +from .auth import SigV4AuthFactory + +__all__ = [ + "SigV4AuthFactory", +] diff --git a/aws_lambda_powertools/utilities/iam/auth.py b/aws_lambda_powertools/utilities/iam/auth.py new file mode 100644 index 00000000000..7144e6fb651 --- /dev/null +++ b/aws_lambda_powertools/utilities/iam/auth.py @@ -0,0 +1,51 @@ + +from typing import Optional +from botocore.auth import SigV4Auth +from botocore.credentials import Credentials +from boto3 import Session + + +class SigV4AuthFactory: + """ + SigV4 authentication utility + + Args: + region (str): AWS region + service (str): AWS service + access_key (str, optional): AWS access key + secret_key (str, optional): AWS secret key + token (str, optional): AWS session token + + Returns: + SigV4Auth: SigV4Auth instance + + Examples + -------- + **Using default credentials** + >>> from aws_lambda_powertools.utilities.iam import SigV4AuthFactory + >>> auth = SigV4AuthFactory(region="us-east-2", service="vpc-lattice-svcs") + + + + """ + def __init__( + self, + region: str, + service: str, + access_key: Optional[str], + secret_key: Optional[str], + token: Optional[str], + ): + self._region = region + self._service = service + + if access_key and secret_key or token: + self._access_key = access_key + self._secret_key = secret_key + self._credentials = Credentials(access_key=self._access_key, secret_key=self._secret_key, token=token) + + else: + self._credentials = Session().get_credentials() + + def __call__(self): + return SigV4Auth(credentials=self._credentials, service=self._service, region=self._region) \ No newline at end of file From 9930eda7adaadf42faa44950f20d057629dae7f4 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sun, 11 Jun 2023 16:38:21 -0400 Subject: [PATCH 02/21] update --- aws_lambda_powertools/utilities/iam/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/iam/auth.py b/aws_lambda_powertools/utilities/iam/auth.py index 7144e6fb651..02bd6bc02d8 100644 --- a/aws_lambda_powertools/utilities/iam/auth.py +++ b/aws_lambda_powertools/utilities/iam/auth.py @@ -2,7 +2,7 @@ from typing import Optional from botocore.auth import SigV4Auth from botocore.credentials import Credentials -from boto3 import Session +import botocore.session class SigV4AuthFactory: @@ -45,7 +45,7 @@ def __init__( self._credentials = Credentials(access_key=self._access_key, secret_key=self._secret_key, token=token) else: - self._credentials = Session().get_credentials() + self._credentials = botocore.session.Session().get_credentials() def __call__(self): return SigV4Auth(credentials=self._credentials, service=self._service, region=self._region) \ No newline at end of file From 8ba92d9f2a511953284ebb4fda7d66cf57a43c51 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sat, 17 Jun 2023 07:45:32 -0400 Subject: [PATCH 03/21] updating some stuff --- .../utilities/iam/__init__.py | 4 +-- .../utilities/iam/{auth.py => aws_auth.py} | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) rename aws_lambda_powertools/utilities/iam/{auth.py => aws_auth.py} (53%) diff --git a/aws_lambda_powertools/utilities/iam/__init__.py b/aws_lambda_powertools/utilities/iam/__init__.py index 77a837e5761..c4240c3e045 100644 --- a/aws_lambda_powertools/utilities/iam/__init__.py +++ b/aws_lambda_powertools/utilities/iam/__init__.py @@ -1,6 +1,6 @@ """Advanced feature flags utility""" -from .auth import SigV4AuthFactory +from .aws_auth import AwsSignedRequest __all__ = [ - "SigV4AuthFactory", + "AwsSignedRequest", ] diff --git a/aws_lambda_powertools/utilities/iam/auth.py b/aws_lambda_powertools/utilities/iam/aws_auth.py similarity index 53% rename from aws_lambda_powertools/utilities/iam/auth.py rename to aws_lambda_powertools/utilities/iam/aws_auth.py index 02bd6bc02d8..31b0c88b582 100644 --- a/aws_lambda_powertools/utilities/iam/auth.py +++ b/aws_lambda_powertools/utilities/iam/aws_auth.py @@ -1,13 +1,14 @@ from typing import Optional from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials import botocore.session -class SigV4AuthFactory: +class AwsSignedRequest: """ - SigV4 authentication utility + Authenticating Requests (AWS Signature Version 4) Args: region (str): AWS region @@ -24,28 +25,47 @@ class SigV4AuthFactory: **Using default credentials** >>> from aws_lambda_powertools.utilities.iam import SigV4AuthFactory >>> auth = SigV4AuthFactory(region="us-east-2", service="vpc-lattice-svcs") + """ - - """ def __init__( self, - region: str, service: str, + method: str, + url: str, + data: Optional[str], + params: Optional[str], + headers: Optional[str], access_key: Optional[str], secret_key: Optional[str], token: Optional[str], + region: Optional[str], + sign_payload: Optional[bool] = False, ): - self._region = region + self._service = service + self._method = method + self._url = url + self._data = data + self._params = params + self._headers = headers + + if not region: + self._region = botocore.session.Session().get_config_variable("region") + else: + self._region = region if access_key and secret_key or token: self._access_key = access_key self._secret_key = secret_key self._credentials = Credentials(access_key=self._access_key, secret_key=self._secret_key, token=token) - else: self._credentials = botocore.session.Session().get_credentials() def __call__(self): - return SigV4Auth(credentials=self._credentials, service=self._service, region=self._region) \ No newline at end of file + request = AWSRequest(method=self._method, url=self._url, data=self._data, params=self._params, headers=self._headers) + if sign_payload is False: + request.context["payload_signing_enabled"] = False + + signed_request = SigV4Auth(credentials=self._credentials, service_name=self._service, region_name=self._region).add_auth(request) + return signed_request.prepare() From 0399a7807cc6ae26777d55f9369643a9a2622141 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sat, 17 Jun 2023 07:55:20 -0400 Subject: [PATCH 04/21] fixing token --- aws_lambda_powertools/utilities/iam/aws_auth.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/utilities/iam/aws_auth.py b/aws_lambda_powertools/utilities/iam/aws_auth.py index 31b0c88b582..985a702f770 100644 --- a/aws_lambda_powertools/utilities/iam/aws_auth.py +++ b/aws_lambda_powertools/utilities/iam/aws_auth.py @@ -38,8 +38,8 @@ def __init__( headers: Optional[str], access_key: Optional[str], secret_key: Optional[str], - token: Optional[str], region: Optional[str], + token: Optional[str] = None, sign_payload: Optional[bool] = False, ): @@ -55,12 +55,14 @@ def __init__( else: self._region = region - if access_key and secret_key or token: + if access_key and secret_key: self._access_key = access_key self._secret_key = secret_key - self._credentials = Credentials(access_key=self._access_key, secret_key=self._secret_key, token=token) + self._token = token + self._credentials = Credentials(access_key=self._access_key, secret_key=self._secret_key, token=self._token) else: - self._credentials = botocore.session.Session().get_credentials() + credentials = botocore.session.Session().get_credentials() + self._credentials = credentials.get_frozen_credentials() def __call__(self): request = AWSRequest(method=self._method, url=self._url, data=self._data, params=self._params, headers=self._headers) From d41c6fc8a907f7387fe2b34fb40cb460f244d746 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Tue, 1 Aug 2023 11:29:30 -0400 Subject: [PATCH 05/21] couple more changes --- .../utilities/iam/aws_auth.py | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/aws_lambda_powertools/utilities/iam/aws_auth.py b/aws_lambda_powertools/utilities/iam/aws_auth.py index 985a702f770..a27b13e4b0c 100644 --- a/aws_lambda_powertools/utilities/iam/aws_auth.py +++ b/aws_lambda_powertools/utilities/iam/aws_auth.py @@ -1,12 +1,20 @@ from typing import Optional +from enum import Enum from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials import botocore.session -class AwsSignedRequest: +class AWSServicePrefix(Enum): + LATTICE = "vpc-lattice-svcs" + RESTAPI = "execute-api" + HTTPAPI = "apigateway" + APPSYNC = "appsync" + + +class AWSSigV4Auth: """ Authenticating Requests (AWS Signature Version 4) @@ -30,44 +38,48 @@ class AwsSignedRequest: def __init__( self, - service: str, - method: str, url: str, - data: Optional[str], - params: Optional[str], - headers: Optional[str], - access_key: Optional[str], - secret_key: Optional[str], region: Optional[str], + body: Optional[str] = None, + params: Optional[dict] = None, + headers: Optional[dict] = None, + method: Optional[str] = "GET", + service: Enum = AWSServicePrefix.LATTICE, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, token: Optional[str] = None, - sign_payload: Optional[bool] = False, ): - self._service = service - self._method = method - self._url = url - self._data = data - self._params = params - self._headers = headers + self.service = service.value + self.region = region + self.method = method + self.url = url + self.data = body + self.params = params + self.headers = headers - if not region: - self._region = botocore.session.Session().get_config_variable("region") - else: - self._region = region - - if access_key and secret_key: - self._access_key = access_key - self._secret_key = secret_key - self._token = token - self._credentials = Credentials(access_key=self._access_key, secret_key=self._secret_key, token=self._token) + if access_key and secret_key and token: + self.access_key = access_key + self.secret_key = secret_key + self.token = token + self.credentials = Credentials(access_key=self.access_key, secret_key=self.secret_key, token=self.token) else: credentials = botocore.session.Session().get_credentials() - self._credentials = credentials.get_frozen_credentials() + self.credentials = credentials.get_frozen_credentials() - def __call__(self): - request = AWSRequest(method=self._method, url=self._url, data=self._data, params=self._params, headers=self._headers) - if sign_payload is False: - request.context["payload_signing_enabled"] = False + if self.headers is None: + self.headers = {"Content-Type": "application/json"} - signed_request = SigV4Auth(credentials=self._credentials, service_name=self._service, region_name=self._region).add_auth(request) - return signed_request.prepare() + sigv4 = SigV4Auth(credentials=self.credentials, service_name=self.service, region_name=self.region) + + request = AWSRequest(method=self.method, url=self.url, data=self.data, params=self.params, headers=self.headers) + + if self.service == AWSServicePrefix.LATTICE.value: + # payload signing is not supported for vpc-lattice-svcs + request.context["payload_signing_enabled"] = False + + sigv4.add_auth(request) + self.signed_request = request.prepare() + + def __call__(self): + return self.signed_request From 7899e8a5d776c9c74041d1e5f7f953c08dc2aafa Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Tue, 1 Aug 2023 11:31:49 -0400 Subject: [PATCH 06/21] fixing init --- aws_lambda_powertools/utilities/iam/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/iam/__init__.py b/aws_lambda_powertools/utilities/iam/__init__.py index c4240c3e045..c8aa90199fb 100644 --- a/aws_lambda_powertools/utilities/iam/__init__.py +++ b/aws_lambda_powertools/utilities/iam/__init__.py @@ -1,6 +1,7 @@ """Advanced feature flags utility""" -from .aws_auth import AwsSignedRequest +from .aws_auth import AWSServicePrefix, AWSSigV4Auth __all__ = [ - "AwsSignedRequest", + "AWSServicePrefix", + "AWSSigV4Auth", ] From 2539bbf8f5f89ce0f2ccdfc1780b04e2385daaa6 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Tue, 1 Aug 2023 11:41:06 -0400 Subject: [PATCH 07/21] class docs --- aws_lambda_powertools/utilities/iam/aws_auth.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/iam/aws_auth.py b/aws_lambda_powertools/utilities/iam/aws_auth.py index a27b13e4b0c..d4daf01bc89 100644 --- a/aws_lambda_powertools/utilities/iam/aws_auth.py +++ b/aws_lambda_powertools/utilities/iam/aws_auth.py @@ -8,6 +8,15 @@ class AWSServicePrefix(Enum): + """ + AWS Service Prefixes + + URLs: + https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html + + Args: + Enum (_type_): _description_ + """ LATTICE = "vpc-lattice-svcs" RESTAPI = "execute-api" HTTPAPI = "apigateway" @@ -19,8 +28,13 @@ class AWSSigV4Auth: Authenticating Requests (AWS Signature Version 4) Args: + url (str): URL region (str): AWS region - service (str): AWS service + body (str, optional): Request body + params (dict, optional): Request parameters + headers (dict, optional): Request headers + method (str, optional): Request method + service (str, optional): AWS service access_key (str, optional): AWS access key secret_key (str, optional): AWS secret key token (str, optional): AWS session token From ca8837ae787cae93496c3a228e83cafa853e9919 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Tue, 1 Aug 2023 11:51:37 -0400 Subject: [PATCH 08/21] small example fix --- aws_lambda_powertools/utilities/iam/aws_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/iam/aws_auth.py b/aws_lambda_powertools/utilities/iam/aws_auth.py index d4daf01bc89..59893f0f0e1 100644 --- a/aws_lambda_powertools/utilities/iam/aws_auth.py +++ b/aws_lambda_powertools/utilities/iam/aws_auth.py @@ -45,8 +45,8 @@ class AWSSigV4Auth: Examples -------- **Using default credentials** - >>> from aws_lambda_powertools.utilities.iam import SigV4AuthFactory - >>> auth = SigV4AuthFactory(region="us-east-2", service="vpc-lattice-svcs") + >>> from aws_lambda_powertools.utilities.iam import AWSSigV4Auth + >>> auth = AWSSigV4Auth(region="us-east-2", service=AWSServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") """ From cfcec8a23fa358fafba0e4e13d9dd06420cb6681 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Fri, 18 Aug 2023 08:07:04 -0400 Subject: [PATCH 09/21] roughing in some stuff --- .../utilities/{iam => auth}/__init__.py | 2 + .../utilities/auth/aws_auth.py | 236 ++++++++++++++++++ .../utilities/iam/aws_auth.py | 99 -------- 3 files changed, 238 insertions(+), 99 deletions(-) rename aws_lambda_powertools/utilities/{iam => auth}/__init__.py (81%) create mode 100644 aws_lambda_powertools/utilities/auth/aws_auth.py delete mode 100644 aws_lambda_powertools/utilities/iam/aws_auth.py diff --git a/aws_lambda_powertools/utilities/iam/__init__.py b/aws_lambda_powertools/utilities/auth/__init__.py similarity index 81% rename from aws_lambda_powertools/utilities/iam/__init__.py rename to aws_lambda_powertools/utilities/auth/__init__.py index c8aa90199fb..a280c90744e 100644 --- a/aws_lambda_powertools/utilities/iam/__init__.py +++ b/aws_lambda_powertools/utilities/auth/__init__.py @@ -2,6 +2,8 @@ from .aws_auth import AWSServicePrefix, AWSSigV4Auth __all__ = [ + "AuthProvider", "AWSServicePrefix", "AWSSigV4Auth", + "JWTAuth" ] diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py new file mode 100644 index 00000000000..4ea4060c994 --- /dev/null +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -0,0 +1,236 @@ + +from typing import Optional +from enum import Enum +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest +from botocore.credentials import Credentials +import botocore.session +from abc import ABC, abstractmethod + +import json + +import urllib3 +import base64 + + +def _authorization_header(client_id: str, client_secret: str) -> str: + """ + Generates the Authorization header for the request + + Args: + client_id (str): Client ID + client_secret (str): Client Secret + + Returns: + str: Base64 encoded Authorization header + """ + auth_string = f"{client_id}:{client_secret}" + encoded_auth_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8") + return f"Basic {encoded_auth_string}" + +def _get_token(response: dict) -> str: + """ + Gets the token from the response + + Args: + response (dict): Response from the authentication endpoint + + Returns: + str: Token + """ + if "access_token" in response: + return response["access_token"] + elif "id_token" in response: + return response["id_token"] + else: + raise Exception("Unable to get token from response") + +def _request_access_token(auth_endpoint: str, body: dict, headers: dict) -> dict: + """ + Gets the token from the Auth0 authentication endpoint + + Args: + client_id (str): Client ID + client_secret (str): Client Secret + audience (str): Audience + auth_endpoint (str): Auth0 authentication endpoint + + Returns: + str: Token + """ + headers["Content-Type"] = "application/x-www-form-urlencoded" + + http = urllib3.PoolManager() + + if isinstance(body, dict): + json_body = json.dumps(body) + elif isinstance(body, str): + json_body = body + + try: + response = http.request("POST", auth_endpoint, headers=headers, body=json_body) + response = response.json() + return _get_token(response) + except urllib3.exceptions.RequestError as error: + # If there is an error with the request, handle it here + raise error + except urllib3.exceptions.HTTPError as error: + raise error + + + + + +class AWSServicePrefix(Enum): + """ + AWS Service Prefixes - Enumerations of the supported service proxy types + + URLs: + https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html + """ + LATTICE = "vpc-lattice-svcs" + RESTAPI = "execute-api" + HTTPAPI = "apigateway" + APPSYNC = "appsync" + + +class AuthProvider(Enum): + """ + Auth Provider - Enumerations of the supported authentication providers + """ + AUTH0 = "auth0" + COGNITO = "cognito" + OKTA = "okta" + +class AWSSigV4Auth: + """ + Authenticating Requests (AWS Signature Version 4) + + Args: + url (str): URL + region (str): AWS region + body (str, optional): Request body + params (dict, optional): Request parameters + headers (dict, optional): Request headers + method (str, optional): Request method + service (str, optional): AWS service + access_key (str, optional): AWS access key + secret_key (str, optional): AWS secret key + token (str, optional): AWS session token + + Returns: + SigV4Auth: SigV4Auth instance + + Examples + -------- + **Using default credentials** + >>> from aws_lambda_powertools.utilities.iam import AWSSigV4Auth + >>> auth = AWSSigV4Auth(region="us-east-2", service=AWSServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") + """ + + + def __init__( + self, + url: str, + region: Optional[str], + body: Optional[str] = None, + params: Optional[dict] = None, + headers: Optional[dict] = None, + method: Optional[str] = "GET", + service: Enum = AWSServicePrefix.LATTICE, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + token: Optional[str] = None, + ): + + self.service = service.value + self.region = region + self.method = method + self.url = url + self.data = body + self.params = params + self.headers = headers + + if access_key and secret_key and token: + self.access_key = access_key + self.secret_key = secret_key + self.token = token + self.credentials = Credentials(access_key=self.access_key, secret_key=self.secret_key, token=self.token) + else: + credentials = botocore.session.Session().get_credentials() + self.credentials = credentials.get_frozen_credentials() + + if self.headers is None: + self.headers = {"Content-Type": "application/json"} + + sigv4 = SigV4Auth(credentials=self.credentials, service_name=self.service, region_name=self.region) + + request = AWSRequest(method=self.method, url=self.url, data=self.data, params=self.params, headers=self.headers) + + if self.service == AWSServicePrefix.LATTICE.value: + # payload signing is not supported for vpc-lattice-svcs + request.context["payload_signing_enabled"] = False + + sigv4.add_auth(request) + self.signed_request = request.prepare() + + def __call__(self): + return self.signed_request + + + +class JWTAuth: + + def __init__( + self, + client_id: str, + client_secret: str, + auth_endpoint: str, + provider: Enum = AuthProvider.COGNITO, + audience: Optional[str] = None, + scope: Optional[list] = None + ): + + self.client_id = client_id + self.client_secret = client_secret + self.auth_endpoint = auth_endpoint.removesuffix("/") + self.headers = {"Content-Type": "application/x-www-form-urlencoded"} + self.provider = provider + self.audience = audience + self.scope = scope + + self.body = { + "client_id": self.client_id, + "client_secret": self.client_secret, + } + + if self.provider == AuthProvider.COGNITO.value: + + encoded_auth_string = _authorization_header(self.client_id, self.client_secret) + self.headers["Authorization"] = f"Basic {encoded_auth_string}" + self.body["grant_type"] = "client_credentials" + if self.scope: + self.body["scope"] = " ".join(self.scope) + + if self.provider == AuthProvider.AUTH0.value: + + self.body["client_id"] = self.client_id + self.body["client_secret"] = self.client_secret + self.body["grant_type"] = "client_credentials" + self.body["audience"] = self.audience + + if self.provider == AuthProvider.OKTA.value: + + encoded_auth_string = _authorization_header(self.client_id, self.client_secret) + self.headers["Accept"] = "application/json" + self.headers["Authorization"] = f"Basic {encoded_auth_string}" + self.headers["Cache-Control"] = "no-cache" + + self.body["grant_type"] = "client_credentials" + if scope: + self.body["scope"] = " ".join(self.scope) + + + response = _request_access_token(auth_endpoint=self.auth_endpoint, body=self.body, headers=self.headers) + + diff --git a/aws_lambda_powertools/utilities/iam/aws_auth.py b/aws_lambda_powertools/utilities/iam/aws_auth.py deleted file mode 100644 index 59893f0f0e1..00000000000 --- a/aws_lambda_powertools/utilities/iam/aws_auth.py +++ /dev/null @@ -1,99 +0,0 @@ - -from typing import Optional -from enum import Enum -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest -from botocore.credentials import Credentials -import botocore.session - - -class AWSServicePrefix(Enum): - """ - AWS Service Prefixes - - URLs: - https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html - - Args: - Enum (_type_): _description_ - """ - LATTICE = "vpc-lattice-svcs" - RESTAPI = "execute-api" - HTTPAPI = "apigateway" - APPSYNC = "appsync" - - -class AWSSigV4Auth: - """ - Authenticating Requests (AWS Signature Version 4) - - Args: - url (str): URL - region (str): AWS region - body (str, optional): Request body - params (dict, optional): Request parameters - headers (dict, optional): Request headers - method (str, optional): Request method - service (str, optional): AWS service - access_key (str, optional): AWS access key - secret_key (str, optional): AWS secret key - token (str, optional): AWS session token - - Returns: - SigV4Auth: SigV4Auth instance - - Examples - -------- - **Using default credentials** - >>> from aws_lambda_powertools.utilities.iam import AWSSigV4Auth - >>> auth = AWSSigV4Auth(region="us-east-2", service=AWSServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") - """ - - - def __init__( - self, - url: str, - region: Optional[str], - body: Optional[str] = None, - params: Optional[dict] = None, - headers: Optional[dict] = None, - method: Optional[str] = "GET", - service: Enum = AWSServicePrefix.LATTICE, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, - token: Optional[str] = None, - ): - - self.service = service.value - self.region = region - self.method = method - self.url = url - self.data = body - self.params = params - self.headers = headers - - if access_key and secret_key and token: - self.access_key = access_key - self.secret_key = secret_key - self.token = token - self.credentials = Credentials(access_key=self.access_key, secret_key=self.secret_key, token=self.token) - else: - credentials = botocore.session.Session().get_credentials() - self.credentials = credentials.get_frozen_credentials() - - if self.headers is None: - self.headers = {"Content-Type": "application/json"} - - sigv4 = SigV4Auth(credentials=self.credentials, service_name=self.service, region_name=self.region) - - request = AWSRequest(method=self.method, url=self.url, data=self.data, params=self.params, headers=self.headers) - - if self.service == AWSServicePrefix.LATTICE.value: - # payload signing is not supported for vpc-lattice-svcs - request.context["payload_signing_enabled"] = False - - sigv4.add_auth(request) - self.signed_request = request.prepare() - - def __call__(self): - return self.signed_request From a4320f0d63c6e5c84b67a6b06ad779c9b293ac54 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Fri, 18 Aug 2023 08:09:40 -0400 Subject: [PATCH 10/21] missed includes --- aws_lambda_powertools/utilities/auth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/auth/__init__.py b/aws_lambda_powertools/utilities/auth/__init__.py index a280c90744e..8c3e6468f9c 100644 --- a/aws_lambda_powertools/utilities/auth/__init__.py +++ b/aws_lambda_powertools/utilities/auth/__init__.py @@ -1,5 +1,5 @@ """Advanced feature flags utility""" -from .aws_auth import AWSServicePrefix, AWSSigV4Auth +from .aws_auth import AuthProvider, AWSServicePrefix, AWSSigV4Auth, JWTAuth __all__ = [ "AuthProvider", From 08f97bf262779a21d0c8dddc1270531328369072 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 28 Mar 2024 17:52:51 +0000 Subject: [PATCH 11/21] Initial tests --- .../utilities/auth/__init__.py | 8 +-- .../utilities/auth/aws_auth.py | 61 +++++++++---------- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/__init__.py b/aws_lambda_powertools/utilities/auth/__init__.py index 8c3e6468f9c..ea3cb64400c 100644 --- a/aws_lambda_powertools/utilities/auth/__init__.py +++ b/aws_lambda_powertools/utilities/auth/__init__.py @@ -1,9 +1,5 @@ """Advanced feature flags utility""" + from .aws_auth import AuthProvider, AWSServicePrefix, AWSSigV4Auth, JWTAuth -__all__ = [ - "AuthProvider", - "AWSServicePrefix", - "AWSSigV4Auth", - "JWTAuth" -] +__all__ = ["AuthProvider", "AWSServicePrefix", "AWSSigV4Auth", "JWTAuth"] diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 4ea4060c994..fdfb49f9112 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -1,16 +1,15 @@ +from __future__ import annotations -from typing import Optional -from enum import Enum -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest -from botocore.credentials import Credentials -import botocore.session -from abc import ABC, abstractmethod - +import base64 import json +from enum import Enum +from typing import Optional +import botocore.session import urllib3 -import base64 +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest +from botocore.credentials import Credentials, ReadOnlyCredentials def _authorization_header(client_id: str, client_secret: str) -> str: @@ -28,6 +27,7 @@ def _authorization_header(client_id: str, client_secret: str) -> str: encoded_auth_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8") return f"Basic {encoded_auth_string}" + def _get_token(response: dict) -> str: """ Gets the token from the response @@ -45,7 +45,8 @@ def _get_token(response: dict) -> str: else: raise Exception("Unable to get token from response") -def _request_access_token(auth_endpoint: str, body: dict, headers: dict) -> dict: + +def _request_access_token(auth_endpoint: str, body: dict, headers: dict) -> str: """ Gets the token from the Auth0 authentication endpoint @@ -71,14 +72,10 @@ def _request_access_token(auth_endpoint: str, body: dict, headers: dict) -> dict response = http.request("POST", auth_endpoint, headers=headers, body=json_body) response = response.json() return _get_token(response) - except urllib3.exceptions.RequestError as error: + except (urllib3.exceptions.RequestError, urllib3.exceptions.HTTPError) as error: # If there is an error with the request, handle it here - raise error - except urllib3.exceptions.HTTPError as error: - raise error - - - + # REVIEW: CREATE A CUSTOM EXCEPTION FOR THIS + raise Exception(error) class AWSServicePrefix(Enum): @@ -88,6 +85,7 @@ class AWSServicePrefix(Enum): URLs: https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html """ + LATTICE = "vpc-lattice-svcs" RESTAPI = "execute-api" HTTPAPI = "apigateway" @@ -98,10 +96,12 @@ class AuthProvider(Enum): """ Auth Provider - Enumerations of the supported authentication providers """ + AUTH0 = "auth0" COGNITO = "cognito" OKTA = "okta" + class AWSSigV4Auth: """ Authenticating Requests (AWS Signature Version 4) @@ -128,11 +128,10 @@ class AWSSigV4Auth: >>> auth = AWSSigV4Auth(region="us-east-2", service=AWSServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") """ - def __init__( self, url: str, - region: Optional[str], + region: str, body: Optional[str] = None, params: Optional[dict] = None, headers: Optional[dict] = None, @@ -151,6 +150,8 @@ def __init__( self.params = params self.headers = headers + self.credentials: Credentials | ReadOnlyCredentials + if access_key and secret_key and token: self.access_key = access_key self.secret_key = secret_key @@ -178,18 +179,17 @@ def __call__(self): return self.signed_request - class JWTAuth: def __init__( - self, - client_id: str, - client_secret: str, - auth_endpoint: str, - provider: Enum = AuthProvider.COGNITO, - audience: Optional[str] = None, - scope: Optional[list] = None - ): + self, + client_id: str, + client_secret: str, + auth_endpoint: str, + provider: Enum = AuthProvider.COGNITO, + audience: Optional[str] = None, + scope: Optional[list] = None, + ): self.client_id = client_id self.client_secret = client_secret @@ -230,7 +230,4 @@ def __init__( if scope: self.body["scope"] = " ".join(self.scope) - - response = _request_access_token(auth_endpoint=self.auth_endpoint, body=self.body, headers=self.headers) - - + # response = _request_access_token(auth_endpoint=self.auth_endpoint, body=self.body, headers=self.headers) # noqa ERA001 From 935435e9d77ab520386c839435b180ac28de821f Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 28 Mar 2024 18:54:14 +0000 Subject: [PATCH 12/21] Making mypy happy --- mypy.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy.ini b/mypy.ini index 5fcb1533707..756988a5b72 100644 --- a/mypy.ini +++ b/mypy.ini @@ -36,6 +36,9 @@ ignore_missing_imports = True [mypy-botocore.response] ignore_missing_imports = True +[mypy-botocore.*] +ignore_missing_imports = True + [mypy-boto3.dynamodb.conditions] ignore_missing_imports = True From 8aca00920d9b91735c2211fd7492f0b55c739fe7 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 28 Mar 2024 18:54:41 +0000 Subject: [PATCH 13/21] Making mypy happy --- aws_lambda_powertools/utilities/auth/aws_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index fdfb49f9112..d478b65de36 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -75,7 +75,7 @@ def _request_access_token(auth_endpoint: str, body: dict, headers: dict) -> str: except (urllib3.exceptions.RequestError, urllib3.exceptions.HTTPError) as error: # If there is an error with the request, handle it here # REVIEW: CREATE A CUSTOM EXCEPTION FOR THIS - raise Exception(error) + raise Exception(error) from error class AWSServicePrefix(Enum): From 5e3fb49ac528060c09f0ebc3ac5770e8f3069d44 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sun, 28 Apr 2024 10:54:01 -0400 Subject: [PATCH 14/21] removing jwt auth --- .../utilities/auth/aws_auth.py | 61 +------------------ 1 file changed, 2 insertions(+), 59 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index d478b65de36..3e6adca4fdc 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -1,12 +1,10 @@ from __future__ import annotations import base64 -import json from enum import Enum from typing import Optional import botocore.session -import urllib3 from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials, ReadOnlyCredentials @@ -24,60 +22,11 @@ def _authorization_header(client_id: str, client_secret: str) -> str: str: Base64 encoded Authorization header """ auth_string = f"{client_id}:{client_secret}" - encoded_auth_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8") + encoded_auth_bytes = base64.b64encode(auth_string) + encoded_auth_string = encoded_auth_bytes.decode("utf-8") return f"Basic {encoded_auth_string}" -def _get_token(response: dict) -> str: - """ - Gets the token from the response - - Args: - response (dict): Response from the authentication endpoint - - Returns: - str: Token - """ - if "access_token" in response: - return response["access_token"] - elif "id_token" in response: - return response["id_token"] - else: - raise Exception("Unable to get token from response") - - -def _request_access_token(auth_endpoint: str, body: dict, headers: dict) -> str: - """ - Gets the token from the Auth0 authentication endpoint - - Args: - client_id (str): Client ID - client_secret (str): Client Secret - audience (str): Audience - auth_endpoint (str): Auth0 authentication endpoint - - Returns: - str: Token - """ - headers["Content-Type"] = "application/x-www-form-urlencoded" - - http = urllib3.PoolManager() - - if isinstance(body, dict): - json_body = json.dumps(body) - elif isinstance(body, str): - json_body = body - - try: - response = http.request("POST", auth_endpoint, headers=headers, body=json_body) - response = response.json() - return _get_token(response) - except (urllib3.exceptions.RequestError, urllib3.exceptions.HTTPError) as error: - # If there is an error with the request, handle it here - # REVIEW: CREATE A CUSTOM EXCEPTION FOR THIS - raise Exception(error) from error - - class AWSServicePrefix(Enum): """ AWS Service Prefixes - Enumerations of the supported service proxy types @@ -141,7 +90,6 @@ def __init__( secret_key: Optional[str] = None, token: Optional[str] = None, ): - self.service = service.value self.region = region self.method = method @@ -180,7 +128,6 @@ def __call__(self): class JWTAuth: - def __init__( self, client_id: str, @@ -190,7 +137,6 @@ def __init__( audience: Optional[str] = None, scope: Optional[list] = None, ): - self.client_id = client_id self.client_secret = client_secret self.auth_endpoint = auth_endpoint.removesuffix("/") @@ -205,7 +151,6 @@ def __init__( } if self.provider == AuthProvider.COGNITO.value: - encoded_auth_string = _authorization_header(self.client_id, self.client_secret) self.headers["Authorization"] = f"Basic {encoded_auth_string}" self.body["grant_type"] = "client_credentials" @@ -213,14 +158,12 @@ def __init__( self.body["scope"] = " ".join(self.scope) if self.provider == AuthProvider.AUTH0.value: - self.body["client_id"] = self.client_id self.body["client_secret"] = self.client_secret self.body["grant_type"] = "client_credentials" self.body["audience"] = self.audience if self.provider == AuthProvider.OKTA.value: - encoded_auth_string = _authorization_header(self.client_id, self.client_secret) self.headers["Accept"] = "application/json" self.headers["Authorization"] = f"Basic {encoded_auth_string}" From d2ca94ff30611d395ecc10d416ba23d4c517291a Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sun, 28 Apr 2024 10:58:13 -0400 Subject: [PATCH 15/21] removing additional jwt code --- .../utilities/auth/aws_auth.py | 67 ------------------- 1 file changed, 67 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 3e6adca4fdc..2361977c58e 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -1,6 +1,5 @@ from __future__ import annotations -import base64 from enum import Enum from typing import Optional @@ -10,23 +9,6 @@ from botocore.credentials import Credentials, ReadOnlyCredentials -def _authorization_header(client_id: str, client_secret: str) -> str: - """ - Generates the Authorization header for the request - - Args: - client_id (str): Client ID - client_secret (str): Client Secret - - Returns: - str: Base64 encoded Authorization header - """ - auth_string = f"{client_id}:{client_secret}" - encoded_auth_bytes = base64.b64encode(auth_string) - encoded_auth_string = encoded_auth_bytes.decode("utf-8") - return f"Basic {encoded_auth_string}" - - class AWSServicePrefix(Enum): """ AWS Service Prefixes - Enumerations of the supported service proxy types @@ -125,52 +107,3 @@ def __init__( def __call__(self): return self.signed_request - - -class JWTAuth: - def __init__( - self, - client_id: str, - client_secret: str, - auth_endpoint: str, - provider: Enum = AuthProvider.COGNITO, - audience: Optional[str] = None, - scope: Optional[list] = None, - ): - self.client_id = client_id - self.client_secret = client_secret - self.auth_endpoint = auth_endpoint.removesuffix("/") - self.headers = {"Content-Type": "application/x-www-form-urlencoded"} - self.provider = provider - self.audience = audience - self.scope = scope - - self.body = { - "client_id": self.client_id, - "client_secret": self.client_secret, - } - - if self.provider == AuthProvider.COGNITO.value: - encoded_auth_string = _authorization_header(self.client_id, self.client_secret) - self.headers["Authorization"] = f"Basic {encoded_auth_string}" - self.body["grant_type"] = "client_credentials" - if self.scope: - self.body["scope"] = " ".join(self.scope) - - if self.provider == AuthProvider.AUTH0.value: - self.body["client_id"] = self.client_id - self.body["client_secret"] = self.client_secret - self.body["grant_type"] = "client_credentials" - self.body["audience"] = self.audience - - if self.provider == AuthProvider.OKTA.value: - encoded_auth_string = _authorization_header(self.client_id, self.client_secret) - self.headers["Accept"] = "application/json" - self.headers["Authorization"] = f"Basic {encoded_auth_string}" - self.headers["Cache-Control"] = "no-cache" - - self.body["grant_type"] = "client_credentials" - if scope: - self.body["scope"] = " ".join(self.scope) - - # response = _request_access_token(auth_endpoint=self.auth_endpoint, body=self.body, headers=self.headers) # noqa ERA001 From 28ae9a995f30de55eb800bd11b68435bf35fb705 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Wed, 1 May 2024 06:41:32 -0400 Subject: [PATCH 16/21] sigV4a --- .../utilities/auth/aws_auth.py | 80 +++++++++++++++++++ poetry.lock | 66 +++++++++++++-- pyproject.toml | 5 ++ 3 files changed, 143 insertions(+), 8 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 2361977c58e..3fb9ce2aece 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -4,6 +4,7 @@ from typing import Optional import botocore.session +from botocore import crt from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials, ReadOnlyCredentials @@ -21,6 +22,7 @@ class AWSServicePrefix(Enum): RESTAPI = "execute-api" HTTPAPI = "apigateway" APPSYNC = "appsync" + S3 = "s3" class AuthProvider(Enum): @@ -36,6 +38,7 @@ class AuthProvider(Enum): class AWSSigV4Auth: """ Authenticating Requests (AWS Signature Version 4) + Requests that were signed with SigV4 will have SignatureVersion set to AWS4-HMAC-SHA256 Args: url (str): URL @@ -107,3 +110,80 @@ def __init__( def __call__(self): return self.signed_request + + +class AWSSigV4aAuth: + """ + Authenticating Requests (AWS Signature Version 4a) + Requests that were signed with SigV4A will have a SignatureVersion set to AWS4-ECDSA-P256-SHA256 + + Args: + url (str): URL + region (str): AWS region + body (str, optional): Request body + params (dict, optional): Request parameters + headers (dict, optional): Request headers + method (str, optional): Request method + service (str, optional): AWS service + access_key (str, optional): AWS access key + secret_key (str, optional): AWS secret key + token (str, optional): AWS session token + + Returns: + SigV4aAuth: SigV4aAuth instance + + Examples + -------- + **Using default credentials** + >>> from aws_lambda_powertools.utilities.iam import AWSSigV4aAuth + >>> auth = AWSSigV4aAuth(region="us-east-2", service=AWSServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") + """ + + def __init__( + self, + url: str, + region: str, + body: Optional[str] = None, + params: Optional[dict] = None, + headers: Optional[dict] = None, + method: Optional[str] = "GET", + service: Enum = AWSServicePrefix.LATTICE, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + token: Optional[str] = None, + ): + self.service = service.value + self.region = region + self.method = method + self.url = url + self.data = body + self.params = params + self.headers = headers + + self.credentials: Credentials | ReadOnlyCredentials + + if access_key and secret_key and token: + self.access_key = access_key + self.secret_key = secret_key + self.token = token + self.credentials = Credentials(access_key=self.access_key, secret_key=self.secret_key, token=self.token) + else: + credentials = botocore.session.Session().get_credentials() + self.credentials = credentials.get_frozen_credentials() + + if self.headers is None: + self.headers = {"Content-Type": "application/json"} + + signer = crt.auth.CrtSigV4AsymAuth(self.credentials, self.service, self.region) + + request = AWSRequest(method=self.method, url=self.url, data=self.data, params=self.params, headers=self.headers) + + if self.service == AWSServicePrefix.LATTICE.value: + # payload signing is not supported for vpc-lattice-svcs + request.context["payload_signing_enabled"] = False + + signer.add_auth(request) + self.signed_request = request.prepare() + + def __call__(self): + return self.signed_request diff --git a/poetry.lock b/poetry.lock index a7432a42908..a8c1e3ef247 100644 --- a/poetry.lock +++ b/poetry.lock @@ -260,6 +260,57 @@ files = [ botocore = ">=1.11.3" wrapt = "*" +[[package]] +name = "awscrt" +version = "0.20.9" +description = "A common runtime for AWS Python projects" +optional = false +python-versions = ">=3.7" +files = [ + {file = "awscrt-0.20.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf05690c7471a4939329e99d7449dfef3adda2c63169e55170a09545fc7ba41"}, + {file = "awscrt-0.20.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:792aa0cc68b3eae49e0bc31bd749f1c9be04dd7946d9559286478929c000bdf6"}, + {file = "awscrt-0.20.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ebdbd40fc4c9d2a775137691b05a04f2b158e220f3735eb89c807fdc827602d"}, + {file = "awscrt-0.20.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b0470258351a23038603039060ed9cec0d83c55a1af0b604541f7b24142f998a"}, + {file = "awscrt-0.20.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3fec3cf8cca7feeb8610e704ed54b20abd630a0cabefa3b2b08068cc3ebff580"}, + {file = "awscrt-0.20.9-cp310-cp310-win32.whl", hash = "sha256:03db50e930509787f83fb3790600d779976479bcf3eea4c732c4dde9de0f64f2"}, + {file = "awscrt-0.20.9-cp310-cp310-win_amd64.whl", hash = "sha256:fdaab4213d86476e312301c65442fa60836de27647d20f2085fd7ba6493322c6"}, + {file = "awscrt-0.20.9-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3391bd3263f0128174e66b41ec3ec8de516e89bc8359ea1ef3cede7715e729f9"}, + {file = "awscrt-0.20.9-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2a7049c105e91fb13d11aa1be6eda1b16f3551c2098dfbd254579ababd84bb"}, + {file = "awscrt-0.20.9-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bef39e6fbf4a393ea1bb9f868cb2da4b339aa4702cd6a29b5aff2aa0291e438b"}, + {file = "awscrt-0.20.9-cp311-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:159991638b35632a688c109b038812d5bb68da075aba9cb5e641c935e2e355c7"}, + {file = "awscrt-0.20.9-cp311-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a0a47bda8897e5b70e318206929fbacd187766acd40cffcfad2f9c613557d6dc"}, + {file = "awscrt-0.20.9-cp311-abi3-win32.whl", hash = "sha256:a8b5c8da81905b75e89805b6cabbb03983895d5362a12c11cbcd5db64acf373a"}, + {file = "awscrt-0.20.9-cp311-abi3-win_amd64.whl", hash = "sha256:fd5f778f13dd4134d0a79e452700a5ad8a580f75aa2bbc25e6e997b486054d9c"}, + {file = "awscrt-0.20.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:07a7bf8ec73927f33b3ec1294d7d0fb6147a28b3d93f92e9a235e245eed3ce11"}, + {file = "awscrt-0.20.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e71276dd2734ee1c814790fa93694345f68da1fa31ad50fc269211fd17728acc"}, + {file = "awscrt-0.20.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31d79ffae73fd8faa7040603a9f08ce393da58b6bd49cfe97c1f4f4410ed0bb1"}, + {file = "awscrt-0.20.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a758ba8c6fbd5c70b305d182a693eb786d8d4bed74d9ad30d8ea7e7dd086fc4b"}, + {file = "awscrt-0.20.9-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e124944bac9764787041f0c097fe144baf3788ba1907a02335882ce85644e32f"}, + {file = "awscrt-0.20.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:be307411b587762588845b7ee259c8c64078aa49e168809c411329dbe0f63bb1"}, + {file = "awscrt-0.20.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:469cadb8d9da9768e5a4446932ea31d39798a14f6475c0699a6cacf976dd8595"}, + {file = "awscrt-0.20.9-cp37-cp37m-win32.whl", hash = "sha256:03ec77d94ed89c92a78a3310fc4582f87b9fba8a363d021a25a35c5edf22d609"}, + {file = "awscrt-0.20.9-cp37-cp37m-win_amd64.whl", hash = "sha256:09711c83ab0094c80cb73e534026e39b17d76f8c2ed0588825cb5fbf34a8e643"}, + {file = "awscrt-0.20.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:579ad3a97cafd3a8311130913a894a07727cba63de2fd070d59335538bf70754"}, + {file = "awscrt-0.20.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d14b076e6c38ef768ef640be638b9400bc95cc3c49547f54f062f692f33765a"}, + {file = "awscrt-0.20.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:273496b7e958d631ee6fb967817ea6211061abbdaa3222a5b5d33a4282e9e0a0"}, + {file = "awscrt-0.20.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d1631a1dd7e9348635e2b4ba535629ac041587b290ffc6a1017f15834f462066"}, + {file = "awscrt-0.20.9-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d23e4d65451ffa8a36a6fe958aaee792248911b02f37dfc2b7d390c84ea69fb2"}, + {file = "awscrt-0.20.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4a32e399678b03f4f69fb989994287b2b5c6975a2545e56521352424a971add"}, + {file = "awscrt-0.20.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0845bb11e5f0059190646b35d9e73dd0794213e81478f3bdcb0e20f14fa3a72c"}, + {file = "awscrt-0.20.9-cp38-cp38-win32.whl", hash = "sha256:dcc6c295ad7960af091e4cc5964141629d791204ee3e0d8bc1c8fb2357e2c6a6"}, + {file = "awscrt-0.20.9-cp38-cp38-win_amd64.whl", hash = "sha256:be839e864131ffd5e09a93adb89cc27f51784f1f6251d986dff0d2ff2bb82820"}, + {file = "awscrt-0.20.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b9cb4d1c44bd72b8242ff92a96fefdfc159cf561e2e15bb2339919b5dfe41ce0"}, + {file = "awscrt-0.20.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fc18904e097cd5db8d41e835458764056015ee65c4e52542a95574bb3a20018"}, + {file = "awscrt-0.20.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b1c64a16e840a89153422223f6393a362178ed8fdcc7fc6dd3f27d1daf97d0"}, + {file = "awscrt-0.20.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e496208281394ae47277c0e93f9b68712205e05d477d3fd9248e2719c48c2f1"}, + {file = "awscrt-0.20.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3d5ab194cca53c308e8bedd9d706875ce02f0759e63434e27efaee0624b96ee7"}, + {file = "awscrt-0.20.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb63581126b0dacebbf479ad35a70903b6e3b6361266c037a5c9a1ef8bd14d38"}, + {file = "awscrt-0.20.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7bb111b447facb4c7946eb68ebbf929348f102cbea2d6989a59cba361b6cb64f"}, + {file = "awscrt-0.20.9-cp39-cp39-win32.whl", hash = "sha256:8e6fe74262223b70693fde4ea19d3a28ce7583f0afdc98db1f024b58a7a81b27"}, + {file = "awscrt-0.20.9-cp39-cp39-win_amd64.whl", hash = "sha256:16c10f007d37db9166dd47a16f3496c99849c184b0058d2fb4698b8513eca54c"}, + {file = "awscrt-0.20.9.tar.gz", hash = "sha256:243785ac9ee64945e0479c2384325545f29597575743ce84c371556d1014e63e"}, +] + [[package]] name = "babel" version = "2.14.0" @@ -368,13 +419,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.55" +version = "1.34.94" description = "Low-level, data-driven core of boto 3." optional = false -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "botocore-1.34.55-py3-none-any.whl", hash = "sha256:07044c3cbfb86d0ecb9c56d887b8ad63a72eff0e4f6ab329cf335f1fd867ea0b"}, - {file = "botocore-1.34.55.tar.gz", hash = "sha256:bb333e3845bfe65600f36bf92d09668306e224fa9f4e4f87b77f6957192ae59f"}, + {file = "botocore-1.34.94-py3-none-any.whl", hash = "sha256:f00a79002e0cb9d6895ecd0919c506402850177d7b6c4d2634fa2da362d95bcb"}, + {file = "botocore-1.34.94.tar.gz", hash = "sha256:99b11be9a28f9051af4c96fa121e9c3f22a86d499abd773c9e868b2a38961bae"}, ] [package.dependencies] @@ -382,11 +433,11 @@ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, - {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, ] [package.extras] -crt = ["awscrt (==0.19.19)"] +crt = ["awscrt (==0.20.9)"] [[package]] name = "bytecode" @@ -2614,7 +2665,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3606,4 +3656,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0.0" -content-hash = "a9993e4b3b2b78051915b41314b9c8c1281c0349405b665153167621ba400544" +content-hash = "032b588e2434297b867cd0e7ed2db2b4c131964f162a5154ff7a9cf47e316045" diff --git a/pyproject.toml b/pyproject.toml index 59f3efb8c88..ea5d4d72787 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ typing-extensions = "^4.11.0" datadog-lambda = { version = ">=4.77,<6.0", optional = true } aws-encryption-sdk = { version = "^3.1.1", optional = true } jsonpath-ng = { version = "^1.6.0", optional = true } +awscrt = "^0.20.9" [tool.poetry.dev-dependencies] coverage = { extras = ["toml"], version = "^7.5" } @@ -122,6 +123,10 @@ types-redis = "^4.6.0.7" testcontainers = { extras = ["redis"], version = "^3.7.1" } multiprocess = "^0.70.16" + +[tool.poetry.group.dev-dependencies.dependencies] +botocore = "^1.34.94" + [tool.coverage.run] source = ["aws_lambda_powertools"] omit = [ From a2c713a60a16e2811a39536222041200f6fc5fee Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Wed, 1 May 2024 07:19:28 -0400 Subject: [PATCH 17/21] remove one more jwt reference --- aws_lambda_powertools/utilities/auth/aws_auth.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 3fb9ce2aece..16a7282e871 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -22,17 +22,6 @@ class AWSServicePrefix(Enum): RESTAPI = "execute-api" HTTPAPI = "apigateway" APPSYNC = "appsync" - S3 = "s3" - - -class AuthProvider(Enum): - """ - Auth Provider - Enumerations of the supported authentication providers - """ - - AUTH0 = "auth0" - COGNITO = "cognito" - OKTA = "okta" class AWSSigV4Auth: From e01e65c1c51fc3d653bf4c1fd56d593a23fa1301 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sun, 15 Sep 2024 10:42:52 -0400 Subject: [PATCH 18/21] refinements --- .../utilities/auth/__init__.py | 4 +- .../utilities/auth/aws_auth.py | 145 +++++++----------- 2 files changed, 56 insertions(+), 93 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/__init__.py b/aws_lambda_powertools/utilities/auth/__init__.py index ea3cb64400c..1068a2feab0 100644 --- a/aws_lambda_powertools/utilities/auth/__init__.py +++ b/aws_lambda_powertools/utilities/auth/__init__.py @@ -1,5 +1,5 @@ """Advanced feature flags utility""" -from .aws_auth import AuthProvider, AWSServicePrefix, AWSSigV4Auth, JWTAuth +from .aws_auth import ServicePrefix, SigV4aAuth, SigV4Auth -__all__ = ["AuthProvider", "AWSServicePrefix", "AWSSigV4Auth", "JWTAuth"] +__all__ = ["ServicePrefix", "SigV4Auth", "SigV4aAuth"] diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 16a7282e871..8380c3c2fe5 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -1,19 +1,18 @@ from __future__ import annotations +import json +import os from enum import Enum from typing import Optional import botocore.session from botocore import crt -from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest -from botocore.credentials import Credentials, ReadOnlyCredentials -class AWSServicePrefix(Enum): +class ServicePrefix(Enum): """ AWS Service Prefixes - Enumerations of the supported service proxy types - URLs: https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html """ @@ -24,22 +23,19 @@ class AWSServicePrefix(Enum): APPSYNC = "appsync" -class AWSSigV4Auth: +class SigV4Auth: """ Authenticating Requests (AWS Signature Version 4) Requests that were signed with SigV4 will have SignatureVersion set to AWS4-HMAC-SHA256 Args: url (str): URL - region (str): AWS region - body (str, optional): Request body + service (ServicePrefix): AWS service Prefix + region (str, Optional): AWS region + body (dict, optional): Request body params (dict, optional): Request parameters headers (dict, optional): Request headers method (str, optional): Request method - service (str, optional): AWS service - access_key (str, optional): AWS access key - secret_key (str, optional): AWS secret key - token (str, optional): AWS session token Returns: SigV4Auth: SigV4Auth instance @@ -47,76 +43,58 @@ class AWSSigV4Auth: Examples -------- **Using default credentials** - >>> from aws_lambda_powertools.utilities.iam import AWSSigV4Auth - >>> auth = AWSSigV4Auth(region="us-east-2", service=AWSServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") + >>> from aws_lambda_powertools.utilities.auth import SigV4Auth + >>> prepped = SigV4Auth.prepare_request(region="us-east-2", service=ServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") """ - def __init__( - self, + @staticmethod + def prepare_request( url: str, - region: str, - body: Optional[str] = None, + service: ServicePrefix, + region: Optional[str], + body: Optional[dict] = None, params: Optional[dict] = None, headers: Optional[dict] = None, method: Optional[str] = "GET", - service: Enum = AWSServicePrefix.LATTICE, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, - token: Optional[str] = None, ): - self.service = service.value - self.region = region - self.method = method - self.url = url - self.data = body - self.params = params - self.headers = headers - - self.credentials: Credentials | ReadOnlyCredentials - - if access_key and secret_key and token: - self.access_key = access_key - self.secret_key = secret_key - self.token = token - self.credentials = Credentials(access_key=self.access_key, secret_key=self.secret_key, token=self.token) + if region is None: + region = os.environ.get("AWS_REGION") + + if body is not None: + body = json.dumps(body) else: - credentials = botocore.session.Session().get_credentials() - self.credentials = credentials.get_frozen_credentials() + body = json.dumps({}) - if self.headers is None: - self.headers = {"Content-Type": "application/json"} + credentials = botocore.session.Session().get_credentials() - sigv4 = SigV4Auth(credentials=self.credentials, service_name=self.service, region_name=self.region) + signer = crt.auth.CrtSigV4Auth(credentials, service.value, region) - request = AWSRequest(method=self.method, url=self.url, data=self.data, params=self.params, headers=self.headers) + if headers is None: + headers = {"Content-Type": "application/json"} - if self.service == AWSServicePrefix.LATTICE.value: + request = AWSRequest(method=method, url=url, data=body, params=params, headers=headers) + + if service.value == "vpc-lattice-svcs": # payload signing is not supported for vpc-lattice-svcs request.context["payload_signing_enabled"] = False - sigv4.add_auth(request) - self.signed_request = request.prepare() - - def __call__(self): - return self.signed_request + signer.add_auth(request) + return request.prepare() -class AWSSigV4aAuth: +class SigV4aAuth: """ Authenticating Requests (AWS Signature Version 4a) Requests that were signed with SigV4A will have a SignatureVersion set to AWS4-ECDSA-P256-SHA256 Args: url (str): URL - region (str): AWS region - body (str, optional): Request body + service (ServicePrefix): AWS service Prefix + region (str, Optional): AWS region + body (dict, optional): Request body params (dict, optional): Request parameters headers (dict, optional): Request headers method (str, optional): Request method - service (str, optional): AWS service - access_key (str, optional): AWS access key - secret_key (str, optional): AWS secret key - token (str, optional): AWS session token Returns: SigV4aAuth: SigV4aAuth instance @@ -124,55 +102,40 @@ class AWSSigV4aAuth: Examples -------- **Using default credentials** - >>> from aws_lambda_powertools.utilities.iam import AWSSigV4aAuth - >>> auth = AWSSigV4aAuth(region="us-east-2", service=AWSServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") + >>> from aws_lambda_powertools.utilities.iam import SigV4aAuth + >>> prepped = SigV4aAuth.prepare_request(region="us-east-2", service=ServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") """ - def __init__( - self, + @staticmethod + def prepare_request( url: str, - region: str, - body: Optional[str] = None, + service: ServicePrefix, + region: Optional[str], + body: Optional[dict] = None, params: Optional[dict] = None, headers: Optional[dict] = None, method: Optional[str] = "GET", - service: Enum = AWSServicePrefix.LATTICE, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, - token: Optional[str] = None, ): - self.service = service.value - self.region = region - self.method = method - self.url = url - self.data = body - self.params = params - self.headers = headers - - self.credentials: Credentials | ReadOnlyCredentials - - if access_key and secret_key and token: - self.access_key = access_key - self.secret_key = secret_key - self.token = token - self.credentials = Credentials(access_key=self.access_key, secret_key=self.secret_key, token=self.token) + if region is None: + region = os.environ.get("AWS_REGION") + + if body is not None: + body = json.dumps(body) else: - credentials = botocore.session.Session().get_credentials() - self.credentials = credentials.get_frozen_credentials() + body = json.dumps({}) - if self.headers is None: - self.headers = {"Content-Type": "application/json"} + credentials = botocore.session.Session().get_credentials() - signer = crt.auth.CrtSigV4AsymAuth(self.credentials, self.service, self.region) + signer = crt.auth.CrtSigV4AsymAuth(credentials, service.value, region) - request = AWSRequest(method=self.method, url=self.url, data=self.data, params=self.params, headers=self.headers) + if headers is None: + headers = {"Content-Type": "application/json"} - if self.service == AWSServicePrefix.LATTICE.value: + request = AWSRequest(method=method, url=url, data=body, params=params, headers=headers) + + if service.value == "vpc-lattice-svcs": # payload signing is not supported for vpc-lattice-svcs request.context["payload_signing_enabled"] = False signer.add_auth(request) - self.signed_request = request.prepare() - - def __call__(self): - return self.signed_request + return request.prepare() From ffac44375fcc94e0bce8be7552c79ad41852c21f Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Fri, 20 Sep 2024 07:55:17 -0400 Subject: [PATCH 19/21] default region --- aws_lambda_powertools/utilities/auth/aws_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 8380c3c2fe5..47f40317e53 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -110,7 +110,7 @@ class SigV4aAuth: def prepare_request( url: str, service: ServicePrefix, - region: Optional[str], + region: Optional[str] = "*", body: Optional[dict] = None, params: Optional[dict] = None, headers: Optional[dict] = None, From 4aa96599f12aaec864055473ffb82db8f265e431 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Fri, 20 Sep 2024 07:55:54 -0400 Subject: [PATCH 20/21] remove if --- aws_lambda_powertools/utilities/auth/aws_auth.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 47f40317e53..1aa44e8e429 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -116,9 +116,6 @@ def prepare_request( headers: Optional[dict] = None, method: Optional[str] = "GET", ): - if region is None: - region = os.environ.get("AWS_REGION") - if body is not None: body = json.dumps(body) else: From 002d0c049cf2590399d18e2c7db81574d2562838 Mon Sep 17 00:00:00 2001 From: stephenbawks Date: Sun, 22 Sep 2024 09:04:02 -0400 Subject: [PATCH 21/21] update example --- aws_lambda_powertools/utilities/auth/aws_auth.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/utilities/auth/aws_auth.py b/aws_lambda_powertools/utilities/auth/aws_auth.py index 1aa44e8e429..7479d721ab8 100644 --- a/aws_lambda_powertools/utilities/auth/aws_auth.py +++ b/aws_lambda_powertools/utilities/auth/aws_auth.py @@ -42,8 +42,7 @@ class SigV4Auth: Examples -------- - **Using default credentials** - >>> from aws_lambda_powertools.utilities.auth import SigV4Auth + >>> from aws_lambda_powertools.utilities.auth import SigV4Auth, ServicePrefix >>> prepped = SigV4Auth.prepare_request(region="us-east-2", service=ServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") """ @@ -101,8 +100,7 @@ class SigV4aAuth: Examples -------- - **Using default credentials** - >>> from aws_lambda_powertools.utilities.iam import SigV4aAuth + >>> from aws_lambda_powertools.utilities.iam import SigV4aAuth, ServicePrefix >>> prepped = SigV4aAuth.prepare_request(region="us-east-2", service=ServicePrefix.LATTICE, url="https://test-fake-service.vpc-lattice-svcs.us-east-2.on.aws") """