Introduction#

GitHub this note shows how to setup lambda with RDS proxy

  • Deploy lambda with dependencies
  • Lambda connect to RDS inside VPC
  • Lambda connect RDS proxy

Setup#

Project structure

|--bin
|--lambda-demo.ts
|--lambda
|--index.py
|--package
|--requirements.txt
|--lib
|--lambda-demo-stack.ts
|--config.ts

Let install libs in package dir

python3 -m pip install "psycopg[binary,pool]" --target package
python3 -m pip install psycopg --target package

Logic code to get data from RDS postgresql

# Note: the module name is psycopg, not psycopg3
import psycopg
import numpy as np
import json
dbname = "demo"
user = "postgresql"
password = "Admin2024"
host = "database-1.c9y4mg20eppz.ap-southeast-1.rds.amazonaws.com"
port = "5432"
reponse = []
# Connect to an existing database
with psycopg.connect(f"dbname={dbname} user={user} port={port} host={host} password={password}") as conn:
# Open a cursor to perform database operations
with conn.cursor() as cur:
# Execute a command: this creates a new table
cur.execute("""SELECT * FROM book """)
cur.fetchone()
# will return (1, 100, "abc'def")
# You can use `cur.fetchmany()`, `cur.fetchall()` to return a list
# of several records, or even iterate on the cursor
for record in cur:
reponse.append(record)
print(record)
# Make the changes to the database persistent
conn.commit()
def handler(context, event):
print("Hello")
return reponse

Lambda Function#

  • Lambda VPC
  • Deploy lamdba with dependencies

Let create a lambda which connects to a VPC

export class LambdaDemoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: LambdaProps) {
super(scope, id, props)
const vpc = cdk.aws_ec2.Vpc.fromLookup(this, 'VpcLookup', {
vpcId: 'vpc-0d08a1e78d2e91cb0',
vpcName: 'demo'
})
const securityGroup = cdk.aws_ec2.SecurityGroup.fromSecurityGroupId(
this,
'SecurityGroupforLambda',
'sg-014fee8db3f201b09'
)
const subnet = cdk.aws_ec2.Subnet.fromSubnetId(
this,
'SubnetLambda',
'subnet-0fc863892f278dabe'
)
new cdk.aws_lambda.Function(this, 'LambdaDemo', {
functionName: 'LambdaDemo',
handler: 'index.handler',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda/')),
memorySize: 521,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.PYTHON_3_9,
vpc: vpc,
allowPublicSubnet: true,
vpcSubnets: { subnets: [subnet] },
securityGroups: [securityGroup],
environment: {
PYTHONPATH: '/var/task/package',
HOST: props.host,
PASSWORD: props.password,
USER: props.user
}
})
}
}

RDS Instance#

Let create a RDS database instance with proxy. First, let create a subnetgroup

const subnetGroup = new cdk.aws_rds.SubnetGroup(this, 'SubnetForRdsDemo', {
description: 'subnetgroup for rds demo',
vpc: vpc,
removalPolicy: cdk.RemovalPolicy.DESTROY,
subnetGroupName: 'SubnetForRdsDemo',
vpcSubnets: {
subnetType: cdk.aws_ec2.SubnetType.PUBLIC
}
})

Second, create secrete for databse credential and for proxy

const unsafeSecret = new cdk.aws_secretsmanager.Secret(
this,
'UnsafeSecretDemo',
{
secretName: 'UnsafeSecretDemo',
secretObjectValue: {
username: cdk.SecretValue.unsafePlainText('posgresql'),
password: cdk.SecretValue.unsafePlainText('Admin2024'),
engine: cdk.SecretValue.unsafePlainText('postgres')
// resourceId: cdk.SecretValue.unsafePlainText(""),
// dbInstanceIdentifier: cdk.SecretValue.unsafePlainText("")
}
}
)

And an IAM role for the proxy

const proxyRole = new cdk.aws_iam.Role(this, 'RoleForProxyRdsDemo', {
roleName: 'RoleForProxyRdsDemo',
assumedBy: new cdk.aws_iam.ServicePrincipal('rds.amazonaws.com')
})
proxyRole.addToPolicy(
new cdk.aws_iam.PolicyStatement({
effect: Effect.ALLOW,
actions: ['secretsmanager:GetSecretValue'],
resources: [secret.secretArn]
})
)
proxyRole.addToPolicy(
new cdk.aws_iam.PolicyStatement({
effect: Effect.ALLOW,
actions: ['kms:Decrypt'],
resources: ['*']
})
)

Third, create a proxy target the database instance

const proxy = new cdk.aws_rds.DatabaseProxy(this, 'RdsProxyDemo', {
dbProxyName: 'RdsProxyDemo',
proxyTarget: cdk.aws_rds.ProxyTarget.fromInstance(rds),
//
secrets: [rds.secret!],
role: proxyRole,
vpc,
clientPasswordAuthType: cdk.aws_rds.ClientPasswordAuthType.POSTGRES_MD5,
securityGroups: [securityGroup],
requireTLS: false
})

Finally here is the database instance stack

export class RdsProxyDemo extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props)
const vpc = cdk.aws_ec2.Vpc.fromLookup(this, 'VpcLookup', {
vpcId: 'vpc-0d08a1e78d2e91cb0',
vpcName: 'demo'
})
const securityGroup = cdk.aws_ec2.SecurityGroup.fromSecurityGroupId(
this,
'SecurityGroupforLambda',
'sg-014fee8db3f201b09'
)
const subnet = cdk.aws_ec2.Subnet.fromSubnetId(
this,
'SubnetLambda',
'subnet-0fc863892f278dabe'
)
const unsafeSecret = new cdk.aws_secretsmanager.Secret(
this,
'UnsafeSecretDemo',
{
secretName: 'UnsafeSecretDemo',
secretObjectValue: {
username: cdk.SecretValue.unsafePlainText('posgresql'),
password: cdk.SecretValue.unsafePlainText('Admin2024'),
engine: cdk.SecretValue.unsafePlainText('postgres')
// resourceId: cdk.SecretValue.unsafePlainText(""),
// dbInstanceIdentifier: cdk.SecretValue.unsafePlainText("")
}
}
)
const credentials =
cdk.aws_rds.Credentials.fromGeneratedSecret('postgresql')
const subnetGroup = new cdk.aws_rds.SubnetGroup(this, 'SubnetForRdsDemo', {
description: 'subnetgroup for rds demo',
vpc: vpc,
removalPolicy: cdk.RemovalPolicy.DESTROY,
subnetGroupName: 'SubnetForRdsDemo',
vpcSubnets: {
subnetType: cdk.aws_ec2.SubnetType.PUBLIC
}
})
const rds = new cdk.aws_rds.DatabaseInstance(this, 'RdsDbInstanceDemo', {
databaseName: 'RdsDbInstanceDemo',
engine: cdk.aws_rds.DatabaseInstanceEngine.postgres({
version: cdk.aws_rds.PostgresEngineVersion.VER_15_4
}),
instanceType: cdk.aws_ec2.InstanceType.of(
cdk.aws_ec2.InstanceClass.T3,
cdk.aws_ec2.InstanceSize.MICRO
),
// credentials: cdk.aws_rds.Credentials.fromGeneratedSecret("postgresql"),
// credentials: cdk.aws_rds.Credentials.fromSecret(secret),
// credentials : {
// username: "postgresql",
// password: cdk.SecretValue.unsafePlainText("Admin2024")
// },
credentials: credentials,
vpc: vpc,
subnetGroup: subnetGroup,
// vpcSubnets: {subnetType: cdk.aws_ec2.SubnetType.PUBLIC},
securityGroups: [securityGroup],
port: 5432
})
const proxyRole = new cdk.aws_iam.Role(this, 'RoleForProxyRdsDemo', {
roleName: 'RoleForProxyRdsDemo',
assumedBy: new cdk.aws_iam.ServicePrincipal('rds.amazonaws.com')
})
proxyRole.addToPolicy(
new cdk.aws_iam.PolicyStatement({
effect: Effect.ALLOW,
actions: ['secretsmanager:GetSecretValue'],
resources: ['*']
})
)
proxyRole.addToPolicy(
new cdk.aws_iam.PolicyStatement({
effect: Effect.ALLOW,
actions: ['kms:Decrypt'],
resources: ['*']
})
)
const proxy = new cdk.aws_rds.DatabaseProxy(this, 'RdsProxyDemo', {
dbProxyName: 'RdsProxyDemo',
proxyTarget: cdk.aws_rds.ProxyTarget.fromInstance(rds),
//
secrets: [rds.secret!],
role: proxyRole,
vpc,
clientPasswordAuthType: cdk.aws_rds.ClientPasswordAuthType.POSTGRES_MD5,
securityGroups: [securityGroup],
requireTLS: false
})
}
}

Troubleshooting#

Connect to default database name

psql -h PROXY_ENDPOINT -p 5432 -U postgresql -d postgres

Reference#