-
Notifications
You must be signed in to change notification settings - Fork 937
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2469 from senthilkumarmohan/main
New pattern: eventbridge-schedule-secret-rotation-cdk
- Loading branch information
Showing
10 changed files
with
564 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# Amazon EventBridge Scheduler to AWS Lambda to AWS Secrets Manager | ||
|
||
This sample project demonstrates rotating secrets in AWS Secrets Manager using Amazon EventBridge Scheduler and AWS Lambda at desired intervals. | ||
|
||
Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/eventbridge-schedule-secret-rotation-cdk](https://serverlessland.com/patterns/eventbridge-schedule-secret-rotation-cdk) | ||
|
||
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. | ||
|
||
## Requirements | ||
|
||
* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. | ||
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured | ||
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) | ||
* [AWS CDK Toolkit](https://docs.aws.amazon.com/cdk/v2/guide/cli.html) (AWS CDK Toolkit) installed and configured | ||
|
||
## Deployment Instructions | ||
|
||
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: | ||
``` | ||
git clone https://github.com/aws-samples/serverless-patterns | ||
``` | ||
2. Create and activate Python virtual environment. For instructions, please refer this [page](https://python.land/virtual-environments/virtualenv#Python_venv_activation) | ||
3. Install the required pacakages | ||
``` | ||
pip install -r requirements.txt | ||
``` | ||
4. Change directory to the pattern directory: | ||
``` | ||
cd serverless-patterns/eventbridge-schedule-secret-rotation-cdk | ||
``` | ||
5. From the command line, use AWS CDK to deploy the AWS resources for the pattern as specified in the [eventbridge_schedule_secret_rotation_stack.py](/cdk/eventbridge_schedule_secret_rotation_stack.py) file: | ||
``` | ||
cdk deploy | ||
``` | ||
The sample project configures the secret rotation to be triggered every hour. If you wish to use use a different value, you could do so by providing a cron/rate based expression for the *SecretRotationSchedule* param. Please refer AWS [documentation](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html) about using cron/rate expressions. | ||
``` | ||
cdk deploy --parameters SecretRotationSchedule="cron(* 0/1 * * ? *)" | ||
``` | ||
6. Note the outputs from the CDK deployment process. These contain the arns for the Demo Secret and the rotation lambda. | ||
## How it works | ||
An EventBridge schedule is created based on the 'SecretRotationschedule' CDK parameter with the rotation lambda as the target. Rotation lambda performs the below mentioned steps on the demo secret. | ||
- Creates a new version of the secret with AWSPENDING staging label. | ||
- Updates the new version of the secret with the new value. | ||
- Updates current version of the secret in AWSCURRENT stage to AWSPREVIOUS. | ||
- Updates the staging label on the new version of secret from AWSPENDING to AWSCURRENT | ||
## Testing | ||
- Copy *SecretArn* value from the cdk output. | ||
- Run the following command from the command line to retrive the secret value | ||
``` | ||
aws secretsmanager get-secret-value --secret-id="{SecretArn}" | ||
``` | ||
- Run the following command to view the versions created for the secret as part of rotation process | ||
``` | ||
aws secretsmanager describe-secret --secret-id="{SecretArn}" | ||
``` | ||
## Cleanup | ||
1. Delete the stack | ||
``` | ||
cdk destroy EventbridgeScheduleSecretRotationCdkStack | ||
``` | ||
2. Deactivate the Python virtual environment. For instructions, please refer this [page](https://python.land/virtual-environments/virtualenv#Python_venv_activation) | ||
---- | ||
Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
SPDX-License-Identifier: MIT-0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#!/usr/bin/env python3 | ||
import os | ||
|
||
import aws_cdk as cdk | ||
|
||
from cdk.eventbridge_schedule_secret_rotation_stack import EventbridgeScheduleSecretRotationCdkStack | ||
|
||
|
||
app = cdk.App() | ||
EventbridgeScheduleSecretRotationCdkStack(app, "EventbridgeScheduleSecretRotationCdkStack", | ||
# If you don't specify 'env', this stack will be environment-agnostic. | ||
# Account/Region-dependent features and context lookups will not work, | ||
# but a single synthesized template can be deployed anywhere. | ||
|
||
# Uncomment the next line to specialize this stack for the AWS Account | ||
# and Region that are implied by the current CLI configuration. | ||
|
||
#env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), | ||
|
||
# Uncomment the next line if you know exactly what Account and Region you | ||
# want to deploy the stack to. */ | ||
|
||
#env=cdk.Environment(account='123456789012', region='us-east-1'), | ||
|
||
# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html | ||
) | ||
|
||
app.synth() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"app": "python3 app.py", | ||
"watch": { | ||
"include": [ | ||
"**" | ||
], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"requirements*.txt", | ||
"source.bat", | ||
"**/__init__.py", | ||
"python/__pycache__", | ||
"tests" | ||
] | ||
}, | ||
"context": { | ||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
"@aws-cdk/core:checkSecretUsage": true, | ||
"@aws-cdk/core:target-partitions": [ | ||
"aws", | ||
"aws-cn" | ||
], | ||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, | ||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, | ||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, | ||
"@aws-cdk/aws-iam:minimizePolicies": true, | ||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, | ||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, | ||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, | ||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true, | ||
"@aws-cdk/core:enablePartitionLiterals": true, | ||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, | ||
"@aws-cdk/aws-iam:standardizedServicePrincipals": true, | ||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, | ||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, | ||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, | ||
"@aws-cdk/aws-route53-patters:useCertificate": true, | ||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false, | ||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, | ||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, | ||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, | ||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, | ||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, | ||
"@aws-cdk/aws-redshift:columnId": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, | ||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, | ||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, | ||
"@aws-cdk/aws-kms:aliasNameRef": true, | ||
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, | ||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true, | ||
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true | ||
} | ||
} |
Empty file.
117 changes: 117 additions & 0 deletions
117
eventbridge-schedule-secret-rotation-cdk/cdk/eventbridge_schedule_secret_rotation_stack.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import json | ||
from aws_cdk import ( | ||
CfnOutput, | ||
CfnParameter, | ||
Duration, | ||
Stack, | ||
aws_iam as iam, | ||
aws_lambda as lambda_, | ||
aws_secretsmanager as secretsmanager, | ||
aws_events as events, | ||
aws_scheduler as scheduler | ||
) | ||
from constructs import Construct | ||
|
||
class EventbridgeScheduleSecretRotationCdkStack(Stack): | ||
|
||
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: | ||
super().__init__(scope, construct_id, **kwargs) | ||
|
||
param_rotation_schedule = CfnParameter(self, "SecretRotationSchedule", | ||
description="Secret rotation schedule (cron or rate)", | ||
default="cron(0 0/1 * * ? *)" | ||
) | ||
|
||
secret = secretsmanager.CfnSecret(self, "Secret", | ||
secret_string = json.dumps({"value":"initial_value"}), | ||
name=f'secret-rotation-demo-secret', | ||
description = "Secret Rotation Demo secret") | ||
|
||
rotation_lambda = self.create_rotation_lambda(secret) | ||
|
||
self.setup_rotation_schedule(param_rotation_schedule, rotation_lambda) | ||
|
||
CfnOutput(self, "SecretArn", | ||
value=secret.attr_id) | ||
|
||
CfnOutput(self, "SecretRotationLambdaArn", | ||
value=rotation_lambda.function_arn) | ||
|
||
CfnOutput(self, "RotationSchedule", | ||
value=str(param_rotation_schedule.value_as_string)) | ||
|
||
def create_rotation_lambda(self, secret): | ||
|
||
rotation_lambda_role = iam.Role(self, "secret-rotator-lambda-role", | ||
role_name = f"secret-rotator-lambda-role", | ||
assumed_by = iam.ServicePrincipal("lambda.amazonaws.com"), | ||
managed_policies = [ | ||
iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole") | ||
], | ||
inline_policies = { | ||
"secret-rotator-policy": | ||
iam.PolicyDocument( | ||
statements = [ | ||
iam.PolicyStatement( | ||
actions = ["secretsmanager:DescribeSecret", | ||
"secretsmanager:GetSecretValue", | ||
"secretsmanager:PutSecretValue", | ||
"secretsmanager:UpdateSecretVersionStage"], | ||
resources = [secret.attr_id] | ||
), | ||
iam.PolicyStatement( | ||
actions = ["events:PutEvents"], | ||
resources = [f"arn:aws:events:{Stack.of(self).region}:{Stack.of(self).account}:event-bus/default"]) | ||
] | ||
) | ||
} | ||
) | ||
|
||
rotation_lambda = lambda_.Function(self, "secret-rotation-lambda", | ||
role=rotation_lambda_role, | ||
code=lambda_.Code.from_asset("src//lambda"), | ||
handler="rotation_lambda.lambda_handler", | ||
runtime=lambda_.Runtime.PYTHON_3_11, | ||
environment={ | ||
"SECRET_ID": secret.attr_id | ||
}, | ||
timeout=Duration.seconds(120) | ||
) | ||
|
||
lambda_.CfnPermission(self, "events-permission-on-rotator-lambda", | ||
action="lambda:InvokeFunction", | ||
function_name=rotation_lambda.function_name, | ||
principal="events.amazonaws.com") | ||
|
||
return rotation_lambda | ||
|
||
def setup_rotation_schedule(self, param_rotation_schedule, rotation_lambda): | ||
scheduler_role = iam.Role(self, "secret-rotation-scheduler-role", | ||
role_name = f"secret-rotation-scheduler-role", | ||
assumed_by = iam.ServicePrincipal("scheduler.amazonaws.com"), | ||
inline_policies = { | ||
"secret-rotation-scheduler-policy": | ||
iam.PolicyDocument( | ||
statements = [ | ||
iam.PolicyStatement( | ||
actions = ["lambda:InvokeFunction"], | ||
resources = [rotation_lambda.function_arn] | ||
) | ||
] | ||
) | ||
} | ||
) | ||
scheduler.CfnSchedule(self, "secret-rotation-scheduler", | ||
flexible_time_window=scheduler.CfnSchedule.FlexibleTimeWindowProperty( | ||
mode="OFF" | ||
), | ||
schedule_expression=param_rotation_schedule.value_as_string, | ||
target=scheduler.CfnSchedule.TargetProperty( | ||
arn=rotation_lambda.function_arn, | ||
role_arn=scheduler_role.role_arn, | ||
retry_policy=scheduler.CfnSchedule.RetryPolicyProperty( | ||
maximum_retry_attempts=0, | ||
maximum_event_age_in_seconds=5 * 60 | ||
) | ||
) | ||
) |
94 changes: 94 additions & 0 deletions
94
eventbridge-schedule-secret-rotation-cdk/eventbridge-schedule-secret-rotation-cdk.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
{ | ||
"title": "Secret rotation using Amazon EventBridge Scheduler and AWS Lambda", | ||
"description": "Secret rotation in AMS Secrets Manager using Amazon EventBridge Scheduler and AWS Lambda", | ||
"language": "Python", | ||
"level": "200", | ||
"framework": "CDK", | ||
"introBox": { | ||
"headline": "How it works", | ||
"text": [ | ||
"This sample project demonstrates rotating secrets in AWS Secrests Manager using Amazon EventBridge Scheduler and AWS Lambda at desired intervals for several use cases. e.g. rotating OAtuth tokens with limited lifespan.", | ||
"An EventBridge schedule is created based on the 'Secret rotation schedule' CDK parameter with rotation Lambda as the target which does the job", | ||
"You could provide the interval at which you want the secret to be rotated as a cron or rate expression." | ||
] | ||
}, | ||
"gitHub": { | ||
"template": { | ||
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/eventbridge-schedule-secret-rotation-cdk", | ||
"templateURL": "serverless-patterns/eventbridge-schedule-secret-rotation-cdk", | ||
"projectFolder": "eventbridge-schedule-secret-rotation-cdk", | ||
"templateFile": "cdk/eventbridge_schedule_secret_rotation_stack.py" | ||
} | ||
}, | ||
"deploy": { | ||
"text": [ | ||
"cdk deploy" | ||
] | ||
}, | ||
"testing": { | ||
"text": [ | ||
"See the GitHub repo for detailed testing instructions." | ||
] | ||
}, | ||
"cleanup": { | ||
"text": [ | ||
"Delete the stack: <code>cdk destroy</code>." | ||
] | ||
}, | ||
"authors": [ | ||
{ | ||
"name": "Senthil Mohan", | ||
"image": "https://ca.slack-edge.com/E015GUGD2V6-U03JTLBU1HD-ffd97b7877e5-512", | ||
"bio": "Senthil is a Solutions Architect at AWS working with ISVs in the UK to migrate, modernize and build their software products on AWS.", | ||
"linkedin": "senthil-kumar-mohan" | ||
}, | ||
{ | ||
"name": "Shubhankar Sumar", | ||
"image": "https://ca.slack-edge.com/E015GUGD2V6-W01BSTRT7EZ-3a3d16cf4fa5-192", | ||
"bio": "Shubhankar is a Sr. Solutions Architect at AWS working with ISVs in the UK to build, run, and scale their software products on AWS.", | ||
"linkedin": "shubhankar-sumar" | ||
} | ||
], | ||
"resources": { | ||
"bullets": [ | ||
{ | ||
"text": "Using cron and rate expressions to schedule rules in Amazon EventBridge", | ||
"link": "https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html" | ||
}, | ||
{ | ||
"text": "AWS Secrets Manager rotation template", | ||
"link": "https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas/blob/master/SecretsManagerRotationTemplate/lambda_function.py" | ||
} | ||
] | ||
}, | ||
"patternArch": { | ||
"icon1": { | ||
"x": 20, | ||
"y": 50, | ||
"service": "eventbridge-scheduler", | ||
"label": "Amazon EventBridge Scheduler" | ||
}, | ||
"icon2": { | ||
"x": 50, | ||
"y": 50, | ||
"service": "lambda", | ||
"label": "AWS Lambda" | ||
}, | ||
"icon3": { | ||
"x": 80, | ||
"y": 50, | ||
"service": "secretsmanager", | ||
"label": "AWS Secrets Manager" | ||
}, | ||
"line1": { | ||
"from": "icon1", | ||
"to": "icon2", | ||
"label": "trigger" | ||
}, | ||
"line2": { | ||
"from": "icon2", | ||
"to": "icon3", | ||
"label": "rotate secret" | ||
} | ||
} | ||
} |
Oops, something went wrong.