Predix_Logo
  • Categories
    • Questions
    • Predix | Updates
      • Pricing
      • Product
    • Deloitte - Private
    • How-To
    • Accenture-Private
  • Explore
    • Topics
    • Questions
    • Articles
    • Feedback or Feature Requests
  • Sign in
  • Home /
  • Questions /
avatar image
0
Question by Evan Dana · May 10, 2017 at 07:14 AM · securitywebsocket

How to Secure a WebSocket Connection?

tldr;

  • What is the recommended way to secure a WS connection?

  • Predix Websocket Client: How does it push messages from the secure target WebSocket to the browser and let the client JavaScript know?

  • Predix Websocket Client: If the connection to Predix WebSocket Client is established through REST who then creates a WS relay connection, how does the client JavaScript know about, connect to, and manage the WS relay connection?



Options Overview

  1. Use a server-side relay to connect securely (Predix Websocket Client)

  2. On Websocket open, pass token in headers

  3. On Websocket open, pass in tokens

  4. Manually create HTTP WS Upgrade request

  5. Secure messages with tokens

  6. Are there any other options?



Use a server-side relay to connect securely

Since server-side technology can send headers (with tokens) when establishing a secure connection, then a browser could connect to an insecure service who then securely connects to the target WebSocket. While it is possible to write this code in Node.js or Java, why not use the Predix Websocket Client template?


Pros

  • Secure WS connection

Cons

  • Additional overhead with secondary service
  • Requires Java and JavaScript experts to implement
  • How does it push messages from the secure target WebSocket to the browser and let the client JavaScript know?
  • If the connection to Predix WebSocket Client is established through REST who then creates a WS relay connection, how does the client JavaScript know about, connect to, and manage the WS relay connection?






On Websocket open, pass token in headers

Not possible from Browser.

new WebSocket(url, protocol) does not provide an interface for headers to be sent.



On WebSocket open, pass in tokens


Pros

  • Would secure the WebSocket (if it works)

Cons

  • Not supported across all browsers

  • Not recommended

Browser method for creating a WebSocket connection is new WebSocket(url, protocol). url can be in the format of either ws://somehost.com/resource, it could also include username and password in this format: ws://username:password@somehost.com/resource. While it is possible to shim a custom string in place of the username and password, I am unsure if our token length would result in exceeding the url length limit.



Manually create HTTP WS Upgrade request

Does not work.

Technically, new Websocket() performs a HTTP GET request on the same endpoint with an "upgrade" header, upon successful handshake, the socket connection is then permitted. In theory, headers could be sent with this HTTP request, but in practice, the interface for new WebSocket() doesn't allow them. If you try to set headers, they are rejected.

The WebSocket Spec

  GET /chat HTTP/1.1
     Host: server.example.com
     Upgrade: websocket
     Connection: Upgrade
     Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
     Origin: http://example.com
     Sec-WebSocket-Protocol: chat, superchat
     Sec-WebSocket-Version: 13

If you try to make an XMLHttpRequest with the WebSocket headers, it rejects the request because those headers can't be sent. I confirmed this using Polymer Iron-Ajax and XMLHttpRequest.

Does not work:

 var xhr = new XMLHttpRequest();
 xhr.addEventListener('load', handleLoad);
  
 xhr.open("GET", 'ws://somehost/livestream');
 xhr.setRequestHeader('authorization', 'bearer someverylongstringthathasbeenshortened');
 xhr.setRequestHeader('tenant', 'some-tenant-string-goes-here');
 xhr.setRequestHeader('Upgrade', 'websocket');
 xhr.setRequestHeader('Connection', 'Upgrade');
 xhr.setRequestHeader('Sec-WebSocket-Key', 'SeCREtKeY==');
 xhr.setRequestHeader('Sec-WebSocket-Protocol', 'chat, superchat');
 xhr.setRequestHeader('Sec-WebSocket-Version', '13');
 xhr.send();
  
  
 function handleLoad (res) {
   document.querySelector('#output').innerText = xhr.responseText;
 }

This will also error out because Chrome performs a pre-fetch OPTIONS method request before any GET request. This OPTIONS method is rejected on WS endpoints without specific configuration



Secure messages with tokens

Open a WebSocket connection without any security, but then pass a token with each subscription request. No data will be sent to client unless token validates.


Pros

  • Easy to set up

Cons

  • WebSocket is still insecure

Comment
Add comment
10 |1200 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

3 Replies

  • Sort: 
avatar image
4
Best Answer

Answer by Evan Dana · May 11, 2017 at 11:29 AM

Summary of conversation with @Greg Stroup:

Summary

Take the server code from Security Starter's web server and turn into a Secure WS Relay repo that will enable the following:

  1. UI connects securely to WS Relay through HTTPS using headers with tokens

  2. WS Relay then opens a secure connection to the Target WS Endpoint

  3. WS Relay responds to UI with socket ID of WS Relay - target endpoint connection

  4. UI establishes WS connection (insecurely) to WS Relay with standard new WebSocket()

  5. UI socket messages include socket ID

  6. Upon receiving socket messages from UI, WS Relay only forwards messages with matching socket ID


Connection Security

  • Insecure WS to WS Relay

  • Secure WS Relay to Target WS Endpoint



Potential Enhancements

  • Add additional security on REST WS Relay secure connection with PassportJS



Implementation Details

alt text

  • Example: https://github.build.ge.com/AutomationNorth/hmi-microapp/blob/develop/server.js#L63

  • UI connects to Server code who connects to service

  • Service is now protected from DDOS attacks because UI server would fail first. This is preferable because service downtime could corrupt data integrity.

Reverse Proxy Requests and Handle WS Differently

 /**
  * Reverse proxy HTTP requests on the given endpoint. If proxyWs is true then it will also listen to upgrade requests on
  * that endpoint and proxy those too.
  * @param endpoint The endpoint where this reverse proxy should listen (ie. /my-reverse-proxy)
  * @param url The URL where these requests should be proxied to
  * @param proxyWs Whether this endpoint should also reverse proxy websocket connections
  */
 let doReverseProxy = (endpoint, url, proxyWs) => {
     console.log('Proxying requests to', endpoint, '->', url, proxyWs === true ? '(including websockets)' : '');
 
     let curProxy = reverseProxy.create(url, { ws: proxyWs === true });
 
     // proxy HTTP requests as express middleware
     app.use(endpoint, (req, res) => {
         curProxy.web(req, res);
     });
 
     curProxy.on('error', (err) => {
         console.warn('Error while proxying request to', url, err);
     });
 
     if (proxyWs === true) {
         // listen to upgrades and proxy them too if they come from the right place
         server.on('upgrade', (req, socket, head) => {
             if (req.url.startsWith(endpoint)) {
                 // since websocket requests aren't getting caught by the middleware their URL still has the reverse proxy endpoint
                 // on it. so we need to turn /my-reverse-proxy/some-url into /some-url for it to be reverse proxied correctly
                 req.originalUrl = req.url;
                 req.url = req.url.substring(endpoint.length);
                 curProxy.ws(req, socket, head);
             }
         });
         server.on('error', function (err, req, socket) {
             console.warn('Server error on socket', url, err);
             console.warn('arguments', arguments);
             socket.end();
         });
     }
 };

Pass Along Headers

 const httpProxy = require('http-proxy');
 const HttpsProxyAgent = require('https-proxy-agent');
 
 let options = {
     changeOrigin: true,
 };
 
 // if a proxy is set then use it in the connection
 let proxyServer = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
 if (proxyServer) {
     console.log('Configuring the reverse proxy to make its requests through the following HTTP proxy:', proxyServer);
     options.agent = new HttpsProxyAgent(proxyServer);
 }
 
 // if we're running locally look to the environment for token/tenant info
 options.headers = {};
 if (process.env.DEBUG_TOKEN) {
     options.headers.Authorization = 'Bearer ' + process.env.DEBUG_TOKEN;
 }
 
 if (process.env.TENANT_ID) {
     options.headers.tenant = process.env.TENANT_ID;
 }
 
 /**
  * Creates a reverse proxy on the given URL with the given options.
  * @param url The URL to proxy things to
  * @param otherOptions The options to use. These will supplement the default options at the top of this module.
  * @returns {*|Object} An http-proxy object
  */
 let createReverseProxy = (url, otherOptions) => {
     let myOptions = otherOptions || {};
 
     let properties = Object.getOwnPropertyNames(options);
     for (let i = 0; i < properties.length; i++) {
         let property = properties[i];
         if (!myOptions.hasOwnProperty(property)) {
             myOptions[property] = options[property];
         }
     }
     myOptions.target = url;
     return httpProxy.createProxyServer(myOptions);
 };
 
 module.exports = {
     create: createReverseProxy,
 };



References

  • Predix Toolkit - Secure WS - API Explorer: https://predix-toolkit.run.asv-pr.ice.predix.io/#!/wsClient
    • Go to Client Login, then API Explorer to preview how this is set up

    • Underlying code for this example: https://github.com/PredixDev/security-starter

  • Reference App: https://github.com/PredixDev/predix-rmd-ref-app
    • Uses Predix Websocket Server and Webapp Starter

  • Webapp Starter: https://github.com/PredixDev/predix-webapp-starter
    • UI starter app that connects to Predix Websocket Server

  • Predix Websocket Server: https://github.com/PredixDev/predix-websocket-server
    • Uses Predix Websocket Client

    • Note: Still unsure how to configure the Websocket Client through the Websocket Server

  • Predix Websocket Client: https://github.com/PredixDev/predix-websocket-client


screen-shot-2017-07-10-at-22259-pm.png (35.6 kB)
Comment
Add comment · Share
10 |1200 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
1

Answer by Tom Turner · May 10, 2017 at 06:16 PM

You can look at how predix-websocket-client makes a secure connection to Predix Timeseries, then imagine how it's implemented on the Time Series WSS side.

Essentially, Time Series is tied to UAA. You pass a UAA Token to the WSS endpoint in the Header when making the connection. On the server side, Timeseries checks that token with the same UAA and if it matches up, then it lets you create the WS Connection.

Comment
Add comment · Share
10 |1200 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
1

Answer by Greg Stroup · May 10, 2017 at 02:45 PM

Good question. You're on the right track. I think "Use a server-side relay to connect securely" is the way to go. But I believe predix-websocket-client is really just a client. It's only part of what you need. Did you look at the predix-websocket-server repo? (It uses the predix-websocket-client.) I think that's more like what you need.

https://github.com/PredixDev/predix-websocket-server

The Predix Event Hub service would also meet your needs. You could configure it to talk to your back end websocket, then browsers could subscribe to it for events. (Haven't done this myself, but I think it should work.)

@Susheel Choudhari @Tom Turner any insights?

Comment
Add comment · Share
10 |1200 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Follow this Question

Answers Answers and Comments

90 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

How does Predix help ensure security and data privacy? 2 Answers

proxy server on Fast token lib not working 2 Answers

Deploying Seed UI App without UAA 3 Answers

How to protect microapp against Cross-Site Request Forgery? 1 Answer

Data ingestion to TimerSeries service from NodeJS 2 Answers

GE Monogram
  • Legal
  • Cookies
  • Forum Terms
  • Contact Us
  • Copyright © 2017 General Electric Company. All rights reserved.


Enterprise
Social Q&A

  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Create an article
  • Submit your feedback or feature request
  • Categories
  • Questions
  • Predix | Updates
    • Pricing
    • Product
  • Deloitte - Private
  • How-To
  • Accenture-Private
  • Explore
  • Topics
  • Questions
  • Articles
  • Feedback or Feature Requests