Sending events from GravityZone cloud platform to SIEMs lacking HTTPS listeners

This article aims to help you build a connector between GravityZone and SIEM solutions that do not have HTTPS listeners for events.

Bitdefender GravityZone – the cloud platform, provides alerts about security events in CEF and JSON message standards. These alerts are sent through the Event Push Service API.

The GravityZone APIs are exposed using JSON-RPC 2.0 protocol specified here. For details on GravityZone API, refer to the available documentation:

If your SIEM does not have any HTTP/HTTPS listeners, but supports a Syslog service, you need to build a Node.js connector.

The connector uses the POST method to receive authenticated and secured messages from the GravityZone Event Push Service API. It parses the message and then forwards it to a local or a remote Syslog server. You can use the Syslog server to feed these messages to the SIEM.

To build the connector, follow these steps:

  1. Check the prerequisites

  2. Install Node.js and Npm

  3. Obtain the security certificate for authentication

  4. Build the Node.js connector

  5. Test the connector

  6. Configure GravityZone to send messages to the SIEM

Check the prerequisites

Before proceeding any further, you need to meet the following prerequisites:

  • Linux basic knowledge

  • Node.js advanced knowledge

  • GravityZone cloud solution

  • A GravityZone API key that covers Event Push Service API

  • Ubuntu 20.04 LTS server with the following configuration:

    • Hardware:

      • 1 CPU

      • 2 GB RAM

      • 1 Gbit virtual NIC

      • 80 GB HDD

    • Node.js installed with Node version 8.1.x or higher

    • OpenSSL installed


    This configuration can sustain an environment up to 15000 endpoints. The CPU and network usage will increase proportionally with the number of endpoints.

Install Node.js and npm

  1. Connect to the Ubuntu 20.04 server.

  2. Install Node.js and npm:

    $ sudo apt update
    $ sudo apt install -y nodejs npm

Obtain the security certificate for authentication

The GravityZone cloud platform only sends Push Event messages to HTTPS capable collectors. For the collector service to function over HTTPS, and provide a secure communication with Bitdefender Cloud, you need to set it up to function with an SSL/TLS certificate. You can obtain an SSL/TLS certificate for this service in a few ways:

  1. From a trusted public Certificate Authority (CA)

    We strongly recommend this method, since it will allow our Cloud servers to properly validate the identity the URL of the collector and avoid any man-in-the-middle attacks.

    To obtain such a certificate, you need to generate a certificate signing request (CSR) with the correct information and provide it to your public certificate provider to be signed.

    Learn about generating a CSR

  2. From your company’s internal PKI

    Not recommended because the Bitdefender public cloud service is not be able to validate a certificate signed by a private CA. We recommend to always use certificates signed by publicly trusted CAs for publicly facing services such as the collector you are about to configure.

    To obtain such a certificate, you also need to generate a CSR with the correct information, and provide it to your company’s PKI administrators to be signed.

    Learn about generating a CSR

  3. Create a self-signed certificate

    We strongly advise against this option. It does not provide any certificate validation methods and exposes the communication to man-in-the-middle attacks. This method should only be used for testing purposes and never in production environments.

    Generate a self-signed certificate using OpenSSL

Further on, you will need the sslkey.pem and ssl.cer/ssl.crt files, signed by your CA of choice.

Build the Node.js connector

The connector uses the following files:

  • package.json

  • config.json

  • server.js

  • epsSyslogHelper.js

You can either modify the template files available here, or create them from scratch as described in this section.

To build the Node.js connector, follow the next steps:

  1. Create a directory on your server in which you would like to place the connector application:

    $ mkdir folder_name
    $ cd folder_name


    This is the directory which we will refer to as APP_ROUTE

  2. Create the package.json file:

    1. Create the file:

      $ vim package.json
    2. Enter in edit mode and copy the JSON content:

           "name": "app-name",
           "version": "1.0.0",
           "private": true,
           "description": "client that will be able to
      receive authenticated POST requests and write each row in the local or remote
           "main": "server.js",
           "scripts": {
                    "test": "echo \"Error: no test
      specified\" && exit 1"
           "author": "",
           "license": "MIT",
           "dependencies": {
    3. Save the file and quit vim.


    You can use this online tool to validate the syntax of any json file in this article and avoid errors.

  3. Fetch dependencies:

    • Express framework

      Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. You need Express to create the HTTP server.

      Enter the following command to install Express and add it into package.json file:

      $ npm install express --save
    • Syslog client

      The Syslog client module facilitates sending messages to a Syslog server across networks.

      Enter the following command to install the Syslog client module and add it into package.json file:

      $ npm install syslog-client --save
  4. Create the config.json file

    The path for the file should be APP_ROUTE/api/config/config.json

    This file contains the settings used by server.js when starting.

    1. Create the following directory for the configuration file:

      $ mkdir -p api/config
    2. Create the file:

      $ vim api/config/config.json
    3. Enter in edit mode and then add the following JSON content:

           "authorization_string":"Basic dGVzdDp0ZXN0",
    4. Replace the values of the following fields with your actual data:

      • port– the port number on which the connector will listen for incoming events from GravityZone

      • syslog_port – the TCP or UDP port number on which to forward messages to a syslog server

      • transport – the network protocol to send messages over to the syslog server

      • target – the IP address/FQDN of the Syslog server

      • authorization_string – the authorization string for the service (create a random string here, but make sure to keep this format: "Basic xxxxxxxxxxxxx")

      • passphrase – the password of the security certificate private key

    5. Save the file and quit vim.

    6. Copy or move sslkey.pem and ssl.cer certificate files to the api/config/ directory.

  5. Create the server.js file directory.

    The path for the file should be APP_ROUTE/server.js.

    1. Create the file:

      $ vim server.js
    2. Enter in edit mode and copy the following code:

      'use strict';
      const express = require('express');
      const app = express();
      const fs = require('fs');
      const https = require("https");
      const path = require('path');
      const textParser = express.json({limit:’50mb’});
      const epsSyslogHelper = require('./api/epsSyslogHelper');
      if (!process.argv[2]) {
          throw "Missing input file parameter";
      let configPath = process.argv[2];
      const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
      console.log('Load Config file');
      // use basic HTTP auth to secure the api
      app.use('/api', (req, res, next) => {
          // check for basic auth header
          if (!req.headers.authorization) {
              return res.status(401).json({ message: 'Missing Authorization Header' });
          // verify auth credentials
          const authorizationString =  req.headers.authorization;
          if (config.authorization_string !== authorizationString) {
              return res.status(401).json({ message: 'Invalid Authentication Credentials' });
      // url: http://localhost:3000/api/
   '/api', textParser, (request, response) => {
          const body = request.body;
          let syslogHelper = new epsSyslogHelper(config);
      if (! || ! || ! {
          console.error(`please provide secure options in the config file: ${JSON.stringify(config)}`)
          const options = {
              key: fs.readFileSync(path.resolve(__dirname,,
              cert: fs.readFileSync(path.resolve(__dirname,,
          .createServer(options, app)
          .listen(config.port, () => console.log(`Listening on port ${config.port}`));
      } catch (e) {
          console.error('error on starting server')
    3. Save the file and quit vim.

  6. Create the epsSyslogHelper.js file.

    The path for the file should be APP_ROUTE/api/epsSyslogHelper.js.

    1. Create the file:

      $ vim api/epsSyslogHelper.js
    2. Enter in edit mode and then add the following code:

      'use strict';
      let EpsSyslogHelper;
      const syslog = require('syslog-client');
      const os = require('os');
       * EventConverter class
      EpsSyslogHelper = function (config) {
          let hostName = os.hostname();
          if (!hostName) {
              hostName = 'localhost';
          let dot = hostName.indexOf('.');
          if (dot > 0) {
              hostName = hostName.substring(0, dot);
          console.log('Logging using host name %s', hostName);
          this._client = syslog.createClient(, {
              syslogHostname: hostName,
              port: config.syslog_port,
              transport: syslog.Transport[config.transport],
          this._client.on('error', function(err) {
              console.error('Error from syslog network: %s', err);
      EpsSyslogHelper.prototype.log = function _log(msg) {
          let options = {
              facility: syslog.Facility.Local0,
              severity: syslog.Severity.Informational
          let events;
              events =;
          } else if (msg.hasOwnProperty('jsonrpc')) {
               events =;
              for(let eventKey in events) {
                  let syslogMessage = events[eventKey];
                  if(typeof syslogMessage !== 'string') {
                      syslogMessage = JSON.stringify(syslogMessage);
                  console.log("Event key = " + eventKey + " is = " + syslogMessage);
                  const me  = this;
                  this._client.log(syslogMessage, options, function (err) {
                      if (err) {
                      } else {
                          console.log('EventSent to syslog')
      // export module
      module.exports = EpsSyslogHelper;
    3. Save the file and quit vim.

  7. Start the Node server:

    node server.js api/config/config.json

    The connector URL will have the following format: https://your_web_server_hostname_or_public_IP:port/api.

Test the connector

Use this HTTPS message example to test the connector you have just configured:

  • Event Push Service request header

    Authorization: Basic xxxxxxxxxxxxxx
  • Event Push Service payload

    "cef": "0",
     "events": [
    	"CEF:0|Bitdefender|GravityZone|6.4.08|70000|Registration|3| dvc=",
    "CEF:0|Bitdefender|GravityZone|6.4.0-8|35|Product ModulesStatus|5|BitdefenderGZModule=modules dvc=",
    "CEF:0|Bitdefender|GravityZone|6.4.0-8|35|Product ModulesStatus|5|BitdefenderGZModule=modules dvc="
  • Use the following cURL command to send the payload to the collector service:

    curl -k -H 'Authorization: Basic xxxxxxxxxxxxxxxxxx' \
    -H "Content-Type: application/json" \
    -d '{"cef": "0","events": ["CEF:0|Bitdefender|GravityZone|6.4.08|70000|Registration|3| dvc=","CEF:0|Bitdefender|GravityZone|6.4.0-8|35|Product ModulesStatus|5|BitdefenderGZModule=modules dvc=","CEF:0|Bitdefender|GravityZone|6.4.0-8|35|Product ModulesStatus|5|BitdefenderGZModule=modules dvc="]}' \


    Replace the authorization header and URL with the one configured above in the config.json file.

    The event should appear in your defined syslog server and as output of the running server.js.

Configure GravityZone to send messages to the SIEM

Now that the HTTPS collector service is running and listening for messages, you can configure Control Center to send events to the above-defined URL: https://your_web_server_hostname_or_public_IP:port/api.

All settings for Event Push Service API are configured via the setPushEventSettings method. For detailed information about these settings, refer to the Push section of the GravityZone API Guide. Access the right guide for you:

Using your API key of choice, configure the API push events and the service URL where you want the messages delivered:

$ curl --tlsv1.2 -sS -k -X POST \
https://CONTROL_CENTER_APIs_ACCESS_URL/v1.0/jsonrpc/push \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{"id":"1","jsonrpc":"2.0","method":"setPushEventSettings","params":{"serviceSettings":{"requireValidSslCertificate":false,"authorization":"Basic xxxxxxxxxx","url":" https://your_web_server_hostname_or_public_IP:port/api"},"serviceType":"cef","status":1,"subscribeToEventTypes":{"adcloudgz":true,"antiexploit":true,"aph":true,"av":true,"avc":true,"dp":true,"endpoint-moved-in":true,"endpoint-moved-out":true,"exchange-malware":true,"exchange-user-credentials":true,"fw":true,"hd":true,"hwid-change":true,"install":true,"modules":true,"network-monitor":true,"network-sandboxing":true,"new-incident":true,"ransomware-mitigation":true,"registration":true,"supa-update-status":true,"sva":true,"sva-load":true,"task-status":true,"troubleshooting-activity":true,"uc":true,"uninstall":true}}}'


When using a valid service certificate signed by a public CA, we recommend setting  "requireValidSslCertificate":true , to force certificate validation. If you are using a self-signed certificate or a certificate signed by your internal CA, set "requireValidSslCertificate":false.


Make sure to replace "authorization":"Basic xxxxxxxxxx" and "url":" https://your_web_server_hostname_or_public_IP:port/api" with the correct values for your server, as defined in the config.json file, and CONTROL_CENTER_APIs_ACCESS_URL and API_KEY_BASE64_ENCODED_WITH_SEMICOLON_APPENDED with the correct values for your GravityZone instance.

Once configured, wait about 10 minutes for the settings to take effect, and then make a request using getPushEventSettings.

$ curl --tlsv1.2 -sS -k -X POST \
https://CONTROL_CENTER_APIs_ACCESS_URL/v1.0/jsonrpc/push \
-H 'authorization: Basic API_KEY_BASE64_ENCODED' \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{"id":"3","jsonrpc":"2.0","method":"getPushEventSettings","params":{}}'

The result should look like this:

  "id": "2",
  "jsonrpc": "2.0",
  "result": {
    "serviceSettings": {
      "authorization": "********",
      "requireValidSslCertificate": false,
      "url": "https://your_web_server_hostname_or_public_IP:port/api"
    "serviceType": "cef",
    "status": 1,
    "subscribeToCompanies": null,
    "subscribeToEventTypes": {
      "adcloud": false,
      "antiexploit": true,
      "aph": true,
      "av": true,
      "uninstall": true

To send a test event, you can call the sendTestPushEvent API method.

$ curl --tlsv1.2 -sS -k -X POST \
https://CONTROL_CENTER_APIs_ACCESS_URL/v1.0/jsonrpc/push \
-H 'authorization: Basic API_KEY_BASE64_ENCODED' \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{"id":"4","jsonrpc":"2.0","method":"sendTestPushEvent","params":{"eventType": "av"}}'

The result should look like this:

  "id": "4",
  "jsonrpc": "2.0",
  "result": {
    "computer_name": "FC-WIN7-X64-01",
    "computer_fqdn": "fc-win7-x64-01",
    "computer_ip": "",
    "computer_id": "59a1604e60369e06733f8abb",
    "product_installed": "BEST",
    "malware_type": "file",
    "malware_name": "EICAR-Test-File (not a virus)",
    "file_path": "C:\\eicar0000001.txt",
    "hash": "8b3f191819931d1f2cef7289239b5f77c00b079847b9c2636e56854d1e5eff71",
    "final_status": "deleted",
    "timestamp": "2017-09-08T12:01:36.000Z",
    "companyId": "5ac8460f8a799399a78b456c",
    "module": "av",
    "_testEvent_": true

The event should shortly show up in the Syslog server and in the server.js output.