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
CfnOutput
s 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!
Top comments (0)