Register aws iot device using cognito credentials - aws-sdk

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);
}
}
});
});

Related

Cannot connect AWS lambda to mysql database

Hi I cant seem to connect my sql db to my lambda function. The following is the code I tried to use. I have already added a layer for my package.json. I linked my lambda function to amazon lex but it threw out this error 'An error has occurred: Invalid Lambda Response: Received error response from Lambda: Unhandled' when I tried to print ${connection.state}. I also added the necessary lambda permissions.
Would appreciate the help thanks!
function dispatch(intentRequest, callback) {
const sessionAttributes = intentRequest.sessionAttributes;
const slots = intentRequest.currentIntent.slots;
const mysql = require("mysql");
var connection = mysql.createConnection({
host : 'xxx',
user : 'admin',
password : 'xxx',
database : 'xxx',
port : 3306
});
exports.handler = (event, context) => {
connection.connect(function(err) {
if (err) context.fail();
else context.succeed('Success');
});
};
callback(close(sessionAttributes, 'Fulfilled',
{'contentType': 'PlainText', 'content': `Thank you ${connection.state}`}));
}
// Route the incoming request based on intent.
// The JSON body of the request is provided in the event slot.
exports.handler = (event, context, callback) => {
try {
dispatch(event,
(response) => {
callback(null, response);
});
} catch (err) {
callback(err);
}
}

aws rds proxy throws timeout error from nodejs12.x

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

connection in channels.js returns undefined?

I'm trying to establish a real-time socket connection to my client
side via feathers channels. It works without any sort of
authentication. But if i add the following login action scoket is
throwing a weak map key error.
app.on('login', (authResult, { connection }) => {
console.log(connection) // returns undefined
....
})
This is the error I'm receiving
Unhandled Rejection at: Promise Promise { TypeError:
Invalid value used as weak map key
at WeakMap.set ()
app.on('login', (authResult, { connection }) => {
console.log("============>>", connection)
if (authResult && connection) {
app.channel('anonymous').leave(connection);
if (authResult.user && authResult.user['chanelName']) {
let channelName = authResult.user['chanelName'].toString();
channelName = channelName.substr(0, 5)
app.channel(`channel/${channelName}`).join(connection);
} else
app.channel('authenticated').join(connection)
}
});
The connection object is undefined, i think that causes the problem.
Anu suggestions?
Please provide the client side script.
According to fethers documentation connection can be undefined if there is no real-time connection, e.g. when logging in via REST.
You should authenticate your client.
Sample script
const feathers = require('#feathersjs/feathers');
const socketio = require('#feathersjs/socketio-client');
const io = require('socket.io-client');
const auth = require('#feathersjs/authentication-client');
const socket = io('http://localhost:3031');
const app = feathers();
// Setup the transport (Rest, Socket, etc.) here
app.configure(socketio(socket));
const options = {
header: 'Authorization', // the default authorization header for REST
prefix: '', // if set will add a prefix to the header value. for example if prefix was 'JWT' then the header would be 'Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOi...'
path: '/authentication', // the server-side authentication service path
jwtStrategy: 'jwt', // the name of the JWT authentication strategy
entity: 'user', // the entity you are authenticating (ie. a users)
service: 'users', // the service to look up the entity
cookie: 'feathers-jwt', // the name of the cookie to parse the JWT from when cookies are enabled server side
storageKey: 'feathers-jwt', // the key to store the accessToken in localstorage or AsyncStorage on React Native
storage: undefined // Passing a WebStorage-compatible object to enable automatic storage on the client.
}
app.configure(auth(options))
app.authenticate({
strategy: 'jwt',
accessToken: '<JWT TOKEN>'
}).then(() => {
console.log("Auth successfull")
const deviceService = app.service('myService');
deviceService.on('created', message => console.log('Created a message', message));
}).catch(e => {
console.error('Authentication error', e);
// Show login page
});
Hope this will help you.

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.

AWS Lambda/API Gateway - Pass an ID to delete a row in mysql with node.js

I wrote a simple restful API in Node.js for different HTTP-Requests which i then tried to realize with Lambda functions. My problem with the DELETE statement in Lambda is that i dont know how to pass an ID through the API gateway to delete a certain row in the mysql table.
With node.js, I just defined it through the route of the URL (ex. /contacts/:id) and then accessed it with .params.id.
How would you pass an a value (the ID) for the same route (/contacts) to then use it in the handler below to delete a the row with that specific ID?
The Code i posted below works fine when u invoke it locally with --data, for example:
serverless invoke local --function delete --data "2"
the same code works too if i deploy the lambda if i pass the testdata
{
"id": "1"
}
and use event.id instead of event.
I realize that this is not the way you do it, but i ve finally run out of ideas and decided to post my issue here. :-)
So, how do i pass and ID with my DELETE HTTP-Request to /contacts through the api gateway and use it in the mysql query?
module.exports.handler = (event, context, callback) =>
{
const mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: ''
});
// Use the connection
connection.query('DELETE FROM sqllambdadb.contacts WHERE id=?', event, function (err, res) {
if (err) throw err;
else if (res.affectedRows>0) callback(err, 'EMail with ID: '+ event+ ' deleted!');
else callback(err, 'No row with ID: '+ event+ ' found!');
});
connection.end();
};
API Gateway maps requests to the input event object
{
"resource": "Resource path",
"path": "Path parameter",
"httpMethod": "Incoming request's method name"
"headers": {Incoming request headers}
"queryStringParameters": {query string parameters }
"pathParameters": {path parameters}
"stageVariables": {Applicable stage variables}
"requestContext": {Request context, including authorizer-returned key-value pairs}
"body": "A JSON string of the request payload."
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}
In your example, depending on the segement's alias your using, you'd access the id by either destructuring the appropriate event object, or referencing directly via event.pathParameters.id
const mysql = require('mysql');
module.exports.handler = (event, context, callback) => {
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: ''
});
//Destructing id from event.pathParameters object
var {id} = event.pathParameters
// Use the connection
connection.query('DELETE FROM sqllambdadb.contacts WHERE id=?', id, function (err, res) {
if (err) throw err;
else if (res.affectedRows>0) callback(err, 'EMail with ID: '+ id + ' deleted!');
else callback(err, 'No row with ID: '+ id + ' found!');
});
connection.end();
};
So, i set up the yaml to add a parameter value to my URL:
delete:
handler: src/delete.handler
package:
include:
- node_modules/**
- src/delete.js
- serverlessAPI.iml
events:
- http:
path: contact-management/contacts/{id}
method: delete
cors: true
integration: LAMBDA
request:
parameters:
paths:
id: true
with this config, the id will be saved directly in the event object under event.id so you can simply access it like this:
module.exports.handler = (event, context, callback) =>{
const mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'testt123'
});
var id = event.id;
// Use the connection
connection.query('DELETE FROM sqllambdadb.Contacts WHERE id=?', id, function (err, res) {
if (err) throw err;
else if (res.affectedRows>0) callback(err, 'EMail with ID: '+ id + ' deleted!');
else callback(err, 'No row with ID: '+ id + ' found!');
});
connection.end();
};
you can test it by creating a testcase with the following input:
{
"id": "$input.params('id')"
}