Recently I’ve tried to write a lambda function in Go, which connects to MySQL database on RDS instance and use IAM Authentication instead of “traditional” approach with passwords (which I think is an anti-pattern in cloud environments).
I’ve spent way too much time to solve the problem, but the documentation and examples on the Internet are lacking and wrong in some places… It’s time to document it right!
To make the above described scenario working we have to do a copule of things:
Create user on MySQL instance
Connect to the instance and issue the following commands:
mysql> CREATE USER '<DB_USER>' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS'; mysql> GRANT ALL PRIVILEGES ON <DB_NAME>.* TO ''<DB_USERNAME>'@'%'; mysql> FLUSH PRIVILEGES;
Make/modify IAM role for lambda
Our lambda needs the following policy to be able to connect to MySQL instance:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "rds-db:connect" ], "Resource": [ "arn:aws:rds-db:REGION:AWS_ACCOUNT_NUMBER:dbuser:RESOURCE_ID/DB_USER" ] } ] }
Of course you need to replace values in Resource to your account. Make sure that DB_USER is the same as the name you have created in Create user on MySQL instance section above.
If you want to quickly find resource id of the database instance, just issue:
aws rds describe-db-instances --query "DBInstances[*].[DBInstanceIdentifier,DbiResourceId]"
Connect from Go
I’ll skip all “lambda stuff”, validation of parameters and error handling and just paste “the meat” – code to connect to DB:
awsCred := credentials.NewEnvCredentials() // lambda gets credentials from ENV variables dbHost := os.Getenv("DB_HOST") dbUser := os.Getenv("DB_USER") region := os.Getenv("AWS_DEFAULT_REGION") // we assume that db is in the same region token, err := rdsutils.BuildAuthToken(fmt.Sprintf("%s:%d", dbHost, 3306), region, dbUser, awsCred) // specifying port is very important as it cannot authenticate without that and docs say nothing about it dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=true&allowCleartextPasswords=1", dbUser, token, dbHost, dbName) // cleartext passwords are required so mysql can validate token db, err := sql.Open("mysql", dsn) defer db.Close() // here comes your query to database
And here we are – we can connect from lambda to MySQL without any passwords :)
Sharath Apr 17 , 2020 at 19:15 /
You saved the day for me. Thanks!
Not including the the port number was the issue.
sambasivarao May 16 , 2020 at 02:25 /
can please share full code
sambasivarao May 17 , 2020 at 01:41 /
Lambda execution failed with status 200 due to customer function error: dial tcp 127.0.0.1:5432: connect: connection refused. Lambda request id: 3eb5620c-fa10-43ee-b2f1-43234b9108f0
Getting this error can anyone pls help.
luktom May 18 , 2020 at 22:38 /
Please make sure you run your lambda in VPC and also check your security groups (for lambda as well as for the database).
Jose Antonio Feb 26 , 2021 at 19:33 /
I have this output:
mysql_aws.go:9:2: no required module provides package github.com/aws/aws-sdk-go/aws/credentials; to add it:
go get github.com/aws/aws-sdk-go/aws/credentials
mysql_aws.go:10:2: no required module provides package github.com/aws/aws-sdk-go/service/rds/rdsutils; to add it:
go get github.com/aws/aws-sdk-go/service/rds/rdsutils
This is my code
package main
import (
“database/sql”
“fmt”
“log”
“os”
“github.com/aws/aws-sdk-go/aws/credentials”
“github.com/aws/aws-sdk-go/service/rds/rdsutils”
_ “github.com/go-sql-driver/mysql”
)
type Card struct {
Number string `json:”number:”`
Account_Id string `json:”account_id:”`
Credit_Limit float64 `json:”Credit_Limit:”`
Status bool `json:”Status:”`
}
func main() {
awsCred := credentials.NewEnvCredentials() // lambda gets credentials from ENV variables
dbHost := os.Getenv(“host”)
dbUser := os.Getenv(“user”)
dbName := os.Getenv(“dbname”)
region := os.Getenv(“region”) // we assume that db is in the same region
token, err := rdsutils.BuildAuthToken(fmt.Sprintf(“%s:%d”, dbHost, 3306), region, dbUser, awsCred) // specifying port is very important as it cannot authenticate without that and docs say nothing about it
if err != nil {
log.Fatal(err)
}
dsn := fmt.Sprintf(“%s:%s@tcp(%s)/%s?tls=true&allowCleartextPasswords=1”, dbUser, token, dbHost, dbName) // cleartext passwords are required so mysql can validate token
db, err := sql.Open(“mysql”, dsn)
defer db.Close()
if err != nil {
log.Fatal(err)
}
// return nil, errors.New(“[” + dsn + “]” + fmt.Sprintf(“%i”, mySQL.Stats().OpenConnections))
err = db.Ping()
if err != nil {
log.Fatal(err)
}
}
dadada Mar 23 , 2021 at 03:29 /
Note: credentials.NewEnvCredentials() does not work for EC2 or ECS
RDS and Stepping into the Plumbing Center of Pain - Ten Mile Square Technologies Jul 28 , 2021 at 16:34 /
[…] The one that had the working RDSUtils code in GO […]