From 062212792bde55aa36aac94ab60ab9137296d8bd Mon Sep 17 00:00:00 2001 From: RogerZhang Date: Thu, 7 Sep 2023 22:50:15 +0000 Subject: [PATCH 1/5] add secrets_event --- .../utilities/data_classes/__init__.py | 2 ++ .../utilities/data_classes/secrets_event.py | 20 +++++++++++++++++++ tests/events/secretManagerEvent.json | 5 +++++ tests/unit/data_classes/test_secret_event.py | 11 ++++++++++ 4 files changed, 38 insertions(+) create mode 100644 aws_lambda_powertools/utilities/data_classes/secrets_event.py create mode 100644 tests/events/secretManagerEvent.json create mode 100644 tests/unit/data_classes/test_secret_event.py diff --git a/aws_lambda_powertools/utilities/data_classes/__init__.py b/aws_lambda_powertools/utilities/data_classes/__init__.py index c619104fda8..be3abb4fe18 100644 --- a/aws_lambda_powertools/utilities/data_classes/__init__.py +++ b/aws_lambda_powertools/utilities/data_classes/__init__.py @@ -18,6 +18,7 @@ from .kinesis_stream_event import KinesisStreamEvent from .lambda_function_url_event import LambdaFunctionUrlEvent from .s3_event import S3Event, S3EventBridgeNotificationEvent +from .secrets_event import SecretManagerEvents from .ses_event import SESEvent from .sns_event import SNSEvent from .sqs_event import SQSEvent @@ -26,6 +27,7 @@ __all__ = [ "APIGatewayProxyEvent", "APIGatewayProxyEventV2", + "SecretManagerEvents", "AppSyncResolverEvent", "ALBEvent", "CloudWatchDashboardCustomWidgetEvent", diff --git a/aws_lambda_powertools/utilities/data_classes/secrets_event.py b/aws_lambda_powertools/utilities/data_classes/secrets_event.py new file mode 100644 index 00000000000..a0274161d22 --- /dev/null +++ b/aws_lambda_powertools/utilities/data_classes/secrets_event.py @@ -0,0 +1,20 @@ +from typing_extensions import Literal + +from aws_lambda_powertools.utilities.data_classes.common import DictWrapper + + +class SecretManagerEvent(DictWrapper): + @property + def secret_id(self) -> str: + """SecretId: The secret ARN or identifier""" + return self["SecretId"] + + @property + def client_request_token(self) -> str: + """ClientRequestToken: The ClientRequestToken of the secret version""" + return self["ClientRequestToken"] + + @property + def step(self) -> Literal["createSecret", "setSecret", "testSecret", "finishSecret"]: + """Step: The rotation step (one of createSecret, setSecret, testSecret, or finishSecret)""" + return self["Step"] diff --git a/tests/events/secretManagerEvent.json b/tests/events/secretManagerEvent.json new file mode 100644 index 00000000000..f07ea1e0b03 --- /dev/null +++ b/tests/events/secretManagerEvent.json @@ -0,0 +1,5 @@ +{ + "SecretId":"arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3", + "ClientRequestToken":"550e8400-e29b-41d4-a716-446655440000", + "Step":"createSecret" +} \ No newline at end of file diff --git a/tests/unit/data_classes/test_secret_event.py b/tests/unit/data_classes/test_secret_event.py new file mode 100644 index 00000000000..261d93f4525 --- /dev/null +++ b/tests/unit/data_classes/test_secret_event.py @@ -0,0 +1,11 @@ +from aws_lambda_powertools.utilities.data_classes.secrets_event import SecretManagerEvent +from tests.functional.utils import load_event + + +def test_vpc_lattice_event(): + raw_event = load_event("secretManagerEvent.json") + parsed_event = SecretManagerEvent(raw_event) + + assert parsed_event.secret_id == raw_event["SecretId"] + assert parsed_event.client_request_token == raw_event["ClientRequestToken"] + assert parsed_event.step == raw_event["Step"] From 0c790c04358f8cd59543e53275859195c04b0519 Mon Sep 17 00:00:00 2001 From: RogerZhang Date: Thu, 7 Sep 2023 22:58:08 +0000 Subject: [PATCH 2/5] add example --- .../utilities/data_classes/__init__.py | 4 ++-- docs/utilities/data_classes.md | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/data_classes/__init__.py b/aws_lambda_powertools/utilities/data_classes/__init__.py index be3abb4fe18..2dd8f405018 100644 --- a/aws_lambda_powertools/utilities/data_classes/__init__.py +++ b/aws_lambda_powertools/utilities/data_classes/__init__.py @@ -18,7 +18,7 @@ from .kinesis_stream_event import KinesisStreamEvent from .lambda_function_url_event import LambdaFunctionUrlEvent from .s3_event import S3Event, S3EventBridgeNotificationEvent -from .secrets_event import SecretManagerEvents +from .secrets_event import SecretManagerEvent from .ses_event import SESEvent from .sns_event import SNSEvent from .sqs_event import SQSEvent @@ -27,7 +27,7 @@ __all__ = [ "APIGatewayProxyEvent", "APIGatewayProxyEventV2", - "SecretManagerEvents", + "SecretManagerEvent", "AppSyncResolverEvent", "ALBEvent", "CloudWatchDashboardCustomWidgetEvent", diff --git a/docs/utilities/data_classes.md b/docs/utilities/data_classes.md index 7b3aa74e275..b11583f230e 100644 --- a/docs/utilities/data_classes.md +++ b/docs/utilities/data_classes.md @@ -1161,6 +1161,26 @@ You can register your Lambda functions as targets within an Amazon VPC Lattice s --8<-- "examples/event_sources/src/vpc_lattice_payload.json" ``` +### Secrets Manager + +=== "app.py" + + ```python + from aws_lambda_powertools.utilities.data_classes import event_source, SecretManagerEvent + + @event_source(data_class=SecretManagerEvent) + def lambda_handler(event: SecretManagerEvent, context): + # Multiple records can be delivered in a single event + service_client = boto3.client('secretsmanager', endpoint_url=os.environ['SECRETS_MANAGER_ENDPOINT']) + create_secret(service_client, event.secret_id, event.client_request_token) + ... + ``` +=== "Secrets Manager Example Event" + + ```json + --8<-- "tests/events/secretManagerEvent.json" + ``` + ## Advanced ### Debugging From abf6a931b1da54652f2829d1ff6736036b550d85 Mon Sep 17 00:00:00 2001 From: RogerZhang Date: Mon, 11 Sep 2023 21:22:11 +0000 Subject: [PATCH 3/5] address typo, example fixes --- .../utilities/data_classes/__init__.py | 4 ++-- ...rets_event.py => secrets_manager_event.py} | 2 +- docs/utilities/data_classes.md | 20 +++++++++++++------ ...gerEvent.json => secretsManagerEvent.json} | 0 ...event.py => test_secrets_manager_event.py} | 8 ++++---- 5 files changed, 21 insertions(+), 13 deletions(-) rename aws_lambda_powertools/utilities/data_classes/{secrets_event.py => secrets_manager_event.py} (94%) rename tests/events/{secretManagerEvent.json => secretsManagerEvent.json} (100%) rename tests/unit/data_classes/{test_secret_event.py => test_secrets_manager_event.py} (50%) diff --git a/aws_lambda_powertools/utilities/data_classes/__init__.py b/aws_lambda_powertools/utilities/data_classes/__init__.py index 2dd8f405018..b29b63d8345 100644 --- a/aws_lambda_powertools/utilities/data_classes/__init__.py +++ b/aws_lambda_powertools/utilities/data_classes/__init__.py @@ -18,7 +18,7 @@ from .kinesis_stream_event import KinesisStreamEvent from .lambda_function_url_event import LambdaFunctionUrlEvent from .s3_event import S3Event, S3EventBridgeNotificationEvent -from .secrets_event import SecretManagerEvent +from .secrets_manager_event import SecretsManagerEvent from .ses_event import SESEvent from .sns_event import SNSEvent from .sqs_event import SQSEvent @@ -27,7 +27,7 @@ __all__ = [ "APIGatewayProxyEvent", "APIGatewayProxyEventV2", - "SecretManagerEvent", + "SecretsManagerEvent", "AppSyncResolverEvent", "ALBEvent", "CloudWatchDashboardCustomWidgetEvent", diff --git a/aws_lambda_powertools/utilities/data_classes/secrets_event.py b/aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py similarity index 94% rename from aws_lambda_powertools/utilities/data_classes/secrets_event.py rename to aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py index a0274161d22..1db86e71725 100644 --- a/aws_lambda_powertools/utilities/data_classes/secrets_event.py +++ b/aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py @@ -3,7 +3,7 @@ from aws_lambda_powertools.utilities.data_classes.common import DictWrapper -class SecretManagerEvent(DictWrapper): +class SecretsManagerEvent(DictWrapper): @property def secret_id(self) -> str: """SecretId: The secret ARN or identifier""" diff --git a/docs/utilities/data_classes.md b/docs/utilities/data_classes.md index b11583f230e..35fd6b54609 100644 --- a/docs/utilities/data_classes.md +++ b/docs/utilities/data_classes.md @@ -1166,19 +1166,27 @@ You can register your Lambda functions as targets within an Amazon VPC Lattice s === "app.py" ```python - from aws_lambda_powertools.utilities.data_classes import event_source, SecretManagerEvent + import boto3 - @event_source(data_class=SecretManagerEvent) - def lambda_handler(event: SecretManagerEvent, context): - # Multiple records can be delivered in a single event + from aws_lambda_powertools.utilities.data_classes import event_source, SecretsManagerEvent + + @event_source(data_class=SecretsManagerEvent) + def lambda_handler(event: SecretsManagerEvent, context): service_client = boto3.client('secretsmanager', endpoint_url=os.environ['SECRETS_MANAGER_ENDPOINT']) - create_secret(service_client, event.secret_id, event.client_request_token) + secret = service_client.get_secret_value( + SecretId=event.secret_id, + VersionId=event.client_request_token, + VersionStage="AWSCURRENT" + ) + # {'Name': 'MyTestDatabaseSecret','SecretString': '{\n "username":"david",\n "password":"EXAMPLE-PASSWORD"\n}\n',} + # work with secrets afterwards + # see - https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas ... ``` === "Secrets Manager Example Event" ```json - --8<-- "tests/events/secretManagerEvent.json" + --8<-- "tests/events/secretsManagerEvent.json" ``` ## Advanced diff --git a/tests/events/secretManagerEvent.json b/tests/events/secretsManagerEvent.json similarity index 100% rename from tests/events/secretManagerEvent.json rename to tests/events/secretsManagerEvent.json diff --git a/tests/unit/data_classes/test_secret_event.py b/tests/unit/data_classes/test_secrets_manager_event.py similarity index 50% rename from tests/unit/data_classes/test_secret_event.py rename to tests/unit/data_classes/test_secrets_manager_event.py index 261d93f4525..324f6598334 100644 --- a/tests/unit/data_classes/test_secret_event.py +++ b/tests/unit/data_classes/test_secrets_manager_event.py @@ -1,10 +1,10 @@ -from aws_lambda_powertools.utilities.data_classes.secrets_event import SecretManagerEvent +from aws_lambda_powertools.utilities.data_classes.secrets_manager_event import SecretsManagerEvent from tests.functional.utils import load_event -def test_vpc_lattice_event(): - raw_event = load_event("secretManagerEvent.json") - parsed_event = SecretManagerEvent(raw_event) +def test_secrets_manager_event(): + raw_event = load_event("secretsManagerEvent.json") + parsed_event = SecretsManagerEvent(raw_event) assert parsed_event.secret_id == raw_event["SecretId"] assert parsed_event.client_request_token == raw_event["ClientRequestToken"] From 07a6ccf5b74a9957f7a42bbf370795ee7782a002 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 11 Sep 2023 22:52:46 +0100 Subject: [PATCH 4/5] Using parameter utility in the example --- docs/utilities/data_classes.md | 44 +++++++------------ examples/event_sources/src/secrets_manager.py | 16 +++++++ .../src/secrets_manager_event.json | 5 +++ 3 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 examples/event_sources/src/secrets_manager.py create mode 100644 examples/event_sources/src/secrets_manager_event.json diff --git a/docs/utilities/data_classes.md b/docs/utilities/data_classes.md index 35fd6b54609..603ab87f50c 100644 --- a/docs/utilities/data_classes.md +++ b/docs/utilities/data_classes.md @@ -1095,6 +1095,22 @@ This example is based on the AWS Blog post [Introducing Amazon S3 Object Lambda file_key = event.detail.object.key ``` +### Secrets Manager + +AWS Secrets Manager rotation uses an AWS Lambda function to update the secret. [Click here](https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets.html){target="_blank"} for more information about rotating AWS Secrets Manager secrets. + +=== "app.py" + + ```python hl_lines="2 7 11" + --8<-- "examples/event_sources/src/secrets_manager.py" + ``` + +=== "Secrets Manager Example Event" + + ```json + --8<-- "tests/events/secretsManagerEvent.json" + ``` + ### SES === "app.py" @@ -1161,34 +1177,6 @@ You can register your Lambda functions as targets within an Amazon VPC Lattice s --8<-- "examples/event_sources/src/vpc_lattice_payload.json" ``` -### Secrets Manager - -=== "app.py" - - ```python - import boto3 - - from aws_lambda_powertools.utilities.data_classes import event_source, SecretsManagerEvent - - @event_source(data_class=SecretsManagerEvent) - def lambda_handler(event: SecretsManagerEvent, context): - service_client = boto3.client('secretsmanager', endpoint_url=os.environ['SECRETS_MANAGER_ENDPOINT']) - secret = service_client.get_secret_value( - SecretId=event.secret_id, - VersionId=event.client_request_token, - VersionStage="AWSCURRENT" - ) - # {'Name': 'MyTestDatabaseSecret','SecretString': '{\n "username":"david",\n "password":"EXAMPLE-PASSWORD"\n}\n',} - # work with secrets afterwards - # see - https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas - ... - ``` -=== "Secrets Manager Example Event" - - ```json - --8<-- "tests/events/secretsManagerEvent.json" - ``` - ## Advanced ### Debugging diff --git a/examples/event_sources/src/secrets_manager.py b/examples/event_sources/src/secrets_manager.py new file mode 100644 index 00000000000..b25bae3f6b1 --- /dev/null +++ b/examples/event_sources/src/secrets_manager.py @@ -0,0 +1,16 @@ +from aws_lambda_powertools.utilities import parameters +from aws_lambda_powertools.utilities.data_classes import SecretsManagerEvent, event_source + +secrets_provider = parameters.SecretsProvider() + + +@event_source(data_class=SecretsManagerEvent) +def lambda_handler(event: SecretsManagerEvent, context): + # Getting secret value using Parameter utility + # See https://docs.powertools.aws.dev/lambda/python/latest/utilities/parameters/ + secret = secrets_provider.get(event.secret_id, VersionId=event.client_request_token, VersionStage="AWSCURRENT") + + # You need to work with secrets afterwards + # Check more examples: https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas + + return secret diff --git a/examples/event_sources/src/secrets_manager_event.json b/examples/event_sources/src/secrets_manager_event.json new file mode 100644 index 00000000000..18e7dcd935b --- /dev/null +++ b/examples/event_sources/src/secrets_manager_event.json @@ -0,0 +1,5 @@ +{ + "SecretId":"arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3", + "ClientRequestToken":"550e8400-e29b-41d4-a716-446655440000", + "Step":"createSecret" +} From b7fd980029dfdda9090ebf29f2b07429375f59b8 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 12 Sep 2023 09:22:40 +0100 Subject: [PATCH 5/5] Addressing Heitor feedback --- .../utilities/data_classes/secrets_manager_event.py | 7 ++++++- examples/event_sources/src/secrets_manager.py | 2 +- tests/unit/data_classes/test_secrets_manager_event.py | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py b/aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py index 1db86e71725..b5fa41211ef 100644 --- a/aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py +++ b/aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py @@ -11,7 +11,12 @@ def secret_id(self) -> str: @property def client_request_token(self) -> str: - """ClientRequestToken: The ClientRequestToken of the secret version""" + """ClientRequestToken: The ClientRequestToken associated with the secret version""" + return self["ClientRequestToken"] + + @property + def version_id(self) -> str: + """Alias to ClientRequestToken to get token associated to version""" return self["ClientRequestToken"] @property diff --git a/examples/event_sources/src/secrets_manager.py b/examples/event_sources/src/secrets_manager.py index b25bae3f6b1..d69b052779e 100644 --- a/examples/event_sources/src/secrets_manager.py +++ b/examples/event_sources/src/secrets_manager.py @@ -8,7 +8,7 @@ def lambda_handler(event: SecretsManagerEvent, context): # Getting secret value using Parameter utility # See https://docs.powertools.aws.dev/lambda/python/latest/utilities/parameters/ - secret = secrets_provider.get(event.secret_id, VersionId=event.client_request_token, VersionStage="AWSCURRENT") + secret = secrets_provider.get(event.secret_id, VersionId=event.version_id, VersionStage="AWSCURRENT") # You need to work with secrets afterwards # Check more examples: https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas diff --git a/tests/unit/data_classes/test_secrets_manager_event.py b/tests/unit/data_classes/test_secrets_manager_event.py index 324f6598334..6bba952aa9b 100644 --- a/tests/unit/data_classes/test_secrets_manager_event.py +++ b/tests/unit/data_classes/test_secrets_manager_event.py @@ -8,4 +8,5 @@ def test_secrets_manager_event(): assert parsed_event.secret_id == raw_event["SecretId"] assert parsed_event.client_request_token == raw_event["ClientRequestToken"] + assert parsed_event.version_id == raw_event["ClientRequestToken"] assert parsed_event.step == raw_event["Step"]