Skip to content

Commit 26fa056

Browse files
author
Daniel Amores
committed
(feat): Initial commit.
0 parents  commit 26fa056

19 files changed

+4436
-0
lines changed

‎.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# IDE
2+
.idea/
3+
*.iml
4+
5+
# NodeJS
6+
node_modules/
7+
*.log
8+
9+
# MAC
10+
.DS_Store

‎README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
## AWS Secure Websocket
2+
3+
Secured _web socket_ deployed on _AWS_ infrastructure using
4+
the [serverless] framework.
5+
6+
This repository serves as an example for this
7+
[medium article]. Please, invest a few minutes reading it
8+
in case you need further detail.
9+
10+
All services have been implemented using [NodeJS].
11+
12+
### Serverless service user policies
13+
14+
_serverless_ documentation recommends creating a service user
15+
with restricted permissions in order to avoid security issues
16+
- for example, user leaking and having admin rights could be
17+
devastating for your _AWS_ bill.
18+
19+
However, the policies provided on the official example do not
20+
provide access to _AWS Cognito_. In order to use proper
21+
permissions, check the _sls-policies.json_ file within this
22+
repository.
23+
24+
### Infrastructure
25+
26+
The required _AWS_ infrastructure is as follows:
27+
28+
* _Lambda_: used to host our services.
29+
* _API Gateway_: used to expose our services to the outer
30+
world. Also provides authentication using _AWS Cognito_
31+
generated _JWT_ tokens.
32+
* _API WebSocket_: used to expose our services as _near
33+
realtime connections_, allowing _bi directional_ communication.
34+
It also helps us secure our connections using _AWS
35+
Cognito_ generated _JWT_ tokens.
36+
* _DynamoDB_: used to track active _web socket_ connections
37+
and what they're subscribed to.
38+
* _Cognito_: user pool which allows us create and validate
39+
user credentials.
40+
41+
### Deploying it
42+
43+
First of all, you need an _AWS_ account. Then it is
44+
mandatory to [configure serverless locally] with your
45+
credentials.
46+
47+
Once you've set your environment up, you can deploy the
48+
entire stack using the following command:
49+
```bash
50+
serverless deploy -v
51+
```
52+
53+
In case you want to deploy an specific _lambda_, you can
54+
use this other command:
55+
```bash
56+
serverless deploy function -f <functionName> -v
57+
```
58+
59+
You can also remove all the allocated resources by executing
60+
this command:
61+
```bash
62+
serverless remove -v
63+
```
64+
65+
[medium article]: http://
66+
[NodeJS]: https://nodejs.org/
67+
[serverless]: https://serverless.com/
68+
[configure serverless locally]: https://serverless.com/framework/docs/providers/aws/guide/credentials/

‎client/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Environment
2+
.env
3+
4+
# NodeJS
5+
node_modules/
6+
*.log

‎client/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
## AWS Secure Websocket client
2+
3+
Secured _web socket_ client implemented using [websocket].
4+
5+
### Configuration
6+
7+
This client uses [dotenv] to manage credentials and URLs.
8+
The following environment variables must be declared in a
9+
_.env_ file at the root of the client project:
10+
11+
1. _AUTH_ENDPOINT_: _lambda_ service endpoint used to
12+
create the _JWT_ token to be used as identity.
13+
1. _WS_HOST_: _API Gateway WebSocket_ endpoint which
14+
exposes the _web socket_ server.
15+
1. _USERNAME_: username created at _AWS Cognito_. It'll
16+
be used in order to generate the _JWT_ token used to
17+
validate the user identity.
18+
1. _PASSWORD_: user's password as set at _AWS Cognito_.
19+
20+
### Running it!
21+
22+
In order to run the client you must specify:
23+
24+
* Client identifier you'd like to use.
25+
* Event you're subscribing to.
26+
* Whether you want to deliver test messages or not.
27+
28+
Examples:
29+
30+
```bash
31+
# Example pattern
32+
$ node index.js <client-id> <event> <true/false>
33+
34+
# Listener only:
35+
node index.js first-listener greeting
36+
node index.js first-listener greeting false
37+
38+
# Listener publisher:
39+
node index.js second-listener greeting true
40+
```
41+
42+
43+
[dotenv]: https://www.npmjs.com/package/dotenv
44+
[websocket]: https://www.npmjs.com/package/websocket

‎client/index.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use strict';
2+
3+
require('dotenv').config();
4+
const axios = require('axios');
5+
const WebsocketClient = require('websocket').client;
6+
7+
// Websocket client configuration
8+
const client = new WebsocketClient();
9+
10+
client.on('connectFailed', (error) => {
11+
console.error('Connection attempt failed', error);
12+
client.abort();
13+
});
14+
client.on('connect', (connection) => {
15+
console.log('Connected!');
16+
connection.on('error', (error) => {
17+
console.error('Error during connection', error);
18+
connection.close();
19+
});
20+
connection.on('close', () => {
21+
console.log('Connection closed!');
22+
});
23+
connection.on('message', (message) => {
24+
const content = JSON.parse(message.utf8Data);
25+
switch (content.action) {
26+
case 'PING':
27+
console.log('Keeping alive');
28+
break;
29+
case 'GREETING':
30+
console.log(content.value);
31+
break;
32+
default:
33+
console.error('Unsupported response', content);
34+
}
35+
});
36+
37+
// Websockets usually timeout and close automatically after being
38+
// idle for around a minute. This ping/pong implementation keeps
39+
// the socket alive.
40+
const ping = () => {
41+
if (connection.connected) {
42+
// console.log('Pinging!');
43+
const pingMessage = {
44+
action: 'PING'
45+
};
46+
connection.sendUTF(JSON.stringify(pingMessage));
47+
setTimeout(ping, 30000);
48+
}
49+
};
50+
51+
const scheduledMessage = () => {
52+
if (connection.connected) {
53+
console.log('Greeting everyone!');
54+
const greetingMessage = {
55+
action: 'GREETING',
56+
message: `Hello everyone, this is instance ${instance}`
57+
};
58+
connection.sendUTF(JSON.stringify(greetingMessage));
59+
setTimeout(scheduledMessage, 5000);
60+
}
61+
};
62+
63+
ping();
64+
if (greets) {
65+
scheduledMessage();
66+
}
67+
});
68+
69+
// Process configuration and execution
70+
// Connection metadata: API Websocket host address and Cognito user auth :)
71+
72+
const authHost = process.env.AUTH_ENDPOINT;
73+
const host = process.env.WS_HOST;
74+
const authData = {
75+
user: process.env.USERNAME,
76+
password: process.env.PASSWORD
77+
};
78+
79+
if (process.argv.length < 4) {
80+
console.error('ERROR: Client identifier and event must be provided');
81+
console.error('Command has the following pattern: node index.js <client-id> <event> <isPulisher>');
82+
console.error();
83+
console.error('Example usages:');
84+
console.error('\t- Listener only:');
85+
console.error('\t\tnode index.js first-listener greeting');
86+
console.error('\t\tnode index.js first-listener greeting false');
87+
console.error('\t- Listener publisher:');
88+
console.error('\t\tnode index.js second-listener greeting true');
89+
process.exit(1);
90+
}
91+
92+
const instance = process.argv[2];
93+
const event = process.argv[3];
94+
const greets = process.argv.length > 4 ? process.argv[4] : false;
95+
96+
// Retrieve the access token
97+
axios.post(authHost, authData)
98+
.then((response) => {
99+
const loginData = response.data;
100+
console.log(`Generated token for user ${loginData.user}`);
101+
102+
// This old token cause sa signature verification failed at CloudWatch
103+
// loginData.token = 'eyJraWQiOiJBSnlTb0ZuVVk3WlBVVVhMSG5DMnZWMXJmXC9TZ3RsaHYyTHN1Z3lrem5GMD0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIyZjZmNmJkZi1jZDBiLTQ0NmYtYWMyNC1kZGEwM2RjN2UwMGQiLCJhdWQiOiIxNXJhZjQ1cWxrNTVjM2pjZ2dnYzI2aXR0NiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJldmVudF9pZCI6Ijc5MTA4ZjZkLTFhZGItNDhiYy05NTg4LThiNzUxZTRlZTk5ZiIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNTYzOTgzNTkzLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuZXUtY2VudHJhbC0xLmFtYXpvbmF3cy5jb21cL2V1LWNlbnRyYWwtMV9vT25vM1JrN04iLCJjb2duaXRvOnVzZXJuYW1lIjoiZGFtb3Jlc2EiLCJleHAiOjE1NjM5ODcxOTMsImlhdCI6MTU2Mzk4MzU5MywiZW1haWwiOiJkYW5pZWwuYW1vcmVzLmFsdmFyZXpAZXZlcmlzLmNvbSJ9.HPeiTBDwiZd-6Oe7OnyaEZjfEL5in4u1GqxtiA2mhF7aCoqZDDLqanicrRLW3M0x4xV3IdivwD5MdPE-T0WCts60pdnxIBKDBvajehLT-lrWdDv-7SKDXrgaYA4-ZuAmqcrmEN3NgTIVfXB5sMVkeahWAXnPPBiSdGDSCVdrYXTTmM-R8y0TQrAqp38x6u5pfpYmIktEnWqbafoU36nDlRxGBdt9dJ_Esm3Dux05MeILcoYBbo741uXNgsk3qr4pOF-4t0A8TiNSccsyFoSdqFy7B06CsHLckRSIcJQow4Vnn3Ojtg-GYrEuXzeOj8Ydlx-Yb9kvp451_LRMM3NuGQ","refresh":"eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.gHEqD6XveeyAx-Hqf_pJa-lT0jw-VzFStL8KS2szG5_WbEOFXqpi6lkciQDOczuCfRXHpwmfC8gESdz-BDxYDXvM18N0lWj5Ro-0v9LtJ2zOntZqYyclb-Z6Kr0HYFlAw3R3seBC2lUx37DKasrlsDhMyu1-n3JMth8D3uvjPxmduDu3XRxUdSG07qGyc2nagUU9846vd67cA1fPzgoi6rdHb-dK-yqF_XKqYI5cMIOslB7Enx8rcWlZOplmB_VJHM1DBFSAW2xjWiSWSwrNxNnODln8b8cXEk0nOmOpQj6bW0LSHu0pmNFvP3-OId84oCqpF6GV3uAWENiG5SO6UA.DR_yq8TlySkdRjPl.2LYsjn7qhQAYJxtFm862EklbL-DUd7Uc6f9r69UqpaGnRccsZ6IKxSHW4WS3nr-9Q-_Lk5LWDgNPAymdSV9Pj0OtBXDMrktjGukffpDKcr5-Kiqk9vcGvC0ylYWcapHvZSwi8wj8qLawsVhgmIULh-34w9NxX6EPTFwFNc5nZEoZkcCsfTw3IEIinE_ulTNJdfkwpMdvndx9X0-XwKtu7946H1NGJ2XJWZxLhmrSuTHwIjnu8JFBLcYb44SlDj36yv25rOW3Sx-z0AN0HRoW_Brs1tcLonmtkar7Fz9F2XDZVqytJf4HmulSI8ex6WXvO22fHP9ZJAc32r3K455i2llBftgtcOse_WdInr-6_BHZlKOAncCdlLkIpoyAMy2U5xWTYAiIiD4Up24inzh60wQgfaQqcTmDymW7avXAqTAxnr2y34atU0-nMmYifBEuaKIakzvaNXXrUgZFejDKlw2oPc1PY5yIxr63USv3iqLIxaDkmbqU43jq87EVQyl454jaM-eVL-iJvjpZBwxOoCQLCCw4YakRnxt5y7Bbtk0itpUfIEkoGjdkRveMm0gDwolF3LHAvEdNPEXiz6MDr9uWNEijBxvBI42afBv2BIjxfYKeC71BERaMXaV8nAJA2wsC2VFueX2YJLLVbLLnB_uYf6Eb84xKsABlYI__yHYxPrulT2_rwMufmE1Sa12qrNQFl3PeqhY9pqyBXLhogF-0iuMPg2y2b80co0sh2NVvHzAngNg45rOYPPbMCVpg7x65ypAXkf9XK7pIuAl4EzmTbn_KJGQHnY1hDuzdnPdLQ_3GgiaQSXldCT7scxFNchJleR3VWtd9tYtwl8TgKowFOHmn0-knAjgzH4QkA-Wwf4EEe9EuT5hUDdOIK4CFEMx9-qEGeGkOhzr8xb4cb82nddZU5jBzFGL5FKSY2h_RWGbGuffgpORq8GZN_OX665iJMQoXyC37mfCiZ3s2wj9ygzhzTgPUVQ-fbN0G9-aioL6DnYHk4uiuPylTn5MZeWtRY7qW8lpaz5QsexPy8rNxHMl43yBToaxPcddKBKdEVVgc_jp2a8U4kaS8Ea-lBwYH9yzHYwVOqm8xXNHHHVit-FfDN60-s2YmhDClCzmVWR7VaFEem_LTWhhHH3urPnkVRXutdl-SEV4AaKF7wdMUSGRoERQPxmVhKbhVooQiTZuDWc0SUsAKouB-B-r_BjmHZmlzOwVQVw_hZSCept_hLtmXXv7PEIhyBR1zHnOPPHsM8s4KYFjP-Ls5pZpWieO1IZwU2rGq-MSXZmEM.ie2DRoTujV4tGJO5xXtJEg';
104+
105+
// Use the retrieved access token to connect to the socket.
106+
// If Authorizer is not added to the request, server delivers a 401 response.
107+
// If an invalid Authorizer is added, server delivers a 500 response
108+
client.connect(`${host}?connectionType=${event}&Authorizer=${loginData.token}`);
109+
110+
})
111+
.catch((err) => {
112+
console.error('Unable to initialize socket connection', err.toString());
113+
});

‎client/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "aws-secure-websockets-client",
3+
"version": "1.0.0",
4+
"description": "AWS secured socket client",
5+
"main": "index.js",
6+
"author": "Daniel Amores Álvarez <damoresac@gmail.com>",
7+
"license": "MIT",
8+
"dependencies": {
9+
"axios": "0.19.0",
10+
"dotenv": "8.0.0",
11+
"websocket": "1.0.29"
12+
}
13+
}

0 commit comments

Comments
 (0)