In this note, I discuss creating an Amazon RDS for PostgreSQL DB using Terraform and securely automating the provisioning process using GitHub Actions. By the end of this note, you will learn about the underlying architectural dependencies and specific properties needed to create a secure RDS for PostgreSQL DB using Terraform. Per AWS-Docs, Amazon Relational Database Service (Amazon RDS) is a web service that makes it easier to set up, operate, and scale a relational database in the AWS Cloud. It provides cost-efficient, resizable capacity for an industry-standard relational database and manages common database administration tasks. Amazon RDS supports several database engines, such as IBM Db2, MariaDB, Microsoft SQL Server, MySQL, Oracle Database, and PostgreSQL. In this note, I will focus on PostgreSQL.

Terraform is a popular infrastructure-as-code(IaC) tool that enables developers and cloud engineering teams to write code in HashiCorp Configuration Language (HCL) to provision cloud infrastructure. An IaC tool allows for consistent and repeatable infrastructure deployment, reduces the risk of human error, and enhances collaboration by enabling version control and automation of infrastructure changes.

Furthermore, GitHub Actions automates the provisioning of AWS cloud resources using Terraform. GitHub Actions is a powerful automation platform that allows developers to define workflows for continuous integration and continuous deployment (CI/CD) within their GitHub repositories. It facilitates the automation of various tasks, including deploying and managing cloud infrastructure, by running predefined scripts and actions in response to code changes or other triggers.

Over the next several minutes, I’ll explore all the Terraform code for the AWS cloud resources required to provision an Amazon RDS for PostgreSQL.

There are a few prerequisites before creating an Amazon RDS for PostgreSQL DB. These are: 1. Create an Amazon VPC and Subnets to host the RDS DB instance 2. Create an AWS KMS key and policy to encrypt the cloud resources 3. Create a Security Group to control access to the Amazon RDS DB instance 4. Create an IAM role for enhanced monitoring of the Amazon RDS DB instance 5. Create a subnet group and parameter group for the Amazon RDS DB instance After all these resources are provisioned, the last step is 6. Create an Amazon RDS for PostgreSQL DB instance.

If you want to follow along, here is a link to my GitHub repository to access the Terraform code GitHub: kunduso/rds-secretsmanager-rotation-lambda-terraform. Please note that the branch name = add-rds-db-instance.

Step 1: Create the network stack (VPC and Subnets) 101-image-1 An Amazon Virtual Private Cloud (VPC) hosts the Amazon RDS DB instance. By launching the Amazon RDS instance inside a VPC, the cloud engineering team can manage secure access to the instance. This use case did not require a large CIDR, so I allocated one with a /26 range (64 IPs, much more than my requirement). I also created two subnets inside the VPC and a route for the local traffic (inside the VPC). I did not attach any NAT or Internet Gateway to the VPC.

Step 2: Create an AWS KMS key and policy The AWS KMS key encrypts the data stored in the Database and the password to access it. Encrypting the data ensures that only those with the key can access it. 101-image-2 I also followed security best practices by creating a specific KMS Key policy that provides particular service principals access to these specific actions over the KMS key. I expanded one of the KMS key policy statements for your examination. 101-image-3 The complete code is in the GitHub repository.

Step 3: Create a Security Group to control access The Amazon RDS DB instance is configured to listen on port# 5432, so I enabled the ingress rule on that port in the security group below. This security group is then attached to the Amazon RDS DB instance, which allows communication. 101-image-4 Step 4: Create an IAM role and attach a managed policy for enhanced monitoring If you are new to Enhanced monitoring in Amazon RDS, please refer to this detailed document on AWS-Docs. This service can help cloud engineers determine the proper sizing of their Amazon RDS DB instance to manage application availability and control cost. Enhanced monitoring is managed by two properties: (i) setting the monitoring_interval property in the Amazon RDS resource and (ii) creating and attaching an IAM role to send the Amazon RDS instance OS metrics to Amazon CloudWatch. This step is to create the IAM role and attach a managed IAM policy that has permission to send logs to Amazon CloudWatch. 101-image-5 Step 5: Create a DB subnet group and a parameter group for the Amazon RDS DB instance A DB subnet group is a list of subnets inside a VPC that can host the Amazon RDS DB instance. Since I required a multi-AZ deployment of the Amazon RDS DB instance, these subnets had to be in two separate availability zones. Here is a link to Amazon Docs for a detailed explanation. 101-image-6 An Amazon RDS parameter group is a container for engine configuration values for Amazon RDS instances. By modifying a parameter group across multiple instances, cloud engineers can easily customize database parameters, such as memory allocation and query performance. 101-image-7 Amazon RDS provides default parameter groups, but creating a separate one for the project’s unique requirements is better.

Step 6: Create an Amazon RDS for PostgreSQL The last step is to create the Amazon RDS DB instance. As you can examine from the image below, this is the AWS cloud resource where we specify the RDS DB properties such as allocated_storage, storage_type, engine, and engine_version. We also specify references to previously created resources, such as the subnet group name, the security group, the parameter group, and the KMS key. You will also notice that we followed security best practices and enabled multi_az, storage_encrypted, iam_database_authentication_enabled, and deletion_protection. I also learned that as of December 2022, Amazon RDS manages the password (creation and rotation), which frees up the cloud engineering team from managing that. You can read more about that at amazon-rds-secrets-manager. 101-image-8 For a full list of all the properties, please refer to the Terraform AWS Provider resource page.

That is all the Terraform code required to create the Amazon RDS for PostgreSQL. However, since Amazon RDS created the master user password (configured via the manage_master_user_password property in the aws_db_instance resource), storing the reference to programmatically access it later is a valuable practice. These are not necessary to create the Amazon RDS for PostgreSQL instance but are required to use it.

Best practice 1: Store the Amazon RDS for PostgreSQL information as an AWS Systems Manager Parameter parameter. Any application using the Amazon RDS DB will access it on the endpoint. Hence, the application would require that. Although you can embed values in the application, an alternate approach is to store these as encrypted values in the Systems Manager Parameter Store as parameters and provide permissions to the application to fetch and decrypt them from there. That way, hardcoding the values is not required, and the application stays clear of confidential data.

I created two resources: an aws_ssm_parameter to store the RDS endpoint, the port#, and the ARN of the AWS Secrets Manager secret and an aws_iam_policy to enable access to the Systems Manager Parameter Store parameter. 101-image-9 Best practice 2: Create an IAM policy to access the Amazon RDS for PostgreSQL information from the Systems Manager Parameter Store. After storing the Amazon RDS information as a Systems Manager parameter, I created an IAM policy that allows access to that. An application would require the same IAM policy to be attached to the role it assumes to communicate with the Systems Manager parameter. After attaching the IAM policy, the application can communicate with the Systems Manager parameter and get the values of the Amazon RDS for PostgreSQL endpoint, port#, and the AWS Secrets Manager ARN. 101-image-10 Since these are confidential values, encrypting and decrypting them using a KMS Key, as mentioned above, is a good practice.

Finally, I also automated the provisioning of the AWS resources using GitHub Actions. If you want to learn how I managed the authentication process, head over to this note –securely-integrate-aws-credentials-with-github-actions-using-openid-connect. I also scanned my code for vulnerabilities using Bridgecrew Checkov, which generated a scan report. Here is a note on how to enable Checkov with GitHub Actions. I’m also interested in the cost of these resources and use Infracost for that, which I have covered in detail in this note –estimate AWS resource cost. The vulnerabilities and cost estimate are available in the pull request as comments.

Once I added all the Terraform code for the resources and their configurations, I pushed the changes to my remote repository. The GitHub Actions workflow concluded with the command terraform apply, which enabled and provisioned the resources. This took 15-17 minutes to provision.

Then, on the AWS Management Console, I navigated to RDS → Databases and selected the Database I created in this GitHub repository. The image below is part of the Amazon RDS DB instance. 101-image-11 That brings us to the end of this note. Since you have access to the GitHub code repository, fork it and try to provision the AWS cloud resources. Let me know if you have any questions or suggestions.