Automated Database User Management on AWS with Terraform and AWS 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.
- When Terraform triggers this function, send JSON input to this handler.
- It establishes a database connection using the provided database type (
event.dbtype
), database name (event.dbname
), and database endpoint (event.dbendpoint
). - It generates a random username and password using the
generateRandomUsername
andgenerateRandomPassword
functions. - It creates an
appUser
object containing the generated username, password - The
appUser
object is then saved to the AWS Systems Manager (SSM) using thesaveToSSM
function. - The function retrieves the list of users belonging to the specified user groups from the Active Directory using the
getActiveDirectoryUsers
function. - 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. - It creates an RDS database user with the generated username and password using the
createRDSDatabaseUser
function. - 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.