Provision an RDS Instance using the AWS CDK and Secrets

Provision an RDS Instance using the AWS CDK and Secrets

Β·

5 min read

TLDR: Checkout the complete code on GitHub Repo.

🐦 Follow me on Twitter if you would like to see more content like this! 🐦

Introduction

Today, I am going to show you how to provision an RDS instance using the AWS CDK. We will set up an AWS Secret and System Parameter that can be used to allow other resources to connect without using plaintext credentials. Never keep database credentials in plaintext!

Table Of Contents

Check Out a Starter Project

We are going to start from my last post, here is the GitHub Repo. We want to start with a base stack. A base stack is where you should keep your stateful resources. Since a database cannot be easily re-constructed you should consider this stateful. We will be updating the base stack by adding an RDS database.

Update Dependencies

First, we need to update the dependencies in package.json. We are going to need some new dependencies for this tutorial. Note, you should try to keep all the CDK dependency versions the same.

  "dependencies": {
    ...
    "@aws-cdk/aws-rds": "1.95.1",
    "@aws-cdk/aws-secretsmanager": "1.95.1",
    "@aws-cdk/aws-ssm": "1.95.1"
  }

Add a Database Secret

Next, we will start to update the base stack! First, we need to set up a secret and a system parameter. You should never keep plaintext credentials in plaintext or in source control. The AWS Secrets Manager allows you to provide credentials to a number of other AWS resources in a secure way. Let's add the secret.

    // first, lets generate a secret to be used as credentials for our database
    const databaseCredentialsSecret = new secretsManager.Secret(this, `${props?.stage}-DBCredentialsSecret`, {
      secretName: `${props?.stage}-credentials`,
      generateSecretString: {
        secretStringTemplate: JSON.stringify({
          username: 'postgres',
        }),
        excludePunctuation: true,
        includeSpace: false,
        generateStringKey: 'password'
      }
    });    

    // lets output a few properties to help use find the credentials 
    new cdk.CfnOutput(this, 'Secret Name', { value: databaseCredentialsSecret.secretName }); 
    new cdk.CfnOutput(this, 'Secret ARN', { value: databaseCredentialsSecret.secretArn }); 
    new cdk.CfnOutput(this, 'Secret Full ARN', { value: databaseCredentialsSecret.secretFullArn || '' });

A couple of notes here:

  • This is going to generate a password for the username postgres
  • We are using a few CfnOutputs to print some of the secret resource values. This will allow you to either identify which secret we just created or be able to access it via the AWS CLI. Note, this will not actually output a password! These values will be output in the terminal when you deploy your infrastructure.

Create a System Parameter

Next, we can simply create a system parameter with the secret. You will use this service to provide other AWS services with credentials to connect to RDS.

    // next, create a new string parameter to be used
    new ssm.StringParameter(this, 'DBCredentialsArn', {
      parameterName: `${props?.stage}-credentials-arn`,
      stringValue: databaseCredentialsSecret.secretArn,
    });

Load Default Security Group

Our next step will be to get the VPC default security group. The database should be in a security group so that we can set up connectivity between other resources deployed in a VPC.

    // get the default security group
    let defaultSecurityGroup = SecurityGroup.fromSecurityGroupId(this, "SG", vpc.vpcDefaultSecurityGroup);

Optionally Open Access From Your IP

You can optionally add a rule to allow access to the database from your IP. NOTE THIS IS NOT SECURE! You should always put your database in a private subnet! This part is for educational purposes.

    if(props?.yourIpAddres){
      // your to access your RDS instance!
      defaultSecurityGroup.addIngressRule(ec2.Peer.ipv4(props.yourIpAddres), ec2.Port.tcp(5432), 'allow 5432 access from my IP');
    }

Configure RDS Instance

Now, let us configure and create our RDS instance.

    // finally, lets configure and create our database!
    const rdsConfig: rds.DatabaseInstanceProps = {
      engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }),
      // optional, defaults to m5.large
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL),
      vpc,
      // make the db publically accessible
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
      instanceIdentifier: `${props?.stage}`,
      maxAllocatedStorage: 200,
      securityGroups: [defaultSecurityGroup],
      credentials: rds.Credentials.fromSecret(databaseCredentialsSecret), // Get both username and password from existing secret
    }

    // create the instance
    this.rdsInstance = new rds.DatabaseInstance(this, `${props?.stage}-instance`, rdsConfig);

A couple of notes here:

  • We are putting this into a public subnet. THIS IS NOT SECURE. You should always put your database in a private subnet. We are doing this for educational purposes.
  • I am creating the smaller database possible to save on costs.

Output RDS Endpoint

Lastly, let's output the RDS endpoint so you can actually connect publically. Again, this is just for educational purposes. ALWAYS KEEP YOUR DATABASE IN A PRIVATE SUBNET.

    // output the endpoint so we can connect!
    new cdk.CfnOutput(this, 'RDS Endpoint', { value: this.rdsInstance.dbInstanceEndpointAddress });

Deploy

That's it! Synthesize and deploy your code and you will have a provisioned database! This may take some time to come up.

cdk synth
cdk deploy

Conclusion

To get the credentials to connect to your new database you can either use the CLI using the CfnOutput or you can manually find it in your AWS Secrets Manager console. In my subsequent posts, I will be using the RDS tutorial as a starting point for other resources to access it. Enjoy! 🍺

TLDR: Checkout the GitHub Repo with the complete code.

🐦 Follow me on Twitter if you would like to see more content like this! 🐦

Interested in having a 1:1 chat with me over this story, or AWS in general? Head over to Hire The Author and let’s connect!

Did you find this article valuable?

Support Phillip Ninan by becoming a sponsor. Any amount is appreciated!