diff --git a/automate-secrets-manager-tags/.gitignore b/automate-secrets-manager-tags/.gitignore new file mode 100644 index 000000000..8fe328852 --- /dev/null +++ b/automate-secrets-manager-tags/.gitignore @@ -0,0 +1,11 @@ +*.swp +package-lock.json +__pycache__ +.pytest_cache +.venv +*.egg-info +source.bat + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/automate-secrets-manager-tags/README.md b/automate-secrets-manager-tags/README.md new file mode 100644 index 000000000..a8e17bfe3 --- /dev/null +++ b/automate-secrets-manager-tags/README.md @@ -0,0 +1,90 @@ + +# Auto-tag creator's username to AWS Secrets Manager entries + +Implements automatic tagging of AWS Secrets Manager entries with the creator's username. When users authenticated via AWS IAM Identity Center create secrets, their username is automatically added as a tag. This enables easier ownership tracking and management of secrets across the organization. + +Eventbridge rule is configured to look for CreateSecret events to invoke a Lambda function to tag the secret with the creator's username. + +# Deployment Instructions + +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 +``` + +Change directory to the pattern directory: + +``` +cd automate-secrets-manager-tags +``` + +To manually create a virtualenv on MacOS and Linux: + +``` +$ python3 -m venv .venv +``` + +After the init process completes and the virtualenv is created, you can use the following +step to activate your virtualenv. + +``` +$ source .venv/bin/activate +``` + +If you are a Windows platform, you would activate the virtualenv like this: + +``` +% .venv\Scripts\activate.bat +``` + +Once the virtualenv is activated, you can install the required dependencies. + +``` +$ pip install -r requirements.txt +``` + +At this point you can now synthesize the CloudFormation template for this code. + +``` +$ cdk synth +``` + +## Deploy +At this point you can deploy the stack. + +Using the default profile + +``` +$ cdk deploy +``` + +With specific profile + +``` +$ cdk deploy --profile test +``` + +## Useful commands + + * `cdk ls` list all stacks in the app + * `cdk synth` emits the synthesized CloudFormation template + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk docs` open CDK documentation + +## Testing + +To test the automated tagging of secrets in AWS Secrets Manager, we will need to use the AWS console. + +1. Login to AWS Console using AWS IAM Identity Center. +2. Navigate to AWS Secrets Manager and create a secret. +3. Open the "Tags" tab of the newly created secret to see the automated tag key=LoginUserName and value=. + +## Cleanup + +Run below script in the `automate-secrets-manager-tags` directory to delete AWS resources created by this sample stack. + +``` +cdk destroy +``` \ No newline at end of file diff --git a/automate-secrets-manager-tags/app.py b/automate-secrets-manager-tags/app.py new file mode 100644 index 000000000..098141295 --- /dev/null +++ b/automate-secrets-manager-tags/app.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +import os + +import aws_cdk as cdk + +from stacks.main import AutomateSecretsManagerTagsStack + + +app = cdk.App() +AutomateSecretsManagerTagsStack(app, "AutomateSecretsManagerTagsStack") + +app.synth() diff --git a/automate-secrets-manager-tags/automate-secrets-manager-tags.json b/automate-secrets-manager-tags/automate-secrets-manager-tags.json new file mode 100644 index 000000000..0d22634e8 --- /dev/null +++ b/automate-secrets-manager-tags/automate-secrets-manager-tags.json @@ -0,0 +1,81 @@ +{ + "title": "Automate creator username tags for AWS Secrets Manager secrets", + "description": "Automate tagging of AWS Secrets Manager secrets with the creator's username", + "language": "Python", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates the automatic tagging of username to the secrets they created when user is authenticated via AWS IAM Identity Center. This enables easier ownership tracking and management of secrets across the organization.", + "Eventbridge rule is configured to look for CreateSecret events to invoke a Lambda function to tag the secret with the creator's username." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/automate-secrets-manager-tags", + "templateURL": "serverless-patterns/automate-secrets-manager-tags", + "projectFolder": "automate-secrets-manager-tags", + "templateFile": "stacks/main.py" + } + }, + "resources": { + "bullets": [ + { + "text": "Match AWS Secrets Manager events with Amazon EventBridge", + "link": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/monitoring-eventbridge.html" + } + ] + }, + "deploy": { + "text": [ + "cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk destroy." + ] + }, + "authors": [ + { + "name": "Chan Shi Hui", + "image": "https://avatars.githubusercontent.com/u/103100403?v=4", + "bio": "Shi Hui is a Technical Account Manager working at AWS Singapore to build, run, and scale customer workloads on AWS.", + "linkedin": "chan-shi-hui-38915aa3" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "secretsmanager", + "label": "AWS Secrets Manager" + }, + "icon2": { + "x": 50, + "y": 50, + "service": "eventbridge", + "label": "Amazon EventBridge" + }, + "icon3": { + "x": 80, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "line1": { + "from": "icon1", + "to": "icon2" + }, + "line2": { + "from": "icon2", + "to": "icon3" + } + } +} diff --git a/automate-secrets-manager-tags/cdk.json b/automate-secrets-manager-tags/cdk.json new file mode 100644 index 000000000..d543fda14 --- /dev/null +++ b/automate-secrets-manager-tags/cdk.json @@ -0,0 +1,78 @@ +{ + "app": "python3 app.py", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "**/__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-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-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true + } +} diff --git a/automate-secrets-manager-tags/example-pattern.json b/automate-secrets-manager-tags/example-pattern.json new file mode 100644 index 000000000..fd7211f48 --- /dev/null +++ b/automate-secrets-manager-tags/example-pattern.json @@ -0,0 +1,53 @@ +{ + "title": "Automate creator username tags for AWS Secrets Manager secrets", + "description": "Automate tagging of AWS Secrets Manager secrets with the creator's username", + "language": "Python", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates the automatic tagging of username to the secrets they created when user is authenticated via AWS IAM Identity Center. This enables easier ownership tracking and management of secrets across the organization.", + "Eventbridge rule is configured to look for CreateSecret events to invoke a Lambda function to tag the secret with the creator's username." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/automate-secrets-manager-tags", + "templateURL": "serverless-patterns/automate-secrets-manager-tags", + "projectFolder": "automate-secrets-manager-tags", + "templateFile": "automate-secrets-manager-tags.py" + } + }, + "resources": { + "bullets": [ + { + "text": "Match AWS Secrets Manager events with Amazon EventBridge", + "link": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/monitoring-eventbridge.html" + } + ] + }, + "deploy": { + "text": [ + "cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk destroy." + ] + }, + "authors": [ + { + "name": "Chan Shi Hui", + "image": "https://avatars.githubusercontent.com/u/103100403?v=4", + "bio": "Shi Hui is a Technical Account Manager working at AWS Singapore to build, run, and scale customer workloads on AWS.", + "linkedin": "chan-shi-hui-38915aa3" + } + ] +} \ No newline at end of file diff --git a/automate-secrets-manager-tags/lambda/index.py b/automate-secrets-manager-tags/lambda/index.py new file mode 100644 index 000000000..3c2f7fa29 --- /dev/null +++ b/automate-secrets-manager-tags/lambda/index.py @@ -0,0 +1,33 @@ +import json +import boto3 + +secrets_manager_client = boto3.client('secretsmanager') + +def lambda_handler(event, context): + # Extract user info from the EventBridge event + try: + principalId = event['detail']['userIdentity']['principalId'] + print(f'principalId:{principalId}') + user_name = principalId.split(":")[1] + + except KeyError: + print("Error: Could not extract user name from event") + return + + secret_name = event['detail']['requestParameters']['name'] + + try: + # Apply a tag to the secret + secrets_manager_client.tag_resource( + SecretId=secret_name, + Tags=[ + { + 'Key': 'LoginUserName', + 'Value': user_name + } + ] + ) + print(f"Successfully tagged secret {secret_name} with username {user_name}") + except Exception as e: + print(f"Error tagging secret: {str(e)}") + raise e diff --git a/automate-secrets-manager-tags/requirements.txt b/automate-secrets-manager-tags/requirements.txt new file mode 100644 index 000000000..ff84a4000 --- /dev/null +++ b/automate-secrets-manager-tags/requirements.txt @@ -0,0 +1,2 @@ +aws-cdk-lib==2.176.0 +constructs>=10.0.0,<11.0.0 diff --git a/automate-secrets-manager-tags/stacks/main.py b/automate-secrets-manager-tags/stacks/main.py new file mode 100644 index 000000000..7e044828e --- /dev/null +++ b/automate-secrets-manager-tags/stacks/main.py @@ -0,0 +1,44 @@ +from aws_cdk import ( + Stack +) +from constructs import Construct +from aws_cdk import aws_events as events +from aws_cdk import aws_events_targets as targets +from aws_cdk import aws_lambda as _lambda +from aws_cdk import aws_iam as iam + + +class AutomateSecretsManagerTagsStack(Stack): + + def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + # Create a Lambda function to handle the tagging + lambda_function = _lambda.Function(self, "MyEventBridgeLambda", + runtime=_lambda.Runtime.PYTHON_3_13, + handler="index.lambda_handler", + code=_lambda.Code.from_asset("lambda") + ) + + # Grant the Lambda function permission to tag the secret + lambda_function.add_to_role_policy( + statement=iam.PolicyStatement( + actions=["secretsmanager:TagResource"], + resources=["arn:aws:secretsmanager:*:*:*"] + ) + ) + + # Create an EventBridge rule to capture SSO login events + # https://docs.aws.amazon.com/secretsmanager/latest/userguide/cloudtrail_log_entries.html#cloudtrail_log_entries_operations + rule = events.Rule(self, "CreateSecretsRule", + event_pattern=events.EventPattern( + source=["aws.secretsmanager"], + detail_type=["AWS API Call via CloudTrail"], + detail={ + "eventSource": ["secretsmanager.amazonaws.com"], + "eventName": ["CreateSecret"] + } + ) + ) + # Set the Lambda function as the target for the rule + rule.add_target(targets.LambdaFunction(lambda_function))