mean-expert-official / fireloop.io

Modern Real-Time Platform by MEAN Expert
Other
172 stars 36 forks source link

How Implement SSL with loopback and fireloop #107

Closed chrno04 closed 7 years ago

chrno04 commented 7 years ago

What type of issue are you creating?

What version of this module are you using?

Write other if any: 1.0.0-beta.2.5

Please add a description for your issue:

I need to implement SSL in my loopback aplication , I use the package loopback-ssl with the default configuration and load my cert , all the loopback option work normal ( rest, authentication), but the socket.io dont work, in the chrome console show:" WebSocket connection to 'wss://mydomain:myport/socket.io/?EIO=3&transport=websocket' failed: Connection closed before receiving a handshake response" , without ssl work perfect , already i test other solution( like a proxy or the loopback oficial tutorial for ssl) but a similar error is show with ssl , my question is i need to edit something in the fireloop package ( loopback-component-realtime) for make work the WEBSOCKET SECURE with the socket io implementation?

jonathan-casarrubias commented 7 years ago

Hi @chrno04 thanks for reaching out,

Hey so... I believe there will be a couple changes to make the SSL work, I'm just finishing all related to security and I think is the right time to make sure SSL works.

I'll need your help to make this work and then I will publish the fixes ASAP.

First locate and open the following file:

sdk/sockets/socket.connections.ts

Then change the following property to true:

https://github.com/mean-expert-official/loopback-sdk-builder/blob/master/tests/ng2web/src/app/shared/sdk/sockets/socket.connections.ts#L78

Now you need to pass the SSL Configurations to the socket.io driver, to do that you will need to change the server/component-config.jsonto javascript format server/component-config.js:

const key  = fs.readFileSync('../key').toString();
const cert = fs.readFileSync('../crt').toString();
const ca    = fs.readFileSync('../intermediate.crt').toString();
module.exports = {
  "loopback-component-explorer": {
    mountPath: "/explorer"
  },
  "@mean-expert/loopback-component-realtime": {
    debug: true,
    auth: true,
    driver: {
      name: "socket.io",
      options: {
        forceNew: true,
        upgrade: false,
        transports: ["websocket"],
        key, cert, ca
      }
    }
  }
}

I think that should do the trick, please inform your results, if works I will add a configuration option for the SDK to work with secured true.

chrno04 commented 7 years ago

well i got the ssl work, with the package yantrashala/loopback-ssl ( only i need to add the server variable to the app.emit("started"); of the library as you put in the readme for loopback-component-realtime, i changed the method to app.emit('started', server);) , i test your method today and inform you, but one thing i need to change is the internal client and the server client in the io.driver file of the loopback-component-realtime for all work, the changes are this.internal = client("https://localhost:" + this.options.app.get('port'), { reconnect: true, rejectUnauthorized: false, secure: true, transports: ['websocket'] }); i use a a windows server , i added the the https ( because when i put the ssl the loopback only respond https request) and reconnect: true, rejectUnauthorized: false, secure: true, in the parameters of the socket , and the server client and the internal client connect, and all work like without the ssl.

The changes to the sdk file sdk/sockets/socket.connections.ts is correct , i need to add the secure:true

jonathan-casarrubias commented 7 years ago

@chrno04 you are correct, the internal and server clients will require to be configured as well

chrno04 commented 7 years ago

Analyzing a little the situation, while I load the certificates to the server variable and pass it through the internal event 'emit' the socket.io gets the certificates, the method in the yantrashala/loopback-ssl are

//loopback config.json

  "httpMode": false,
  "certConfig": {
    "path": "/certificate/path/",
    "key": "local.pem",
    "cert": "local.crt.pem",
    "ca": [],
    "requestCert": false,
    "rejectUnauthorized": false
  }
//loopback server.js

'use strict';
require('ts-node/register');
var loopback     = require('loopback');
var boot         = require('loopback-boot');
var cookieParser = require('cookie-parser');
var loopbackSSL = require('loopback-ssl');
var https = require('https');

var app = module.exports = loopback();

app.use(cookieParser());

// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function(err) {
  if (err) throw err;

  // start the server if `$ node server.js`
  if (require.main === module)
  {

  }
    //app.start();
});

return loopbackSSL.startServer(app);
//inside loopback-ssl library

var startHttps = function(app) {
  var cfg = app.get('certConfig');
  var options = null;
  try {
    options = getServerOptions(cfg);
  } catch (err) {
    console.error('Error reading certificates', err.stack);
    process.exit(1);
  }
  var server = https.createServer(options, app);
  var baseUrl = 'https://' + app.get('host') + ':' +  app.get('port');
  return server.listen(app.get('port'), appStartEvent(app, baseUrl,server));
};
var appStartEvent = function(app, baseUrl,server) {
  app.emit('started',server);
  console.log('Web server listening at: %s', baseUrl);
  if (app.get('loopback-component-explorer')) {
    var explorerPath = app.get('loopback-component-explorer').mountPath;
    console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
  }
};

i modify the method appStartEvent to receive the variable server like you show in the readme of realtime. The only thing that would be necessary is to make the settings to change the code if it is used in Http or https with the loopback component realtime, so really the work you've done is almost out of the box with ssl My app is an ionic3 app (use a fork of sdk builder https://github.com/mean-expert-official/loopback-sdk-builder/issues/390 to generate the use with ionic storage), a web app connected to The loopback using fireloop.io, although my app uses multiple user models so I had to make one or another change in the sdk with the accesstoken since following the tutorial of the official page you have to create a customtoken, but in general it seems to me in my tests everything works as it should work

jonathan-casarrubias commented 7 years ago

Hey @chrno04 have you succeed on implementing SSL?

chrno04 commented 7 years ago

@jonathan-casarrubias The ssl works perfectly with the method explained in the previous post, with the method you mentioned when converting the json file to .js it seems that loopback not recognize it when start .

Basically i load the certificates directly to the variable server using the loopback-ssl package, so when running the method app.emit ('started', server); The socket already collects the certificates, i suppose if i use another method for load the certificates to the express server and send the variable in the emit event does the same job.

i test this other method without the loopback-ssl package and works the same, And I think that in the end is a quick way to implement it in case it is necessary to generate it directly from fireloop, since I only change the server.js for a version that uses https

in the config.json change the url and the swagger protocol to https

 "restApiRoot": "/api",
  "host": "0.0.0.0",
  "port": 3000,
   "url": "https://localhost:3000/",
  "swagger": {
    "protocol": "https"
  },

i load the certificates with the https.server() and modify the code of the server.js

'use strict';
require('ts-node/register');
var loopback = require('loopback');
var boot = require('loopback-boot');
var cookieParser = require('cookie-parser');
var https = require('https');
var fs = require("fs");

var app = module.exports = loopback();

var privateKey = fs.readFileSync('privkey.pem');
var certificate = fs.readFileSync('cert.pem');
var ca = fs.readFileSync('chain.pem');

  app.use(cookieParser());

app.start = function () {
  // start the web server with https
  var server = https.createServer({
    key: privateKey,
    cert: certificate,
    ca: ca,
  }, app).listen( app.get('port'), function () {
    app.emit('started', server);
    var baseUrl = app.get('url').replace(/\/$/, '');
    console.log('Web server listening at: %s', baseUrl);
    if (app.get('loopback-component-explorer')) {
      var explorerPath = app.get('loopback-component-explorer').mountPath;
      console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
    }
  });
  return server;
};

// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function (err) {
  if (err) throw err;

  // start the server if `$ node server.js`
  if (require.main === module) {

  }
  app.start();
});

The changes that have to be made in any of the methods are

With these changes ssl works

jonathan-casarrubias commented 7 years ago

That make sense, I will add a config setter for the secure true in both, the backend and front-end to fix this issue. I will also add documentation referring to this process.

Thanks for looking into it.

Cheers Jon

jonathan-casarrubias commented 7 years ago

@chrno04 I did the necessary implementations to achieve your approach, that is published now...

within component-config.json

{
  "@mean-expert/loopback-component-realtime": {
  "secure": true
  ....
  }
}

within the SDK

import {LoopBackConfig} from 'sdk';

LoopBackConfig.setSecureWebSockets();

With that you will be able to achieve your approach. I say this because while I was doing it I figured out there is another way.

If you are serving with Nginx block with SSL config and proxypass to localhost:3000, you don't even need to configure anything within the SDK or the backend component, since it is under layered.

both approaches works, therefore FireLoop must be able to work in both scenarios and that is why I did the publish for the approach you took.

BTW: I recommend you to use // instead of https://, that way the schema will be automatically detected, therefore you don't need to do extra configurations while working for localhost or secured remote server.

Cheers Jon

dmastag commented 7 years ago

@chrno04 I am also an Ionic user. Did you manage to get the ssl working with the new update?

juliancuni commented 7 years ago

@jonathan-casarrubias @dmastag How about this error?

WebSocket connection to 'wss://mydomain.com/socket.io/?EIO=3&transport=websocket' failed: Error during WebSocket handshake: Unexpected response code: 200

I managed to solve this problem by hacking nginx conf a little bit. My situation is like this: In the same server I have both front end and back end. So, in nginx config "location /" is taken by front end. What I did is I made new location

location /api {
                proxy_pass http://localhost:3000/api;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
        }

But this little hack cause the error above. Error during WebSocket handshake: Unexpected response code: 200 The strange thing is that response is ok. 200 So, I made another location on nginx config which handles all socket.io calls.

location /socket.io {
        proxy_pass http://localhost:3000/socket.io;
.......
}

After that sockets are fine. Hope this help

certifirm commented 6 years ago

I think that this must be included in the IO driver or throw an error.

reconnect: true, rejectUnauthorized: false

or put an option to enable or disable this features. It's very usefull to work in local mode with a wrong certificate.