5

I have two servers using NodeJS on the same domain. (server1.example.com and server2.example.com)

I'd like to send messages from one server to the other in a secure way.

Right now, I'm using sending my message by sending a HTTPS POST such as https://example.com with {secret:XXXXX,message:1234}.

Is there a cleaner way to do this? If so, what would be the exact steps needed? Note: I have a SSL certifiate on the site. Both servers are on the same domain.

RainingChain
  • 7,397
  • 10
  • 36
  • 68
  • 2
    I don't think that the query string gets encrypted when you send requests, so you could send it in the body of a request using a method like POST – Explosion Pills Apr 03 '17 at 23:39
  • @ExplosionPills Thanks, I edited the question using POST. – RainingChain Apr 04 '17 at 01:05
  • technically both servers are on separate domains, they just share a parent – strider Apr 04 '17 at 01:45
  • @RainingChain What is it about an HTTPS request that is *not* "clean" in your opinion? – mscdex Apr 04 '17 at 01:48
  • @mscdex I don't know exactly. I had hoped there was an automated way or library to do this. I would have preferred not to have to send the secret for every message and not actually have to manually manage a secret at all. – RainingChain Apr 04 '17 at 03:41

3 Answers3

4

There are a few options I can think of, though of course, it depends on the amount of encryption and security you are looking for, if HTTPS is not strong enough for what is required for the specific communication. (Though as mentioned you do have HTTPS.)

  1. You can have the sending server issue a whole JWT route. Send the information with a Token and verify it on the other side. Make sure the Token has a short TTL as well. (If you really want to go for broke here, you can start implementing the OAuth2 framework, though that may be complete overkill.)

  2. Additionally, you can create a Websocket HTTPS server, and only accept the information from the information on a specific incoming port. That will allow you to use the JWT and further verify by the port access. The port you open will only be allowed to accept packets from a specific IP, which is your outgoing server.

  3. You can add yet another layer as you are doing by encrypting the entire message (via one of the Node NPM modules or via Crypto), so both the message and secret are hashed.

  4. You can also add a cache layer (either Redis or a Node-Cache module), where all the decryption will be done to speed up the process.

  5. Another trick to use, though you have to work out the actual schedule, is to mix up the various hashing routines you use based on process or on different hours, or whatever schedule you wish.

  6. Finally, a sometimes overlooked option is to install a firewall on the receiving computer with very specific rules on what to receive and from where. (This though is not a Node process, and can take time to get right.)

None of the above is linked to Express though, or middleware. I assume if you adopt any of the above you will need a few NPM modules in the end result.

Probably forgot a few options but hope this helps.

twg
  • 1,075
  • 7
  • 11
4

Just to add to the other solutions already posted, you could just use certificates on both ends, that way you could do the authentication at the TLS layer instead of the application layer if that helps at all.

Assuming you're using node on both servers, you could achieve this like so:

  1. Create one custom CA, and then one certificate and one private key for each server.
  2. Each server might have code like:

    const tls = require('tls');
    
    function onMessage(socket, msg) {
      // `msg` is a parsed message received from `socket`
    }
    
    // Receive incoming messages
    tls.createServer({
      rejectUnauthorized: true,
      requestCert: true,
      ca: MY_CUSTOM_CA,
      cert: THIS_SERVER_CERT,
      key: THIS_SERVER_PRIVATE_KEY
    }, (socket) => {
      if (!socket.authorized)
        return socket.end(); // Certificate did not check out
      console.log('Connection accepted');
    
      // example protocol: newline-delimited JSON
      var jsonBuffer = '';
      socket.setEncoding('utf8');
      socket.on('data', (chunk) => {
        var chunks = chunk.split('\n');
        var numComplete = chunks.length - 1;
        // Last line does not have trailing newline
        var incompleteChunk = chunks[numComplete];
        if (numComplete === 0) {
          jsonBuffer += incompleteChunk;
          return;
        }
        chunks[0] = jsonBuffer + chunks[0];
        for (var i = 0; i < numComplete; ++i) {
          try {
            onMessage(socket, JSON.parse(chunks[i]));
          } catch (ex) {}
        }
        jsonBuffer = incompleteChunk;
      });
    
      socket.on('end', () => {
        console.log('Connection ended');
      });
    }).listen(MY_PORT);
    
    // Send outgoing messages
    function sendMessages(host, port, msgs, cb) {
      if (!Array.isArray(msgs))
        msgs = [msgs];
    
      var req = tls.connect({
        host,
        port,
        rejectUnauthorized: true,
        ca: MY_CUSTOM_CA,
        cert: THIS_SERVER_CERT,
        key: THIS_SERVER_PRIVATE_KEY
      }, () => {
        if (!this.authorized)
          return this.end(); // Certificate did not check out
    
        for (var i = 0; i < msgs.length; ++i)
          this.write(JSON.stringify(msgs[i]) + '\n');
    
        this.end();
      }).once('error', onError).once('close', onClose);
    
      function onError(err) {
        req.removeListener('close', onClose);
        cb(err);
      }
    
      function onClose() {
        cb();
      }
    }
    
  3. Add incoming message handling in onMessage() and send outgoing messages with sendMessages().

You could also just keep a single socket open all the time instead of using a new connection per set of outgoing messages, but that would be a little more involved because you'd need to add an application-level keepalive mechanism and such, but it's certainly doable.

mscdex
  • 104,356
  • 15
  • 192
  • 153
2

You could harden your security some more by verifying the host of the request, via the HOST or ORIGIN header.

Check out: https://stackoverflow.com/questions/18498726/how-do-i-get-the-domain-originating-the-request-in-express-js

Essentially, you'd be making sure that the request with the encrypted secret actually came from a specific server, and not just any.

Community
  • 1
  • 1
strider
  • 5,674
  • 4
  • 24
  • 29