aws rds proxy throws timeout error from nodejs12.x - mysql

I'm getting a connection timeout when I try to connect to mysql rds proxy. I'm followed this tutorial
This is my code
import mysql2 from 'mysql2';
import AWS from 'aws-sdk';
const getConnection = async () => {
const signer = new AWS.RDS.Signer({
username: 'my-user-name',
hostname: 'proxy-name.proxy-someid.us-east-1.rds.amazonaws.com',
port: 3306
});
console.info('Connecting to MySQL proxy via IAM authentication');
const rdsSignerAuth = () => () => {
console.info('CALL rdsSignerAuth');
return signer.getAuthToken({
username: 'my-user-name',
region: 'us-east-1',
hostname: 'proxy-name.proxy-someid.us-east-1.rds.amazonaws.com',
port: 3306
});
};
let connection;
try {
connection = await mysql2.createConnection({
host: 'proxy-name.proxy-someid.us-east-1.rds.amazonaws.com',
user: 'my-user-name',
database: 'database-name',
connectTimeout: 60000,
ssl: { rejectUnauthorized: false },
authPlugins: { mysql_clear_password: rdsSignerAuth },
});
console.info('Connected');
}
catch (e) {
console.error(`MySQL connection error: ${e}`);
throw e;
}
return connection;
};
const mysql2Impl = async () => {
const connection = await getConnection();
//console.info({ type: 'connection', connection });
const result = await connection.promise().query('select * from destiny;');
console.info({ type: 'result', result });
};
export async function testRdsProxy(event, context){
console.info(JSON.stringify({ event, context }));
await mysql2Impl();
return 200;
}
And this is the response
Error {
code: 'ETIMEDOUT',
errno: undefined,
message: 'connect ETIMEDOUT',
sqlState: undefined,
}
I already checked that my lambda function has a policy "rds-db:connect" to "*" resource. Besides, I checked that my proxy is in the same VPC and subnet that my rds db. The secret that holds the credentials to RDS is ok. What I am doing wrong?

The doc states that the RDS proxy cannot be accessed public, so your lambda function need to be in the same security group with the rds proxy.
Please aware that when you make your lambda into a vpc, your lambda may lost its ability to access internet.
Thank you.

You can connect RDS proxy even outside VPC by doing VPC peering from same or different account. I did it for one of the project

If you pass IAM certification
check the user-name(mysql user) has execute [INVOKE LAMBDA] permission
If IAM authentication fails
you should let the proxy setup wizard automatically create an IAM like below
Connectivity > IAM role > Create IAM role
                     > IAM authentication > Required

Related

Error connecting AWS RDS MYSQL from AWS Lambda Node.js

I am trying to connect AWS RDS Mysql from Lamda function written in Node.js. Initially I was getting "timed out" error, then I made below configuration.
-The Lambda execution role has full access to VPC.
-The Lambda function and the RDS instances are now in the same VPC.
-The Lambda function and the RDS instances are in same subnets.
-The Lambda function and the RDS instances shares a security group.
-All inbound traffic permitted.
Now I says that the DB I am trying to connect is unknown.Giving below error:
Response:
{
"errorType": "Error",
"errorMessage": "ER_BAD_DB_ERROR: Unknown database 'empdb'",
"trace": [
"Error: ER_BAD_DB_ERROR: Unknown database 'empdb'",
I am copying the code snippet as well.
var mysql = require('mysql');
var connection = mysql.createConnection({
host : process.env.RDS_HOSTNAME,
user : process.env.RDS_USERNAME,
password : process.env.RDS_PASSWORD,
port : process.env.RDS_PORT,
database : process.env.RDS_DATABASE
});
connection.connect();
exports.handler = (event, context, callback) => {
// allows for using callbacks as finish/error-handlers
context.callbackWaitsForEmptyEventLoop = false;
const sql = "insert into MESSAGE values('Testing1');";
connection.query(sql, (err, res) => {
if (err) {
throw err
}
callback(null, '1 records inserted.');
})
};
I tried both Create and Insert statements. I am new to AWS. Please advise.

RDS MySQL timing out intermittently when called from Lambda using NodeJS

My web app uses Lambda using NodeJS and backend is RDS(MySQL). I'm using serverless-mysql to make db calls.
For some reason, the db call times out intermittently. I tried the following:
Enabled flow logs to see if there are any errors (but couldn't find any reject statuses).
Tried making the database publicly available and took lambda out of VPC (to see if it is an issue with VPC configuration). But still, it was failing intermittently. So VPC is out of the equation.
RDS is not having any unusual spikes and connection exhaustion as monitoring shows a peak of only up to 3 connections. Lambda is always kept warm. I tried increasing the time out to up to 25 seconds. Still no luck.
Below is the code I use:
export async function get(event, context, callback) {
if (await warmer(event)) return 'warmed';
context.callbackWaitsForEmptyEventLoop = false;
try {
const userId = getUserIdFromIdentityId(event);
const query = "select * from UserProfile where UserId = ?";
const result = await mysql.query(query, [userId]);
console.log(result);
console.log('getting user account');
mysql.quit();
return success({
profileSettings: result.length > 0 ? result[0] : null,
});
} catch(e) {
console.log(e);
return failure();
}
}
Success function basically returns a json object like below:
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify(body)
};
mysql is initialized as below:
export const mysql = AWSXray.captureMySQL(require('serverless-mysql')({
config: {
host: process.env.dbHost,
user: process.env.dbUsername,
password: process.env.dbPassword,
database: process.env.database,
}
}));
The only error I can see in Cloudwatch logs is:
Task timed out after 10.01 seconds.

non-Google MySQL database connection with firebase cloud function

When i try to connect to my MySQL, server sends me an error Error: connect ETIMEDOUT
Here is sample code
const mysql = require('mysql');
const mysqlConfig = {
connectionLimit: 1,
host: "remote_host_ip",
user: "server_user",
password: "server_pass",
database: "server_db",
port: 3306
};
mysql.createConnection(mysqlConfig).connect(function (err) {
if (!err) {
console.log("Database is connected");
} else {
console.log("Database is not connected " + err);
}
});
I am on firebase blaze(pay as you go) plan.
I know the question title already states it, but you are not using GCP Cloud SQL, right?
In that case, there's a great possibility of your MySQL server is not reachable from Cloud Functions. Are you sure network connectivity is OK?
Besides, even in Cloud Functions, it's a good idea to use connection pools. Consider using it, it will be something like that:
var config = {
user: 'root',
password: 'akdaskdasdaE',
database: 'database1'
}
config.connectionLimit = 10
config.multipleStatements = true
// needed in GCP Cloud SQL, but it seems it's not your case
// config.socketPath = `/cloudsql/__INSTANCE_CONNECTION_NAME__`
var connectionPool = mysql.createPool(config)
connectionPool.on('connection', () => {
console.log(`[connectionPool] new connection opened`)
})
// then you use connectionPool.getConnection(..)

AWS Lambda and RDS working example (need it to work with Sequelize)

Here's a working example of AWS Lambda and MySQL, but I'd like it to work with Sequelize. How do I initialize Sequelize to work with AWS Lambda? I have the authenticated IAM role working too.
https://dzone.com/articles/passwordless-database-authentication-for-aws-lambd
'use strict';
const mysql = require('mysql2');
const AWS = require('aws-sdk');
// TODO use the details of your database connection
const region = 'eu-west-1';
const dbPort = 3306;
const dbUsername = 'lambda'; // the name of the database user you created in step 2
const dbName = 'lambda_test'; // the name of the database your database user is granted access to
const dbEndpoint = 'lambdatest-cluster-1.cluster-c8o7oze6xoxs.eu-west-1.rds.amazonaws.com';
module.exports.handler = (event, context, cb) => {
var signer = new AWS.RDS.Signer();
signer.getAuthToken({ // uses the IAM role access keys to create an authentication token
region: region,
hostname: dbEndpoint,
port: dbPort,
username: dbUsername
}, function(err, token) {
if (err) {
console.log(`could not get auth token: ${err}`);
cb(err);
} else {
var connection = mysql.createConnection({
host: dbEndpoint,
port: dbPort,
user: dbUsername,
password: token,
database: dbName,
ssl: 'Amazon RDS',
authSwitchHandler: function (data, cb) { // modifies the authentication handler
if (data.pluginName === 'mysql_clear_password') { // authentication token is sent in clear text but connection uses SSL encryption
cb(null, Buffer.from(token + '\0'));
}
}
});
connection.connect();
// TODO replace with your SQL query
connection.query('SELECT * FROM lambda_test.test', function (err, results, fields) {
connection.end();
if (err) {
console.log(`could not execute query: ${err}`);
cb(err);
} else {
cb(undefined, results);
}
});
}
});
};
Instead of using mysql.createConnection() and use your RDS Signer token:
var sequelize = require('sequelize')
const Sequelize = new sequelize(
process.env.database_name,
process.env.databse_user,
token,
{
dialect: 'mysql',
dialectOptions: {
ssl: 'Amazon RDS',
authPlugins: { // authSwitchHandler is deprecated
mysql_clear_password: () => () => {
return token
}
}
},
host: process.env.db_proxy_endpoint,
port: process.env.db_port,
pool: {
min: 0, //default
max: 5, // default
idle: 3600000
},
define: {
charset: 'utf8mb4'
}
}
// then return your models (defined in separate files usually)
await Sequelize.authenticate() // this just does a SELECT 1+1 as result;
await Sequelize.sync() // DO NOT use this in production, this tries to create tables defined by your models. Consider using sequelize migrations instead of using sync()
Also it's a good idea to keep your database connection parameters in a config file so no one can see them. (process.env)
We are working with Sequelize and Lambda, but you will need to reserve more resources, in our case we need at least 1GB to run a lambda with Sequelize. Without it, just with mysql2 it runs just with 128MB.
But if you really wanna use Sequelize just replace your createConnection for something like what you will find in sequelize doc
Probably you will use the context.callbackWaitsForEmptyEventLoop=true because you may have some issues when you call the callback function and you get nothing because your Event Loop probably will never be empty.

Register aws iot device using cognito credentials

I am working on a app where I need to track the changes of some devices and show those in the frontend.
For the user login I'm using cognito and I'm getting the credential after login and I already got valid credential because I connected AWS DynamoDB using the same credential.
Now I want to register a aws.iot device with the same cognito credential.
I'm following https://github.com/aws/aws-iot-device-sdk-js
I checked with some static credential with a aws user like:
client.device = awsIot.device({
clientId: clientID,
host: host,
accessKeyId: AccessKeyId,
secretKey: secretKey,
protocol: 'wss'
});
And this works fine.
Then I tried the same using aws cognito assessKeyId and secretKey, but this I time I got 403.
I checked connect to AWS IoT using web socket with Cognito authenticated users, but it didn't help.
My current code is like:
var awsIot = require('aws-iot-device-sdk');
AWS.config.credentials.get(() => {
const IoT = new AWS.Iot();
IoT.attachPrincipalPolicy({
policyName: 'PubSub',
principal: AWS.config.credentials.identityId
}, (err, res) => {
if (err) {
} else {
let credential;
if (AWS.config.credentials && AWS.config.credentials.data && AWS.config.credentials.data.Credentials) {
let credentials = AWS.config.credentials.data.Credentials;
awsIot.device({
clientId: clientID,
host: host,
accessKeyId: credentials.AccessKeyId,
secretKey: credentials.secretKey,
protocol: 'wss',
sessionToken: credentials.SessionToken
});
}
}
});
});
Can anybody please help me, what I'm missing here.
What worked for me was passing in the data from the AWS.config.credentials object directly, i.e.
if (AWS.config.credentials) {
awsIot.device({
clientId: clientID,
host: host,
accessKeyId: AWS.config.credentials.accessKeyId,
secretKey: AWS.config.credentials.secretAccessKey,
protocol: 'wss',
sessionToken: AWS.config.credentials.sessionToken
});
}
Perhaps check also that the accessKeyId etc. begin with small letters and not caps, if you are calling via this method.
Finally I got the solution in this case, all I needed to do, is pass empty string as accesskey, secret key and session token while creating the device and then device credential as the device is created.
AWS.config.credentials.get(() => {
const IoT = new AWS.Iot();
IoT.attachPrincipalPolicy({
policyName: 'PubSub',
principal: AWS.config.credentials.identityId
}, (err, res) => {
if (err) {
} else {
let credential;
if (AWS.config.credentials && AWS.config.credentials.data && AWS.config.credentials.data.Credentials) {
let credentials = AWS.config.credentials.data.Credentials;
var device = awsIot.device({
clientId: clientID,
host: host,
accessKeyId: '',
secretKey: '',
protocol: 'wss',
sessionToken: ''
});
device.updateWebSocketCredentials(credentials.AccessKeyId, credentials.SecretKey, credentials.SessionToken, credentials.Expiration);
}
}
});
});