Automated Database User Management on AWS with Terraform and AWS Lambda

Oguzhan
3 min readJun 24, 2023

--

Database User Management with Terraform and Lambda

In our business, we have many databases and many users want to access these databases. We use Terraform when any team/project needs a new database or product wants to release. I mean this is not a generic problem, but our infrastructure is completely serving private networks, and we don’t want to allow terraform IP addresses to our private network. Basically, we need some features for database access management. Let's look.

Note: This infra is designed for multi-accounts on AWS with Active Directory.

The above design explains how its works.

Base Formula

  • When Terraform created a new Database with a resource or module (it does not matter) lambda invoke triggering with depends rds create the resource.
  • Invoke resource call lambda function and lambda function access to the new database with root credentials and create restricted app user and password.
  • lambda function also can create IAM-authenticated users with restrictions.
  • When the lambda function succeeds, it saves app user credentials to the related SSM parameter store and returns a lambda response with SSM parameter paths.
  • When terraform invoke function is successful, terraform gets ssm parameter paths from the output, and puts them into the related application config.

Lambda Flow

First I want to talk about the lambda function pseudocode.

def handler(event):
dbConnection = connectDB(event.dbtype, event.dbname, event.dbendpoint)
username = generateRandomUsername()
password = generateRandomPassword()

appUser = {
username: username,
password: password
}

ssmPath = saveToSSM(appUser)

adUsers = getActiveDirectoryUsers(event.groups)
grantDatabaseAccess(adUsers, event.dbname)

createRDSDatabaseUser(username, password)

RETURN ssmPath

Let’s explain it.

  1. When Terraform triggers this function, send JSON input to this handler.
  2. It establishes a database connection using the provided database type (event.dbtype), database name (event.dbname), and database endpoint (event.dbendpoint).
  3. It generates a random username and password using the generateRandomUsername and generateRandomPassword functions.
  4. It creates an appUser object containing the generated username, password
  5. The appUser object is then saved to the AWS Systems Manager (SSM) using the saveToSSM function.
  6. The function retrieves the list of users belonging to the specified user groups from the Active Directory using the getActiveDirectoryUsers function.
  7. It grants the appropriate database access to the Active Directory users by calling the grantDatabaseAccess function with the list of users and the database name as parameters.
  8. It creates an RDS database user with the generated username and password using the createRDSDatabaseUser function.
  9. Finally, the ssmPath object is returned as the output of the lambda function.

Terraform Flow

Terraform side is simple. We need just the aws_lambda_invocation resource for the trigger function.

resource "aws_lambda_invocation" "invoke" {
count = var.enable_rds_lambda_invoke ? 1 : 0
function_name = var.auto_permission_function_name

input = jsonencode({
dbname = var.db_name
username = var.db_master_username
password = var.db_master_password
dbtype = var.db_type
account = var.aws_account_id
env = var.env
project = var.project
appname = var.appname
dbendpoint = var.db_endpoint
groups = var.iam_user_groups
})

depends_on = [ module.rds ]
}

output "result_entry" {
value = aws_lambda_invocation.invoke.*.result
}

Actually, I modulated this resource for simple calling.

Note: Terraform AWS Provider 5.1.0 and later versions support lifecycle argument, which means you can invoke this function each time or just once whatever you want. https://registry.terraform.io/providers/hashicorp/aws/5.1.0/docs/resources/lambda_invocation#lifecycle_scope

When Terraform call this resource, and function response returns, like this;

{
request_id : "",
parameter_names :
{
parameters :[
"/application/DB_USERNAME"
"/application/DB_PASSWORD"
]
}
}

Now we can use these parameters with output resource.

Thank you. Hope this post will be helpful when you need it one day.

--

--

Oguzhan

Solutions Architect, #AWS, food, chess, books and cheese lover. Philosophy 101.