├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── db_manager.py ├── lambda_function.py ├── requirements.txt ├── secret_policy_manager.py ├── secret_rotation_manager.py ├── secret_update_manager.py └── ssm-automation.yaml /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Centralized Database User Management using AWS Lambda and SSM 2 | 3 | This project provides an AWS Lambda function to manage MySQL database users. The Lambda function can be used to create, delete, update grants, or reset the password of a user. Additionally, it integrates with AWS Secrets Manager for secret management. The function is designed to be triggered via an AWS Systems Manager (SSM) Automation document. 4 | 5 | # Features 6 | 7 | | Action | Description | 8 | |---------------------|-------------------------------------------------------------------| 9 | | User Creation | Create a new MySQL database user. | 10 | | User Deletion | Delete an existing MySQL database user and its associated secret. | 11 | | Update User Grant | Update privileges of an existing MySQL database user. | 12 | | Reset User Password | Reset password for an existing MySQL database user. | 13 | 14 | 15 | ## Security 16 | 17 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 18 | 19 | ## License 20 | 21 | This library is licensed under the MIT-0 License. See the LICENSE file. 22 | 23 | -------------------------------------------------------------------------------- /db_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import string 3 | import secrets 4 | 5 | logger = logging.getLogger() 6 | logger.setLevel(logging.INFO) 7 | 8 | def db_exists(cursor, db_name): 9 | logger.info(f"Checking if db {db_name} exists") 10 | query = "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = %s" 11 | cursor.execute(query, (db_name,)) 12 | #cursor.execute(f"select COUNT(*) from information_schema.schemata where schema_name = '{db_name}'") 13 | return cursor.fetchone()[0] > 0 14 | 15 | def user_exists(cursor, username): 16 | logger.info(f"Checking if user {username} exists") 17 | query = "SELECT COUNT(*) FROM mysql.user WHERE user = %s" 18 | cursor.execute(query, (username,)) 19 | #cursor.execute(f"SELECT COUNT(*) FROM mysql.user WHERE user = '{username}'") 20 | return cursor.fetchone()[0] > 0 21 | 22 | def generate_password(length=16): 23 | characters = string.ascii_letters + string.digits + string.punctuation 24 | password = ''.join(secrets.choice(characters) for i in range(length)) 25 | return password 26 | 27 | 28 | def create_user(cursor, db_name, username): 29 | password = generate_password() 30 | logger.info(f"Creating new user: {username} with an auto-generated password") 31 | cursor.execute(f"CREATE USER '{username}'@'%' IDENTIFIED BY '{password}';") 32 | logger.info(f"User {username} created successfully") 33 | return password 34 | 35 | 36 | def delete_user(cursor, username): 37 | logger.info(f"Deleting user: {username}") 38 | cursor.execute(f"DROP USER '{username}'@'%'") 39 | cursor.execute("FLUSH PRIVILEGES;") 40 | logger.info(f"User {username} deleted successfully") 41 | 42 | def grant_privileges(cursor, role, db_name, username): 43 | logger.info(f"Granting {role} privileges to user {username}") 44 | if role == 'admin': 45 | cursor.execute(f"GRANT ALL PRIVILEGES ON `{db_name}`.* TO '{username}'@'%';") 46 | elif role == 'readwrite': 47 | cursor.execute(f"GRANT SELECT, INSERT, UPDATE, DELETE ON `{db_name}`.* TO '{username}'@'%';") 48 | elif role == 'readonly': 49 | cursor.execute(f"GRANT SELECT ON `{db_name}`.* TO '{username}'@'%';") 50 | else: 51 | raise ValueError("Invalid role specified") 52 | cursor.execute("FLUSH PRIVILEGES;") 53 | logger.info("Privileges granted successfully") 54 | 55 | def list_users(cursor, username=None): 56 | try: 57 | # If a specific username is provided, fetch its grants 58 | if username and username != 'list_all_users': 59 | cursor.execute(f"SHOW GRANTS FOR '{username}'@'%';") 60 | grants = cursor.fetchall() 61 | if not grants: 62 | logger.info(f"No grants found for user {username}.") 63 | return [] 64 | return [grant[0] for grant in grants] 65 | 66 | # If 'list_all_users' keyword or no username is provided, list all users 67 | if not username or username == 'list_all_users': 68 | cursor.execute("SELECT User FROM mysql.user;") 69 | users = cursor.fetchall() 70 | 71 | if not users: 72 | logger.info("No users found in the database.") 73 | return [] 74 | 75 | # Extract usernames from the query result 76 | usernames = [user[0] for user in users] 77 | return usernames 78 | 79 | except pymysql.MySQLError as e: 80 | logger.error(f"Error in list_users: {e}") 81 | raise 82 | 83 | 84 | def reset_password(cursor, username): 85 | new_password = generate_password() # Using the generate_password function defined earlier 86 | try: 87 | logger.info(f"Resetting password for user {username}") 88 | cursor.execute(f"ALTER USER '{username}'@'%' IDENTIFIED BY '{new_password}';") 89 | cursor.execute("FLUSH PRIVILEGES;") 90 | logger.info(f"Password reset successfully for user {username}") 91 | return new_password 92 | except pymysql.MySQLError as e: 93 | logger.error(f"Error resetting password for user {username}: {e}") 94 | raise 95 | -------------------------------------------------------------------------------- /lambda_function.py: -------------------------------------------------------------------------------- 1 | import json 2 | import string 3 | import boto3 4 | import pymysql 5 | import logging 6 | import datetime 7 | import os 8 | from botocore.exceptions import ClientError 9 | from secret_policy_manager import update_secret_policy_template 10 | from secret_rotation_manager import enable_secret_rotation 11 | from secret_update_manager import update_secret_policy, update_secret_rotation 12 | from db_manager import create_user, delete_user, grant_privileges, user_exists, generate_password, list_users, reset_password, db_exists 13 | 14 | 15 | 16 | # Configure logging 17 | logger = logging.getLogger() 18 | logger.setLevel(logging.INFO) 19 | 20 | def test_db_connectivity(db_credentials): 21 | try: 22 | # Connect to the database using the new credentials 23 | connection = pymysql.connect(host=db_credentials['host'], 24 | user=db_credentials['username'], 25 | password=db_credentials['password'], 26 | db=db_credentials['db_name'], 27 | port=int(db_credentials['port']), 28 | ssl_verify_identity=True) 29 | cursor = connection.cursor() 30 | 31 | # Query to get the current date and time 32 | cursor.execute("SELECT CURRENT_TIMESTAMP;") 33 | current_time = cursor.fetchone() 34 | logger.info(f"Current time in database: {current_time[0]}") 35 | 36 | # Query to get the current user 37 | cursor.execute("SELECT USER();") 38 | current_user = cursor.fetchone() 39 | logger.info(f"Current user in database: {current_user[0]}") 40 | 41 | # Close the database connection 42 | cursor.close() 43 | connection.close() 44 | 45 | return True, "Database connectivity test successful." 46 | 47 | except pymysql.MySQLError as e: 48 | logger.error(f"Connectivity test failed: {e}") 49 | return False, f"Database connectivity test failed: {e}" 50 | 51 | def format_as_row(items): 52 | if not items: 53 | return "No items found." 54 | 55 | # Join items with a comma and a space for row-wise display 56 | return ", ".join(items) 57 | 58 | 59 | def lambda_handler(event, context): 60 | try: 61 | # Extract details from the event 62 | rds_secret_name = event['rds_secret_name'] 63 | action = event.get('action') 64 | username = event['username'] 65 | db_name = event['db_name'] 66 | secrets_manager = boto3.client('secretsmanager') 67 | 68 | master_credentials = json.loads(secrets_manager.get_secret_value(SecretId=rds_secret_name)['SecretString']) 69 | 70 | # Connect to the RDS database 71 | connection = pymysql.connect(host=master_credentials['host'], 72 | user=master_credentials['username'], 73 | password=master_credentials['password'], 74 | db='mysql', 75 | ssl_verify_identity=True) 76 | cursor = connection.cursor() 77 | if not db_exists(cursor,db_name) : 78 | return {'statusCode': 400, 'body': json.dumps(f'Database {db_name} does not exist')} 79 | if action == 'list_users': 80 | username = event.get('username', 'list_all_users') 81 | user_list = list_users(cursor, username) 82 | row_output = format_as_row(user_list) 83 | return {'statusCode': 200, 'body': json.dumps(row_output)} 84 | 85 | 86 | 87 | if action == 'create_user': 88 | if user_exists(cursor, username): 89 | return {'statusCode': 400, 'body': json.dumps(f'User {username} already exists.')} 90 | 91 | role = event.get('role', 'readonly') 92 | password = create_user(cursor, db_name, username) 93 | grant_privileges(cursor, role, db_name, username) 94 | 95 | 96 | # Get the current date and time in a formatted string 97 | current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 98 | 99 | # Store the new user's details in Secrets Manager 100 | new_secret_name = f"rds-{db_name}-{username}-secret" 101 | new_secret_description = f"Credentials for '{username}' on RDS database '{db_name}'." 102 | new_secret_tags = [ 103 | {'Key': 'create_by_user_automation', 'Value': 'true'}, 104 | {'Key': 'creation_timestamp', 'Value': current_time} 105 | ] 106 | new_secret_value = { 107 | 'username': username, 108 | 'password': password, 109 | 'host': master_credentials['host'], 110 | 'port': master_credentials['port'], 111 | 'db_name': db_name, 112 | 'engine': 'mysql' 113 | } 114 | try: 115 | secret_response = secrets_manager.create_secret(Name=new_secret_name, 116 | Description=new_secret_description, 117 | SecretString=json.dumps(new_secret_value), 118 | Tags=new_secret_tags) 119 | new_secret_arn = secret_response['ARN'] 120 | logger.info("Created Secret to store the credentials : " + new_secret_arn) 121 | except ClientError as e: 122 | # If secret creation fails, delete the newly created user from the database 123 | logger.error(f"Failed to create secret in Secrets Manager: {e}") 124 | delete_user(cursor, username) 125 | cursor.close() 126 | connection.close() 127 | return {'statusCode': 500, 'body': json.dumps('Failed to create secret in Secrets Manager')} 128 | 129 | 130 | # Check if secret rotation is enabled and Rotation Lambda ARN is provided 131 | try: 132 | rotation_lambda_arn = os.environ['ROTATION_LAMBDA_ARN'] 133 | logger.info("The rotation lambda arn is " + rotation_lambda_arn) 134 | rotation_days = os.environ.get('ROTATION_DAYS', 30) 135 | # logger.info("The rotation days are " , str(rotation_days)) 136 | enable_secret_rotation(new_secret_name, rotation_lambda_arn, rotation_days) 137 | logger.info(f"Secret rotation enabled with Lambda ARN: {rotation_lambda_arn}") 138 | except KeyError: 139 | logger.info("Secret rotation ARN not provided. Secret rotation is not enabled.") 140 | 141 | # Update and apply the resource policy to the new secret 142 | roles_to_add = event.get('roles', []) 143 | account_id = context.invoked_function_arn.split(":")[4] 144 | updated_policy = update_secret_policy_template(roles_to_add, account_id, new_secret_arn) 145 | secrets_manager.put_resource_policy(SecretId=new_secret_name, ResourcePolicy=updated_policy) 146 | logger.info(f"Updated resource policy for secret: {new_secret_name}") 147 | 148 | # Conduct a connectivity test if required 149 | if event.get('connectivity_test', False): 150 | test_result, message = test_db_connectivity(new_secret_value) 151 | if not test_result: 152 | return {'statusCode': 500, 'body': json.dumps(message)} 153 | logger.info(message) 154 | 155 | elif action == 'password_reset': 156 | secret_name = event.get('secret_name') 157 | if not secret_name: 158 | cursor.close() 159 | connection.close() 160 | return {'statusCode': 400, 'body': json.dumps('Secret name is required for password reset.')} 161 | 162 | secret = json.loads(secrets_manager.get_secret_value(SecretId=secret_name)['SecretString']) 163 | username = secret['username'] 164 | 165 | if not user_exists(cursor, username): 166 | cursor.close() 167 | connection.close() 168 | return {'statusCode': 404, 'body': json.dumps(f'User {username} does not exist.')} 169 | 170 | new_password = reset_password(cursor, username) 171 | secret['password'] = new_password 172 | secrets_manager.update_secret(SecretId=secret_name, SecretString=json.dumps(secret)) 173 | cursor.close() 174 | connection.close() 175 | return {'statusCode': 200, 'body': json.dumps(f'Password reset successfully for user {username}.')} 176 | 177 | elif action == 'delete_user': 178 | # Check if user exists before attempting to delete 179 | if not user_exists(cursor, username): 180 | return {'statusCode': 400, 'body': json.dumps(f'User {username} does not exist.')} 181 | 182 | # Get the secret metadata from Secrets Manager 183 | secret_name = f"rds-{db_name}-{username}-secret" 184 | try: 185 | secret_metadata = secrets_manager.describe_secret(SecretId=secret_name) 186 | except secrets_manager.exceptions.ResourceNotFoundException: 187 | # Secret does not exist, do not proceed with deleting the user 188 | logger.info(f"No secret found for user {username}, skipping delete operation.") 189 | return {'statusCode': 200, 'body': json.dumps(f'User {username} not deleted as no associated secret was found.')} 190 | 191 | # Check if the user was created by this automation 192 | created_by_automation = any(tag['Key'] == 'create_by_user_automation' and tag['Value'] == 'true' for tag in secret_metadata.get('Tags', [])) 193 | 194 | if created_by_automation: 195 | # Proceed with deleting the user and the corresponding secret 196 | delete_user(cursor, username) 197 | secrets_manager.delete_secret(SecretId=secret_name, ForceDeleteWithoutRecovery=True) 198 | logger.info(f"Deleted the secret and user {username}") 199 | return {'statusCode': 200, 'body': json.dumps(f'User {username} and associated secret deleted successfully.')} 200 | else: 201 | return {'statusCode': 400, 'body': json.dumps(f'User {username} was not created by this automation and cannot be deleted.')} 202 | 203 | elif action == 'update_secret': 204 | secret_id = event.get('secret_name') 205 | 206 | # Update resource policy if provided 207 | if 'policy' in event: 208 | policy = event['policy'] 209 | update_secret_policy(secret_id, policy) 210 | logger.info(f"Resource policy updated for secret: {secret_id}") 211 | 212 | # Update rotation settings if provided 213 | if 'rotation_lambda_arn' in event and 'rotation_days' in event: 214 | rotation_lambda_arn = event['rotation_lambda_arn'] 215 | rotation_days = int(event['rotation_days']) 216 | update_secret_rotation(secret_id, rotation_lambda_arn, rotation_days) 217 | logger.info(f"Rotation settings updated for secret: {secret_id}") 218 | 219 | else: 220 | logger.warning(f"Invalid action specified: {action}") 221 | return {'statusCode': 400, 'body': json.dumps('Invalid action specified.')} 222 | 223 | cursor.close() 224 | connection.close() 225 | return {'statusCode': 200, 'body': json.dumps(f'Action {action} completed successfully for user {username}')} 226 | 227 | except ClientError as e: 228 | logger.error(f"ClientError: {e}") 229 | return {'statusCode': 500, 'body': json.dumps('AWS service error')} 230 | except pymysql.MySQLError as e: 231 | logger.error(f"MySQLError: {e}") 232 | return {'statusCode': 500, 'body': json.dumps('MySQL error')} 233 | except Exception as e: 234 | logger.error(f"Exception: {e}") 235 | return {'statusCode': 500, 'body': json.dumps('An unknown error occurred')} 236 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pymysql 2 | -------------------------------------------------------------------------------- /secret_policy_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def update_secret_policy_template(role_arns, account_id, new_secret_arn): 4 | policy_template = { 5 | "Version": "2012-10-17", 6 | "Statement": [ 7 | { 8 | "Effect": "Allow", 9 | "Principal": { 10 | "AWS": f"arn:aws:iam::{account_id}:root" 11 | }, 12 | "Action": "secretsmanager:*", 13 | "Resource": new_secret_arn 14 | } 15 | ] 16 | } 17 | 18 | for role_arn in role_arns: 19 | role_statement = { 20 | "Effect": "Allow", 21 | "Principal": { 22 | "AWS": role_arn 23 | }, 24 | "Action": "secretsmanager:GetSecretValue", 25 | "Resource": new_secret_arn 26 | } 27 | policy_template['Statement'].append(role_statement) 28 | 29 | return json.dumps(policy_template) 30 | -------------------------------------------------------------------------------- /secret_rotation_manager.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import logging 3 | 4 | logger = logging.getLogger() 5 | logger.setLevel(logging.INFO) 6 | 7 | def enable_secret_rotation(secret_id, rotation_lambda_arn, rotation_days): 8 | try: 9 | secrets_manager = boto3.client('secretsmanager') 10 | rotation_rules = {"AutomaticallyAfterDays": int(rotation_days)} 11 | secrets_manager.rotate_secret( 12 | SecretId=secret_id, 13 | RotationLambdaARN=rotation_lambda_arn, 14 | RotationRules=rotation_rules 15 | ) 16 | logger.info(f"Enabled rotation for secret: {secret_id}") 17 | 18 | except Exception as e: 19 | logger.error(f"Error enabling secret rotation: {e}") 20 | raise 21 | -------------------------------------------------------------------------------- /secret_update_manager.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import logging 3 | 4 | logger = logging.getLogger() 5 | logger.setLevel(logging.INFO) 6 | 7 | def update_secret_policy(secret_id, policy): 8 | try: 9 | secrets_manager = boto3.client('secretsmanager') 10 | secrets_manager.put_resource_policy(SecretId=secret_id, ResourcePolicy=policy) 11 | logger.info(f"Updated resource policy for secret: {secret_id}") 12 | 13 | except Exception as e: 14 | logger.error(f"Error updating secret policy: {e}") 15 | raise 16 | 17 | def update_secret_rotation(secret_id, rotation_lambda_arn, rotation_days): 18 | try: 19 | secrets_manager = boto3.client('secretsmanager') 20 | rotation_rules = {"AutomaticallyAfterDays": rotation_days} 21 | 22 | secrets_manager.rotate_secret( 23 | SecretId=secret_id, 24 | RotationLambdaARN=rotation_lambda_arn, 25 | RotationRules=rotation_rules 26 | ) 27 | logger.info(f"Updated rotation schedule for secret: {secret_id}") 28 | 29 | except Exception as e: 30 | logger.error(f"Error updating secret rotation: {e}") 31 | raise 32 | -------------------------------------------------------------------------------- /ssm-automation.yaml: -------------------------------------------------------------------------------- 1 | description: Invoke Lambda Function with Multiple Inputs 2 | schemaVersion: '0.3' 3 | parameters: 4 | LambdaFunctionName: 5 | type: String 6 | description: The Name Of The Lambda Function To Invoke 7 | DatabaseUserName: 8 | type: String 9 | description: Database User Name 10 | DBName: 11 | type: String 12 | description: Name Of The Database 13 | MasterUserSecretName: 14 | type: String 15 | description: Name Of The Master User Secret In the DB Account 16 | GrantType: 17 | type: String 18 | description: Grant Type 19 | default: '' 20 | allowedValues: 21 | - admin 22 | - readwrite 23 | - readonly 24 | - '' 25 | Action: 26 | type: String 27 | description: Action To Be Performed 28 | allowedValues: 29 | - create_user 30 | - delete_user 31 | - update_grant 32 | - reset_password 33 | mainSteps: 34 | - name: invokeLambda 35 | action: aws:invokeLambdaFunction 36 | isEnd: true 37 | inputs: 38 | FunctionName: '{{ LambdaFunctionName }}' 39 | Payload: '{"username":"{{ DatabaseUserName }}", "role":"{{ GrantType }}", "rds_secret_name":"{{ MasterUserSecretName }}", "db_name":"{{ DBName }}", "action":"{{ Action }}"}' 40 | --------------------------------------------------------------------------------