Skip to content

Instantly share code, notes, and snippets.

@StevenACoffman
Forked from stesie/index.html
Last active March 6, 2017 18:03
Show Gist options
  • Save StevenACoffman/d35af5e3265db8479d48ec492d4f8baa to your computer and use it in GitHub Desktop.
Save StevenACoffman/d35af5e3265db8479d48ec492d4f8baa to your computer and use it in GitHub Desktop.
AWS IoT-based serverless JS-Webapp Pub/Sub demo

AWS IoT Workflow

From Some more on AWS IoT. It’s a little difficult to simplify what’s going on, but I think this is pretty good. At the highest level, think of it this way: AWS IoT recieves messages and routes them based on rules to other AWS services

Here’s the basic workflow of AWS IoT. It’s a simplification and leaves out a number of important services, but this is the core of it. Understanding this is to understand what AWS IOT can do … and what it can do for you.

Here’s the workflow:

  • your Things send messages
  • the Device Gateway receives and authenticates the messages
  • the Rules Engine authorizes the messages and then routes them to other AWS services

A message in AWS IoT consists of two principal parts:

  • a path, which looks like a UNIX path without the leading “/”
  • a payload, which is a JSON message

This message is augmented with authorization information as it flows through AWS IoT.

What follows is some interesting proof of concept stuff, but you can also checkout this workshop.

  • Go to “IAM” in the AWS management console and create a new user first, attaching the pre-defined AWSIoTDataAccess policy.
  • clone this Gist
  • adjust the constants declared at the top of main.js as needed
    • use the created IAM user with the AWSIoTDataAccess policy
    • for the endpoint host run aws iot describe-endpoint CLI command
  • run npm install
  • run ./node_modules/.bin/webpack-dev-server --colors

Next steps

This was just the first (big) part. There’s more stuff left to be done:

  • neither is hard-coding AWS credentials into the application source the way to go nor is publishing the secret key at all
  • … one possible approach would be to use the API Gateway + Lambda to create pre-signed URLs
  • … this could be further limited by using IAM roles and temporary identity federation (through STS Token Service)
  • there’s no user authentication yet, this should be achievable with AWS Cognito
  • … with that publishing/subscribing could be limited to identity-related topics (depends on the use case)

Fooling around

To create an IAM group and add a new IAM user to it

First, use the create-group command to create the group.

$ aws iam create-group --group-name MyIamGroup
{
    "Group": {
        "GroupName": "MyIamGroup",
        "CreateDate": "2012-12-20T03:03:52.834Z",
        "GroupId": "AKIAI44QH8DHBEXAMPLE",
        "Arn": "arn:aws:iam::123456789012:group/MyIamGroup",
        "Path": "/"
    }
}

Next, use the create-user command to create the user.

$ aws iam create-user --user-name MyUser
{
    "User": {
        "UserName": "MyUser",
        "Path": "/",
        "CreateDate": "2012-12-20T03:13:02.581Z",
        "UserId": "AKIAIOSFODNN7EXAMPLE",
        "Arn": "arn:aws:iam::123456789012:user/MyUser"
    }
}

Finally, use the add-user-to-group command to add the user to the group.

$ aws iam add-user-to-group --user-name MyUser --group-name MyIamGroup

To verify that the MyIamGroup group contains the MyUser, use the get-group command.

$ aws iam get-group --group-name MyIamGroup
{
    "Group": {
        "GroupName": "MyIamGroup",
        "CreateDate": "2012-12-20T03:03:52Z",
        "GroupId": "AKIAI44QH8DHBEXAMPLE",
        "Arn": "arn:aws:iam::123456789012:group/MyIamGroup",
        "Path": "/"
    },
    "Users": [
        {
            "UserName": "MyUser",
            "Path": "/",
            "CreateDate": "2012-12-20T03:13:02Z",
            "UserId": "AKIAIOSFODNN7EXAMPLE",
            "Arn": "arn:aws:iam::123456789012:user/MyUser"
        }
    ],
    "IsTruncated": "false"
}

You can also view IAM users and groups with the AWS Management Console.

To specify the policy, use the put-user-policy command.

$ aws iam put-user-policy --user-name MyUser --policy-name MyPowerUserRole --policy-document file://C:\Temp\MyPolicyFile.json

Verify the policy has been assigned to the user with the list-user-policies command.

$ aws iam list-user-policies --user-name MyUser
{
    "PolicyNames": [
        "MyPowerUserRole"
    ],
    "IsTruncated": "false"
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>AWS IoT Pub/Sub Demo</title>
</head>
<body>
<h1>AWS IoT Pub/Sub Demo</h1>
<form>
<button type="button" id="connect">connect!</button>
<div>
<label for="message">Message to send:</label>
<input type="text" id="message" value="Hello world!" />
<button type="button" id="send">send!</button>
</div>
</form>
<h2>Log Output</h2>
<ul id="the-log"></ul>
<script src="bundle.js"></script>
</body>
</html>
"use strict";
import v4 from 'aws-signature-v4';
import crypto from 'crypto';
import MqttClient from './node_modules/mqtt/lib/client';
import websocket from 'websocket-stream';
const AWS_ACCESS_KEY = 'AKIAJG2AAE7GEWVAVO4A';
const AWS_SECRET_ACCESS_KEY = '';
const AWS_IOT_ENDPOINT_HOST = 'A3XL158HTUQU5.iot.us-east-1.amazonaws.com';
const MQTT_TOPIC = '/test/iot-pubsub-demo';
var client;
addLogEntry('Hello World!');
document.getElementById('connect').addEventListener('click', () => {
client = new MqttClient(() => {
var url = v4.createPresignedURL(
'GET',
AWS_IOT_ENDPOINT_HOST.toLowerCase(),
'/mqtt',
'iotdevicegateway',
crypto.createHash('sha256').update('', 'utf8').digest('hex'),
{
'key': AWS_ACCESS_KEY,
'secret': AWS_SECRET_ACCESS_KEY,
'protocol': 'wss',
'expires': 15
}
);
addLogEntry('Connecting to URL: ' + url);
return websocket(url, [ 'mqttv3.1' ]);
});
client.on('connect', () => {
addLogEntry('Successfully connected to AWS IoT Broker! :-)');
client.subscribe(MQTT_TOPIC);
});
client.on('close', () => {
addLogEntry('Failed to connect :-(');
client.end(); // don't reconnect
client = undefined;
});
client.on('message', (topic, message) => {
addLogEntry('Incoming message: ' + message.toString());
});
});
document.getElementById('send').addEventListener('click', () => {
const message = document.getElementById('message').value;
addLogEntry('Outgoing message: ' + message);
client.publish(MQTT_TOPIC, message);
});
function addLogEntry(info) {
const newLogEntry = document.createElement('li');
newLogEntry.textContent = '[' + (new Date()).toTimeString().slice(0, 8) + '] ' + info;
const theLog = document.getElementById('the-log');
theLog.insertBefore(newLogEntry, theLog.firstChild);
}
{
"name": "iot-pubsub-demo",
"version": "0.0.1",
"description": "AWS IoT Pub/Sub Demo",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Stefan Siegl <[email protected]>",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.7.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1"
},
"dependencies": {
"aws-signature-v4": "^1.0.1",
"mqtt": "^1.7.4",
"websocket-stream": "^3.1.0"
}
}
var path = require('path');
module.exports = {
entry: './main.js',
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}
]
}
};
@StevenACoffman
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment